Programiści C często przyjmowali ulotność, co oznaczało, że zmienna może być zmieniona poza bieżącym wątkiem wykonywania; w rezultacie czasami ulegają pokusie, aby użyć go w kodzie jądra, gdy używane są współdzielone struktury danych. Innymi słowy, są znane z traktowania typów lotnych jako pewnego rodzaju łatwej zmiennej atomowej, którymi nie są. Użycie niestabilności w kodzie jądra prawie nigdy nie jest poprawne; ten dokument opisuje dlaczego.
Kluczową kwestią do zrozumienia w odniesieniu do zmienności jest to, że jej celem jest powstrzymanie optymalizacji, która prawie nigdy nie jest tym, czego naprawdę chcemy. W jądrze należy chronić współdzielone struktury danych przed niechcianym równoczesnym dostępem, co jest zupełnie innym zadaniem. Proces ochrony przed niechcianą współbieżnością pozwoli również skuteczniej uniknąć prawie wszystkich problemów związanych z optymalizacją.
Podobnie jak nietrwałe, prymitywy jądra, które zapewniają bezpieczeństwo równoczesnego dostępu do danych (blokady spinowe, muteksy, bariery pamięci itp.) Są zaprojektowane tak, aby zapobiegać niepożądanej optymalizacji. Jeśli są używane prawidłowo, nie będzie potrzeby używania również substancji lotnych. Jeśli niestabilność jest nadal konieczna, prawie na pewno gdzieś w kodzie jest błąd. W prawidłowo napisanym kodzie jądra zmienność może służyć jedynie spowolnieniu działania.
Rozważmy typowy blok kodu jądra:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Jeśli cały kod jest zgodny z regułami blokowania, wartość shared_data nie może się nieoczekiwanie zmienić, gdy blokada_blokuje. Każdy inny kod, który może chcieć grać z tymi danymi, będzie czekał na zamku. Prymitywy spinlock działają jak bariery pamięci - są wyraźnie napisane, aby to robić - co oznacza, że dostęp do danych nie będzie w nich optymalizowany. Zatem kompilator może pomyśleć, że wie, co będzie w shared_data, ale wywołanie spin_lock (), ponieważ działa jako bariera pamięci, zmusi go do zapomnienia wszystkiego, co wie. Nie będzie problemów z optymalizacją dostępu do tych danych.
Gdyby dane shared_data zostały zadeklarowane jako ulotne, blokowanie byłoby nadal konieczne. Ale kompilator nie byłby również w stanie zoptymalizować dostępu do shared_data w sekcji krytycznej, gdy wiemy, że nikt inny nie może z nim pracować. Gdy blokada jest utrzymywana, shared_data nie są ulotne. W przypadku współdzielonych danych właściwe blokowanie sprawia, że niestabilność jest niepotrzebna - i potencjalnie szkodliwa.
Klasa pamięci ulotnej była pierwotnie przeznaczona dla rejestrów we / wy mapowanych w pamięci. Wewnątrz jądra dostęp do rejestrów również powinien być chroniony blokadami, ale nie chce się także, aby kompilator „optymalizował” dostęp do rejestrów w krytycznej sekcji. Ale w jądrze dostęp do pamięci I / O zawsze odbywa się poprzez funkcje akcesorów; dostęp do pamięci we / wy bezpośrednio przez wskaźniki jest mile widziany i nie działa na wszystkich architekturach. Te akcesory zostały napisane, aby zapobiec niechcianej optymalizacji, więc po raz kolejny niestabilność jest niepotrzebna.
Inną sytuacją, w której można by pokusić się o użycie zmiennej volatile jest sytuacja, gdy procesor jest zajęty czekaniem na wartość zmiennej. Właściwy sposób na zajęte oczekiwanie to:
while (my_variable != what_i_want)
cpu_relax();
Wywołanie cpu_relax () może obniżyć zużycie energii przez procesor lub dać podwójnemu procesorowi z hiperwątkiem; zdarza się, że służy również jako bariera pamięci, więc po raz kolejny zmienność jest niepotrzebna. Oczywiście czekanie z zajętością jest na ogół aktem antyspołecznym.
Nadal istnieje kilka rzadkich sytuacji, w których zmienność ma sens w jądrze:
Wspomniane powyżej funkcje akcesorów mogą być używane w architekturach, w których bezpośredni dostęp do pamięci we / wy działa. Zasadniczo każde wywołanie akcesora staje się samo w sobie małą krytyczną sekcją i zapewnia, że dostęp odbywa się zgodnie z oczekiwaniami programisty.
Wbudowany kod asemblera, który zmienia pamięć, ale nie ma innych widocznych skutków ubocznych, może zostać usunięty przez GCC. Dodanie słowa kluczowego volatile do instrukcji asm zapobiegnie temu usunięciu.
Zmienna jiffies jest wyjątkowa, ponieważ może mieć inną wartość za każdym razem, gdy jest przywoływana, ale można ją odczytać bez specjalnego blokowania. Tak więc wahania mogą być niestabilne, ale dodanie innych zmiennych tego typu jest mocno mile widziane. Pod tym względem Jiffies jest uważany za „głupią spuściznę” (słowa Linusa); naprawienie go byłoby większym kłopotem niż jest warte.
Wskaźniki do struktur danych w pamięci koherentnej, które mogą być modyfikowane przez urządzenia we / wy, mogą czasami być uzasadnione. Przykładem tego typu sytuacji jest bufor pierścieniowy używany przez kartę sieciową, w którym karta ta zmienia wskaźniki, aby wskazać, które deskryptory zostały przetworzone.
W przypadku większości kodów żadne z powyższych uzasadnień dla nietrwałości nie ma zastosowania. W rezultacie użycie volatile może być postrzegane jako błąd i będzie wymagało dodatkowej analizy kodu. Deweloperzy, którzy mają pokusę korzystania z niestabilności, powinni cofnąć się o krok i pomyśleć o tym, co naprawdę próbują osiągnąć.