Systemy Operacyjne Ä… semestr drugi WykÅ‚ad ósmy Åšrodki synchronizacji w Linuksie 1. Operacje niepodzielne na liczbach caÅ‚kowitych i na bitach Operacje niepodzielne (atomowe) na zmiennych prostych typów sÄ… zazwyczaj realizowane za pomocÄ… instrukcji maszynowych wÅ‚aÅ›ciwych dla architektury procesora. Listy rozkazów niektórych procesorów zawierajÄ… rozkazy, które pozwalajÄ… wprost wykonać niepodzielnie takie instrukcje jak np.: dodawanie, mnożenie, odczyt, zapis, inne dostarczajÄ… rozkazów uÅ‚atwiajÄ…cych implementacjÄ™ takich operacji w systemach wieloprocesorowych (np. rozkaz blokowania magistrali), jeszcze inne nie posiadajÄ… żadnych rozkazów tego typu (np. SPARC). JÄ…dro systemu Linux udostÄ™pnia szereg interfejsów (funkcji) korzystajÄ…cych z tych rozkazów lub pozwalajÄ…cych w inny sposób zrealizować operacje niepodzielne na typach prostych. Twórcy jÄ…dra wyróżnili specjalny typ atomic_t, który stosowany jest w miejsce zwykÅ‚ego typu Ä…intº. Pozwala to na zdefiniowanie funkcji, które pracujÄ… tylko na takim typie, ukrycie szczegółów implementacji oraz zabezpieczenie przed bÅ‚Ä™dnÄ… optymalizacjÄ… na poziomie kompilacji. Typ atomic_t jest 32-bitowy i bazuje na typie Ä…intº. We wczesnych wersjach jÄ…dra serii 2.6 pozwalaÅ‚ on na przechowywanie wartoÅ›ci jedynie 24-bitowych (3 bajty). MÅ‚odsze 8 bitów (1 bajt) zajmowane byÅ‚o przez blokadÄ™, która byÅ‚a konieczna w przypadku takich architektur jak wspomniany wyżej SPARC. W pózniejszych wersjach jÄ…dra znaleziono sposób na zlikwidowanie tej niedogodnoÅ›ci. Funkcje realizujÄ…ce operacje niepodzielne na liczbach caÅ‚kowitych sÄ… implementowane jako makrodefinicje lub funkcje Ä…inlineº. Oto te z tych funkcji, które dostÄ™pne sÄ… na wszystkich platformach sprzÄ™towych: " ATOMIC_INIT(int i) Ä… pozwala na inicjalizacjÄ™ zmiennej typu atomic_t w miejscu deklaracji, " int atomic_read(atomic_t *v) Ä… niepodzielny odczyt caÅ‚kowitej wartoÅ›ci Ä…vº, " void atomic_set(atomic_t *v, int i) Ä… niepodzielne przypisanie wartoÅ›ci Ä…iº do Ä…vº, " void atomic_add(int i, atomic_t *v) Ä… niepodzielne dodanie wartoÅ›ci Ä…iº do Ä…vº, " void atomic_sub(int i, atomic_t *v) Ä… niepodzielne odejmowanie wartoÅ›ci Ä…iº od Ä…vº, " void atomic_inc(atomic_t *v) Ä… niepodzielna inkrementacja Ä…vº, " void atomic_dec(atomic_t *v) Ä… niepodzielna dekrementacja Ä…vº, " int atomic_sub_and_test(int i, atomic_t *v) Ä… niepodzielne odjÄ™cie Ä…iº od Ä…vº i zwrócenie wartoÅ›ci Ä…trueº, jeÅ›li różnica wynosi Ä…0º, " int atomic_add_negative(int i, atomic_t *v) Ä… niepodzielne dodanie Ä…iº do Ä…vº i zwrócenie wartoÅ›ci Ä…trueº, jeÅ›li suma jest ujemna, " int atomic_dec_and_test(atomic_t *v) Ä… niepodzielna dekrementacja Ä…vº i zwrócenie wartoÅ›ci Ä…trueº jeÅ›li po tej operacji Ä…vº bÄ™dzie miaÅ‚a wartość Ä…0º, " int atomic_inc_and_test(atomic_t *v) Ä… niepodzielna inkrementacja Ä…vº i zwrócenie wartoÅ›ci Ä…trueº jeÅ›li po tej operacji Ä…vº bÄ™dzie miaÅ‚a wartość Ä…0º. Ze wzglÄ™du na rosnÄ…cÄ… popularność platform 64-bitowych na pewnym etapie rozwoju serii 2.6 jÄ…dra dodano typ atomic64_t. Jest to 64-bitowy odpowiednik typu atomic_t. Funkcje, które go obsÅ‚ugujÄ… majÄ… takie same nazwy jak te opisane wyżej, ale rozpoczynajÄ…ce siÄ™ przedrostkiem atomic64_. Oprócz operacji niepodzielnych na liczbach caÅ‚kowitych jÄ…dro dostarcza funkcji realizujÄ…cych niepodzielne operacje na pojedynczych bitach. Nie dziaÅ‚ajÄ… one na żadnym konkretnym typie, tylko pobierajÄ… przez parametry adres miejsca w pamiÄ™ci i numer bitu: " void set_bit(int nr, void *addr) Ä… niepodzielne ustawienie bitu okreÅ›lonego przez Ä…nrº zmiennej o adresie Ä…addrº, " void clear_bit(int nr, void *addr) Ä… niepodzielne wyzerowanie bitu okreÅ›lonego przez Ä…nrº zmiennej o adresie Ä…addrº, " int test_and_set_bit(int nr, void *addr) Ä… niepodzielne ustawienie bitu okreÅ›lonego przez Ä…nrº zmiennej o adresie Ä…addrº, wraz ze zwróceniem jego poprzedniej wartoÅ›ci, " int test_and_clear_bit(int nr, void *addr) Ä… niepodzielne wyzerowanie bitu okreÅ›lonego przez Ä…nrº zmiennej o adresie Ä…addrº, wraz ze zwróceniem jego poprzedniej wartoÅ›ci, " int test_and_change_bit(int nr, void *addr) Ä… niepodzielna negacja bitu okreÅ›lonego przez Ä…nrº zmiennej o adresie Ä…addrº, wraz ze zwróceniem jego poprzedniej wartoÅ›ci, " int test_bit(int nr, void *addr) Ä… niepodzielne testowanie bitu okreÅ›lonego przez Ä…nrº zmiennej o adresie Ä…addrº. IstniejÄ… także odpowiedniki tych funkcji realizujÄ…ce takie same operacje, ale nie w sposób niepodzielny. Nazwy tych funkcji sÄ… takie same, z tym, że rozpoczynajÄ… siÄ™ znakami Ä…__º (podwójnego podkreÅ›lenia). JÄ…dro dostarcza również funkcji pozwalajÄ…cych na znalezienie pierwszego ustawionego bitu w okreÅ›lonym obszarze pamiÄ™ci i pierwszego bitu o wartoÅ›ci zero w okreÅ›lonym obszarze pamiÄ™ci: find_first_bit() i find_first_zero_bit(). JeÅ›li chcemy przeszukać tylko jedno sÅ‚owo możemy skorzystać z szybszych wywoÅ‚aÅ„ __ffs() i __ffz(). 2. Rygle pÄ™tlowe Najczęściej stosowanym rodzajem blokady sÄ… rygle pÄ™tlowe (ang. spin lock), które chroniÄ… Ä…wiÄ™kszeº zasoby (takie jak np. listy zadaÅ„) niż zmienne typów caÅ‚kowitych lub poszczególne bity słów w pamiÄ™ci operacyjnej. Dany rygiel może przetrzymywać tylko jeden wÄ…tek wykonania (zasada wzajemnego wykluczania), natomiast inne wÄ…tki chcÄ…ce skorzystać z chronionego zasobu wykonujÄ… aktywne oczekiwanie, czyli w pÄ™tli oczekujÄ…, aż rygiel zostanie zwolniony i jeden z nich bÄ™dzie mógÅ‚ uzyskać dostÄ™p do zasobu. Ponieważ aktywne oczekiwanie jest marnotrawieniem czasu procesora rygle pÄ™tlowe powinny być stosowane wszÄ™dzie tam, gdzie nie można zawiesić wÄ…tku i gdzie czas przeÅ‚Ä…czania kontekstu byÅ‚by niewspółmiernie dÅ‚uższy z czasem aktywnego oczekiwania. Rygle pÄ™tlowe mogÄ… być używane w procedurach obsÅ‚ugi przerwaÅ„, ale tylko wraz z wyÅ‚Ä…czeniem lokalnego systemu przerwaÅ„, aby uniknąć zakleszczeÅ„. Należy również pamiÄ™tać, że rygle nie sÄ… rekurencyjne i nie sÄ… stosowane w systemach jednoprocesorowych (kompilator wstawia w ich miejsce puste instrukcje lub jeÅ›li podczas kompilacji wÅ‚Ä…czona jest opcja wywÅ‚aszczania jÄ…dra zastÄ™puje je funkcjami wÅ‚Ä…czajÄ…cymi i wyÅ‚Ä…czajÄ…cymi wywÅ‚aszczanie jÄ…dra, opisanymi niżej). JÄ…dro udostÄ™pnia szereg funkcji zwiÄ…zanych z obsÅ‚ugÄ… rygli pÄ™tlowych: " spin_lock() - zakÅ‚ada zadany rygiel, " spin_lock_irq() - wyÅ‚Ä…cza lokalne przerwania i zakÅ‚ada rygiel, " spin_lock_irqsave() - zachowuje stan lokalnych przerwaÅ„, wyÅ‚Ä…cza je i zakÅ‚ada rygiel, " spin_lock_bh() - zakÅ‚ada rygiel i wyÅ‚Ä…cza dolne połówki, 1 Systemy Operacyjne Ä… semestr drugi " spin_unlock() - zwalnia rygiel, " spin_unlock_irq() - wÅ‚Ä…cza przerwania i zwalnia rygiel, " spin_unlock_irqrestore() - przywraca stan przerwaÅ„ i zwalnia rygiel, " spin_unlock_bh() - zwalnia rygiel i wÅ‚Ä…cza dolne połówki, " spin_trylock() - próbuje zaÅ‚ożyć rygiel, jeÅ›li operacja ta siÄ™ nie powiedzie zwraca zero i nie powoduje wejÅ›cia w pÄ™tlÄ™ aktywnego oczekiwania, " spin_is_locked() - zwraca wartość różnÄ… od zera, jeÅ›li rygiel jest już przetrzymywany, " spin_lock_init() - inicjalizuje rygiel (zmienna typu spinlock_t) tworzony w sposób dynamiczny. JeÅ›li problem, który chcemy rozwiÄ…zać sprowadza siÄ™ do problemu pisarzy i czytelników, a Å›ciÅ›lej do wersji tego problemu, gdzie faworyzowani sÄ… czytelnicy, to możemy zastosować rygle pÄ™tlowe R-W. Rygiel dla czytelników może być przetrzymywany przez wiÄ™kszÄ… liczbÄ™ wÄ…tków wykonania tego typu (a nawet może być rekurencyjnie zakÅ‚adany przez jeden wÄ…tek wykonania tego typu), rygiel dla pisarzy Ä… tylko przez jeden wÄ…tek wykonania tego typu. Ponadto pisarz może zakÅ‚adać rygiel tylko wtedy, gdy z zasobu nie korzysta żaden z czytelników. Również w przypadku rygli R-W istnieje w jÄ…drze szereg funkcji pozwalajÄ…cych na manipulacjÄ™ nimi: " read_lock() - zakÅ‚ada rygiel R (dla czytelnika), " read_lock_irq() - zakÅ‚ada rygiel R (dla czytelnika) i wyÅ‚Ä…cza lokalne przerwania, " read_lock_irqsave() - zakÅ‚ada rygiel R, zapamiÄ™tuje stan lokalnych przerwaÅ„ i wyÅ‚Ä…cza je, " read_lock_bh() - zakÅ‚ada rygiel R (dla czytelnika) i wyÅ‚Ä…cza dolne połówki, " read_unlock() - zdejmuje rygiel R, " read_unlock_irq() - zdejmuje rygiel R i wÅ‚Ä…cza lokalne przerwania, " read_unlock_irqrestore() - zdejmuje rygiel R i przywraca stan lokalnych przerwaÅ„, " read_unlock_bh() - zdejmuje rygiel R (dla czytelnika) i wÅ‚Ä…cza dolne połówki, " write_lock() - zakÅ‚ada rygiel W (dla pisarza), " write_lock_irq() - zakÅ‚ada rygiel W (dla pisarza) i wyÅ‚Ä…cza lokalne przerwania, " write_lock_irqsave() - zakÅ‚ada rygiel W, zapamiÄ™tuje stan loklanych przerwaÅ„ i wyÅ‚Ä…cza je, " write_lock_bh() - zakÅ‚ada rygiel W (dla pisarza) i wyÅ‚Ä…cza dolne połówki, " write_ulock() - zdejmuje rygiel W, " write_ulock_irq() - zdejmuje rygiel W i wÅ‚Ä…cza lokalne przerwania, " write_ulock_irqrestore() - zdejmuje rygiel W i przywraca stan lokalnych przerwaÅ„, " write_ulock_bh() - zdejmuje rygiel W i wÅ‚Ä…cza dolne połówki, " write_trylock() - próbuje zaÅ‚ożyć rygiel W, w przypadku niepowodzenia, zwraca wartość równÄ… zero, ale nie powoduje rozpoczÄ™cia pÄ™tli aktywnego oczekiwania, " rw_lock_init() - inicjalizuje strukturÄ™ typu rwlock_t, " rw_is_locked() - jeÅ›li rygiel jest przetrzymywany, to zwraca wartość różnÄ… od zera. 3. Semafory Semafory w przeciwieÅ„stwie do rygli pÄ™tlowych powodujÄ… wprowadzenie wÄ…tków wykonania oczekujÄ…cych na ich podniesienie w stan zawieszenia. Takie wÄ…tki wstawiane sÄ… do kolejki zadaÅ„ oczekujÄ…cych (ang. waitqueue). Semafory nie mogÄ… być przetrzymywane przez wÄ…tki, które już przetrzymujÄ… rygle pÄ™tlowe. Powyższe cech powodujÄ…, że semafory sÄ… używane tylko w kontekÅ›cie procesu, kiedy czas oczekiwania na ich podniesienie jest dużo dÅ‚uższy niż czas przeÅ‚Ä…czania kontekstu. Semafory nie powodujÄ…, tak jak rygle pÄ™tlowe wyÅ‚Ä…czenia wywÅ‚aszczania jÄ…dra, ponadto umożliwiajÄ… przetrzymywanie blokady przez dowolnÄ…, z góry okreÅ›lonÄ… przez licznik semafora liczbÄ™ wÄ…tków. Oto funkcje zwiÄ…zane z obsÅ‚ugÄ… semaforów: " sema_init(sturct semaphore *, int) Ä… inicjalizuje dynamicznie utworzonÄ… strukturÄ™ semafora podanÄ… wartoÅ›ciÄ…, " down_interruptible(struct semaphore *) - próbuje opuÅ›cić semafor, jeÅ›li to siÄ™ nie udaje to wprowadza wÄ…tek wykonania w stan TASK_INTERRUPTIBLE, " down(struct semaphore *) - podobnie jak wyżej, ale wÄ…tek roboczy jest wprowadzany w stan TASK_UNINTERRUPTIBLE, " down_killable(struct semaphore *) - dostÄ™pna od wersji 2.6.26 jÄ…dra, dziaÅ‚a jak down_interruptible(), ale wprowadza wÄ…tek w stan TASK_KILLABLE, " down_timeout(struct semaphore *) - dostÄ™pna od wersji 2.6.26 pozwala ograniczyć czas oczekiwania na podniesienie semafora, " down_trylock(struct semaphore *) - próbuje opuÅ›cić semafor, jeÅ›li nie jest to możliwe zwraca wartość niezerowÄ… i nie zawiesza zadania, " up(struct semaphore *) - podnosi semafor i budzi jeden z wÄ…tków roboczych czekajÄ…cych na jego podniesienie. 2 Systemy Operacyjne Ä… semestr drugi Semafory reprezentowane sÄ… przez strukturÄ™ o nazwie Ä…semaphoreº. Statycznie semafory można inicjalizować za pomocÄ… DEFINE_SEMAPHORE. Podobnie jak w przypadku rygli istniejÄ… semafory R-W. Inicjalizuje siÄ™ je statycznie przy pomocy DECLARE_RWSEM, a dynamicznie przy pomocy init_rwsem(). Funkcje obsÅ‚ugi dziaÅ‚ajÄ… podobnie jak w przypadku zwykÅ‚ych semaforów, ale sÄ… podzielone na przeznaczone dla czytelników (nazwy zakoÅ„czone sÅ‚owem read) i pisarzy (write). Należy pamiÄ™tać, że funkcje down_read_trylock() i down_write_trylock() zwracajÄ… wartoÅ›ci o znaczeniu odwrotnym niż funkcja down_trylock(). Ponadto dla tych semaforów istnieje unikatowa funkcja downgrade_writer() pozwalajÄ…ca przeksztaÅ‚cić pisarza do roli czytelnika. 4. Muteksy Osoby z grona programistów jÄ…dra systemu Linux, które sÄ… odpowiedzialne za mechanizmy synchronizacji zauważyÅ‚y, że najczęściej używanym rodzajem semaforów sÄ… semafory binarne, które peÅ‚niÄ… rolÄ™ muteksów, dlatego w wersji jÄ…dra 2.6.16 zostaÅ‚a wprowadzona poprawka, która definiuje osobny typ danych o nazwie mutex. Jest on okreÅ›lony strukturÄ…, którÄ… można zapisać nastÄ™pujÄ…co (bez uwzglÄ™dnienia pól zwiÄ…zanych z debugowaniem): struct mutex { atomic_t count; spinlock_t wait_lock; struct list_head wait_list; }; W przeciwieÅ„stwie do struktury semaphore, zawartość tej struktury jest niezależna od architektury procesora. TÄ… cechÄ™ posiadajÄ… również w dużej mierze implementacje operacji wykonywanych na niej Ä… mogÄ… być co najwyżej optymalizowane pod wzglÄ™dem czasu wykonania na poszczególnych platformach. Pole count przechowuje stan muteksu. JeÅ›li jego wartość jest równa jeden to jest muteks jest wolny, zero Ä… zajÄ™ty, mniejsza od zera Ä… zajÄ™ty i na jego podniesienie czeka co najmniej jeden wÄ…tek. Rozróżnienie dwóch stanów zajÄ™toÅ›ci pozwala okreÅ›lić, czy konieczne jest budzenie wÄ…tków. API muteksów jest dostÄ™pne po wÅ‚Ä…czeniu pliku nagłówkowego linux/mutex.h i obejmuje nastÄ™pujÄ…ce funkcje: " DEFINE_MUTEX(name) Ä… inicjalizacja muteksu na etapie kompilacji, " mutex_init(struct mutex *lock) Ä… inicjalizacja muteksu na etapie wykonania, " mutex_lock_interruptible(struct mutex *lock) Ä… zajÄ™cie muteksu, jeÅ›li operacja siÄ™ nie powiedzie to wÄ…tek jest ustawiany w stan TASK_INTERRUPTIBLE, " mutex_lock(struct mutex *lock) Ä… jak wyżej, ale wÄ…tek jest ustawiany w stan TASK_UNINTERRUPTIBLE, " mutex_lock_killable(struct mutex *lock) - jak wyżej, ale wÄ…tek jest ustawiany w stan TASK_KILLABLE, " mutex_trylock(struct mutex *lock) Ä… zwraca zero jeÅ›li nie uda siÄ™ zająć muteksu, jeden w przeciwnym przypadku, " mutex_unlock(struct mutex *lock) Ä… zwalnia mutex, " mutex_is_locked(struct mutex *lock) Ä… zwraca wartość wiÄ™kszÄ… od zera jeÅ›li mutex jest zajÄ™ty, zero jeÅ›li jest dostÄ™pny. Muteksy nie sÄ… blokadami rekurencyjnymi i nie mogÄ… być używane w kontekÅ›cie przerwania. Do jÄ…dra wprowadzono także muteksy czasu rzeczywistego. Ich dziaÅ‚anie jest takie samo jak zwykÅ‚ych muteksów, ale wÄ…tek, który zajmie taki mutex zyskuje priorytet czasu rzeczywistego. Ma to na celu zapobieżenie wystÄ…pieniu zjawiska, które nazywa siÄ™ Ä…inwersjÄ… priorytetówº. IntencjÄ… autorów wprowadzenia muteksów do kodu jÄ…dra jest caÅ‚kowite usuniÄ™cie semaforów i zastÄ…pienie ich muteksami, jednakże jak do tej pory nie znaleziono innego rozwiÄ…zania dla kodu, który używa semaforów, które nie sÄ… binarne. 5. Zmienne sygnaÅ‚owe (ang. completion) Zmienne sygnaÅ‚owe sÅ‚użą do synchronizacji pracy wÄ…tków i sÄ… uproszczonÄ… wersjÄ… semaforów. Ich typ jest okreÅ›lony strukturÄ… struct completion. Najczęściej sÄ… one tworzone jako dynamiczne skÅ‚adowe wiÄ™kszych struktur danych. MogÄ… być definiowane statycznie przy pomocy DECLARE_COMPLETION(mr_comp). Z obsÅ‚ugÄ… tych zmiennych zwiÄ…zane sÄ… nastÄ™pujÄ…ce funkcje: " init_completion(struct completion *) - inicjalizuje zmiennÄ… sygnaÅ‚owÄ… utworzonÄ… dynamicznie, " wait_for_completion(struct completion *) - oczekuje na sygnaÅ‚ ze zmiennej sygnaÅ‚owej, " complete(struct completion *) - pobudza wÄ…tki oczekujÄ…ce na sygnaÅ‚. 5. Blokada BKL (ang. Big Kernel Lock) Blokada ta byÅ‚a pomocna w przejÅ›ciu z jÄ…dra 2.0 do 2.2. W jÄ…drze 2.0 implementacja SMP pozwalaÅ‚a na przebywanie w trybie jÄ…dra tylko jednemu procesorowi, w 2.2 możliwe byÅ‚o współbieżne wykonywanie kodu jÄ…dra na wielu procesorach jednoczeÅ›nie. BKL miaÅ‚a być rozwiÄ…zaniem przejÅ›ciowym, które miaÅ‚o być zastÄ…pione blokadami o mniejszej ziarnistoÅ›ci. Niestety, również obecnie wiele mechanizmów jÄ…dra korzysta z tego rozwiÄ…zania. Blokada ta, mimo, że wprowadza wÄ…tek w aktywne oczekiwanie, to umożliwia także jego zawieszenie. Na czas usuniÄ™cia tego wÄ…tku z kolejki zadaÅ„ gotowych ta blokada jest odblokowywana i przywracana kiedy ten wÄ…tek wróci do wspomnianej kolejki. Ponadto BKL jest rekurencyjna, wyÅ‚Ä…cza wywÅ‚aszczanie jÄ…dra i można wykorzystywać jÄ… jedynie w kontekÅ›cie procesu. W chwili obecnej nie zaleca siÄ™ jej stosowania, ale w jÄ…drze nadal sÄ… obecne funkcje jÄ… obsÅ‚ugujÄ…ce: " lock_kernel() - zakÅ‚ada blokadÄ™ BKL, " unlock_kernel() - zdejmuje blokadÄ™ BKL, " kernel_locked() - sprawdza, czy blokada BKL jest już zaÅ‚ożona. 6. Blokady sekwencyjne (ang. seq locks) Blokady sekwencyjne (zmienne typu seqlock_t) korzystajÄ… z licznika sekwencyjnego, który jest inkrementowany kiedy blokada jest zakÅ‚adana i zdejmowana, a dzieje siÄ™ to wówczas, gdy chroniony zasób jest zapisywany. Blokady sekwencyjne w prosty sposób pozwalajÄ… okreÅ›lić czy operacja odczytu nie zostaÅ‚a przepleciona z operacjÄ… zapisu. Przed odczytem jest zapamiÄ™tywana wartość licznika. Po wykonaniu tej operacji odczytywany jest ponownie licznik i jego wartość porównywana jest z wartoÅ›ciÄ… poprzedniÄ…. JeÅ›li sÄ… takie same to można być pewnym, że nie rozpoczęła siÄ™ w trakcie odczytu żadna operacja zapisu. Dodatkowo jeÅ›li te wartoÅ›ci sÄ… równe i parzyste, to wiadomo, że odczyt nie zostaÅ‚ zakłócony przez zapis. Do zakÅ‚adania blokady sekwencyjnej sÅ‚uży funkcja wirte_seqlock(), a do zdejmowania write_sequnlock(). Do odczytu wartoÅ›ci poczÄ…tkowej read_seqbegin(), a do odczytu wartoÅ›ci koÅ„cowej read_seqretry(). Obie te funkcje sÄ… wywoÅ‚ywane w pÄ™tli Ä…doº ... Ä…whileº . Blokady te sÄ… używane do rozwiÄ…zywania problemów typu pisarze i czytelnicy, gdzie faworyzowani sÄ… pisarze. JeÅ›li o dostÄ™p do zasobu ubiega siÄ™ co najmniej dwóch pisarzy, to blokada 3 Systemy Operacyjne Ä… semestr drugi sekwencyjna dziaÅ‚a dla nich jak rygiel pÄ™tlowy. Wartość poczÄ…tkowa tej zmiennej (po jej utworzeniu) wynosi zero. 7. Blokowanie wywÅ‚aszczania W przypadkach, gdy trzeba chronić dane, które sÄ… lokalne dla danego procesora (tj. nie sÄ… widziane przez inne procesory) przed dostÄ™pem współbieżnym można zrezygnować z rygli pÄ™tlowych i zastosować zwykÅ‚e zablokowanie wywÅ‚aszczania. Do tego sÅ‚użą funkcje preempt_disable() i preempt_enable(), które można wielokrotnie zagnieżdżać. Wartość licznika wywÅ‚aszczeÅ„, który jest inkrementowany za każdym wywoÅ‚aniem pierwszej z tych funkcji i dekrementowany z każdym wywoÅ‚aniem drugiej, jest zwracana przez preempt_count(). Licznik ten jest polem o nazwie preempt_count umieszczonym w strukturze thread_info, a wiÄ™c wÅ‚aÅ›ciwym każdemu z wÄ…tków wykonania z osobna. Jest także dostÄ™pna funkcja preempt_enable_no_resched(), która wÅ‚Ä…cza wywÅ‚aszczanie jÄ…dra, ale nie powoduje przeszeregowania zadaÅ„. Zablokowania wywÅ‚aszczania jÄ…dra w systemach wieloprocesorowych można też dokonać przy pomocy get_cpu() i put_cpu(). Pierwsza z tych funkcji zwraca numer procesora, na którym jest wywoÅ‚ywana. 8. Blokowanie dolnych połówek Aby zablokować i odblokować dolne połówki zrealizowane w postaci przerwaÅ„ programowych i taskletów należy użyć odpowiednio: funkcji lock_bh_disable() i lock_bh_enable(). 9. Bariery Bariery sÄ… konstrukcjami, które powstrzymujÄ… kompilator i procesor przed zmianÄ… kolejnoÅ›ci operacji odczytu i zapisu. Wykaz barier pamiÄ™ciowych i kompilatora jest nastÄ™pujÄ…cy: " rmb() - zapobiega zmianie kolejnoÅ›ci odczytów wokół niej, " read_barrier_depends() - zapobiega zmianie kolejnoÅ›ci odczytów zależnych wokół niej, " wmb() - zapobiega zmianie kolejnoÅ›ci zapisów wokół niej, " mb() - zapobiega zmianie porzÄ…dku odczytów i zapisów wokół niej, " smp_rmb() - w systemach SMP dziaÅ‚a jak rmb(), w jednoprocesorowych jak barrier(), " smp_read_barrier_depends() - w systemach SMP dziaÅ‚a jak read_barrier_depends(), w jednoprocesorowych dziaÅ‚a jak barrier(), " smp_wmb() - w systemach SMP dziaÅ‚a jak wmb(), w jednoprocesorowych jak barrier(), " smp_mb() - w systemach SMP dziaÅ‚a jak mb(), w jednoprocesorowych jak barrier(), " barrier() - zapobiega optymalizacji kodu wokół niej na etapie kompilacji. 10. Mechanizm RCU Mechanizm RCU (skrót od Read-Copy-Update) jest efektywnym, zapewniajÄ…cym skalowalność Å›rodkiem synchronizacji, który pozwala rozwiÄ…zać problemy synchronizacji typu pisarze i czytelnicy. Wymaga on pewnego, najczęściej niewielkiego narzutu pamiÄ™ci i wymusza speÅ‚nienie trzech warunków, aby poprawnie funkcjonowaÅ‚: " kod korzystajÄ…cy z zasobu chronionego tym mechanizmem nie może ulec zawieszeniu, " zapisy chronionego zasobu powinny być sporadyczne, a odczyty czÄ™ste, " chroniony zasób musi być dostÄ™pny dla wÄ…tków za pomocÄ… wskaznika. Zasada dziaÅ‚ania tego mechanizmu jest stosunkowo prosta. JeÅ›li wÄ…tek-czytelnik chce odczytać zasób, to musi wczeÅ›niej pozyskać do niego wskaznik i przeprowadza operacjÄ™ czytania za pomocÄ… tego wskaznika. WÄ…tek-pisarz jeÅ›li chce zapisać zasób, to najpierw tworzy jego kopiÄ™, modyfikuje jÄ…, a nastÄ™pnie upublicznia wskaznik na tÄ™ kopiÄ™. Po tej ostatniej operacji, jeÅ›li któryÅ› z czytelników spróbuje pozyskać wskaznik do zasobu, to otrzyma w nim adres nowej kopii. OryginaÅ‚ jest niszczony dopiero po zakoÅ„czeniu operacji przez wszystkich czytelników, którzy otrzymali do niego wskaznik przed upublicznieniem przez pisarza nowej wersji tego zasobu. Pisarz posÅ‚uguje siÄ™ funkcjÄ… rcu_assign_ptr(), aby opublikować wskaznik do nowej, zmodyfikowanej kopii zasobu. Czytelnik korzysta z trzech funkcji: rcu_read_lock(), rcu_dereference() - aby uzyskać adres zasobu, rcu_read_unlock() - aby zwolnić wskaznik. Wskaznikiem uzyskanym przez rcu_dereference() może posÅ‚ugiwać siÄ™ w kodzie umieszczonym miÄ™dzy wywoÅ‚aniami rcu_read_lock() i rcu_read_unlock(). WÅ‚aÅ›ciciel zasobu, który zostaÅ‚ zmodyfikowany może go usunąć po powrocie z funkcji synchronize_rcu(). Może również wykorzystać funkcjÄ™ call_rcu(), aby zarejestrować funkcjÄ™, która zostanie wykonana jeÅ›li wszyscy czytelnicy korzystajÄ…cy z zasobu zakoÅ„czÄ… na nim operacjÄ™. Należy zaznaczyć, że ten mechanizm nie zapewnia ochrony przed współbieżnym zapisem. 4