SOE Systemy Operacyjne Wykład 12 Synchronizacja procesów i wątków dr inŝ. Andrzej Wielgus Instytut Mikroelektroniki i Optoelektroniki WEiTI PW
Problem sekcji krytycznej Podstawowy problem synchronizacji współpracujących procesów i wątków Sekcja krytyczna - fragment kodu procesu wymagający wyłącznego dostępu do określonych zasobów np. plików lub wspólnych struktur danych Problem zapewnienia wzajemnego wykluczania (wzajemnego wyłączania) procesów w dostępie do niepodzielnego zasobu
Ogólna struktura kodu programu początek kodu... sekcja wejściowa - oczekiwanie na pozwolenie wejścia sekcja krytyczna sekcja wyjściowa - sygnalizacja wyjścia reszta kodu...
Rozwiązania problemu sekcji krytycznej Warunki wzajemne wykluczanie tylko jeden proces moŝe działać w swojej sekcji krytycznej postęp jeŝeli Ŝaden proces nie działa w swojej sekcji krytycznej oraz istnieją procesy oczekujące na wejście do swoich sekcji krytycznych, to wybór procesu następuje w skończonym czasie ograniczone czekanie dla kaŝdego procesu czas oczekiwania na pozwolenie wejścia do ich sekcji krytycznych jest ograniczony Rozwiązania programowe Wsparcie sprzętowe
Dla 2 procesów Rozwiązania programowe algorytm Petersona Dla wielu procesów algorytm piekarni (Silberschatz, Galvin, Gagne: Podstawy systemów operacyjnych)
Algorytm Petersona boolean flaga[2]; int numer; flaga[0]=false; flaga[1]=false; while (true) { flaga[i] = true; numer = j; while (flaga[j] && numer==j); sekcja krytyczna flaga[i] = false; reszta kodu }
Wsparcie sprzętowe Wyłączenie przerwań systemy jednoprocesorowe Specjalne rozkazy sprzętowe niepodzielnie wykonują dwie operacje testuj_i_ustaw sprawdzenie i zmiana zawartości słowa zamien zamiana zawartości dwóch słów
Rozwiązanie oparte na wyłączaniu przerwań while (true) { wyłącz przerwania sekcja krytyczna włącz przerwania reszta kodu }
Rozwiązanie oparte na testuj_i_ustaw boolean lock=false; /* zmienna dzielona */ while (true) { while (testuj_i_ustaw(&lock)); sekcja krytyczna lock = false; reszta kodu }
Rozwiązanie oparte na zamien boolean lock=false; /* zmienna dzielona */ boolean key; /* zmienna lokalna procesu */ while (true) { key = true; while (key = true) zamien(&lock, &key); sekcja krytyczna lock = false; reszta kodu }
Klasyczna definicja semafora Semafor to liczba całkowita S której moŝna nadać wartość początkową dostępna jest tylko za pomocą dwóch niepodzielnych operacji wait (czekaj) proces oczekuje na zwolnienie zasobu chronionego semaforem, a gdy to nastąpi, zajmuje ten zasób (zajmuje semafor) signal (sygnalizuj) proces zwalnia zasób chroniony semaforem (zwalnia semafor)
Klasyczne operacje na semaforze Operacja wait Operacja signal wait(s) { while (S<=0); S--; } signal(s) { } S++;
Realizacje semaforów Semafory binarne (mutex) przyjmują tylko dwie wartości: 0 i 1, Semafory liczące (wielowartościowe) przyjmują dowolną wartość całkowitą w jednej operacji moŝna zmienić wartość o 1 (lub o dowolną wartość) Semafory realizowane w postaci plików z operacjami: wait signal - otwórz na wyłączność plik o ustalonej nazwie - zamknij plik
Realizacje semaforów KaŜda realizacja musi zapewniać niepodzielność operacji na semaforze, oczekiwanie procesu na osiągnięcie poŝądanej wartości semafora
Niepodzielność operacji Zabronienie obsługi przerwań na czas operacji na semaforze Wykorzystanie sprzętowych rozkazów synchronizacji, które umoŝliwiają wykonanie sprawdzenia wartości i jej zmianę w jednym rozkazie procesora Wykorzystanie programowego algorytmu synchronizacji dostępu do sekcji krytycznej w tym przypadku sekcją krytyczną jest semafor
Oczekiwanie procesu na zmianę semafora Aktywne czekanie procesu wirująca blokada (spinlock) proces w pętli sprawdza stan semafora, czyli wiruje w niewielkim fragmencie programu zuŝywa czas procesora uŝyteczna w systemach wieloprocesorowych, gdy przewidywany czas oczekiwania jest krótki, poniewaŝ nie wymaga przełączania kontekstu procesu Usypianie i budzenie procesu uŝyteczne przy długim czasie oczekiwania na zmianę wartości semafora
Nowe definicje operacji z usypianiem wait(s) { value--; if (value < 0) { dodaj ten proces do listy block(); } } signal(s) { value++; if (value <= 0) { usuń proces P z listy wakeup(p); } }
Synchronizacja przy pomocy semaforów Kolejność wykonania instrukcji w procesach współbieŝnych P1 instrukcja1; signal(s); P2 wait(s); instrukcja2;
Rozwiązanie problemu sekcji krytycznej while (true) { wait(s); sekcja krytyczna signal(s); reszta kodu }
Niebezpieczeństwa Głodzenie procesu - nieskończone blokowanie Zakleszczenie P1 wait(s1); wait(s2); sekcja krytyczna signal(s1); signal(s2); P2 wait(s2); wait(s1); sekcja krytyczna signal(s2); signal(s1);
Klasyczne problemy synchronizacji Problem ograniczonego buforowania Problem czytelników i pisarzy pierwszy drugi Problem posilających się filozofów Problemy testowe
Problem ograniczonego buforowania Wielu producentów Wielu konsumentów Bufor o ograniczonej długości
Rozwiązanie z uŝyciem semaforów Bufor o długości N Semafory mutex = 1 miejsce = N, element = 0 - binarny - wielowartościowe Producent while (true) { wytwórz element wait(miejsce); wait(mutex); umieść element w buforze signal(mutex); signal(element); Konsument while (true) { wait(element); wait(mutex); pobierz element z bufora signal(mutex); signal(miejsce); zuŝyj element }
Problem czytelników i pisarzy Kilku czytelników i kilku pisarzy korzysta ze wspólnego obiektu kaŝdy pisarz musi uzyskać wyłączny dostęp czytelnicy mogą czytać jednocześnie Pierwszy problem czytelnik czeka na dostęp do obiektu tylko wtedy, gdy pisarz uzyskał dostęp do obiektu Drugi jeśli pisarz czeka na dostęp do obiektu, to Ŝaden czytelnik nie moŝe rozpocząć czytania
Rozwiązanie pierwszego problemu czytelników i pisarzy (1) Semafory: mutex = 1 writer = 1 Zmienna współdzielona int readcount = 0
Pisarz Rozwiązanie pierwszego problemu czytelników i pisarzy (2) Czytelnik while(true) { wait(writer); } pisanie signal(writer); while(true) { wait(mutex); readcount++; if (readcount== 1) wait(writer); signal(mutex); czytanie } wait(mutex); readcount--; if (readcount== 0) signal(writer); signal(mutex) ;
Rozwiązanie drugiego problemu czytelników i pisarzy (1) Semafory: mutex_w = 1; mutex_r = 1; mutex_q = 1; writer = 1; reader = 1; Zmienne współdzielone int readcount = 0; int writecount = 0;
Rozwiązanie drugiego problemu czytelników i pisarzy (2) Pisarz while(true) { wait(mutex_w); writecount++; if (writecount== 1) wait(reader); signal(mutex_w); } wait(writer); pisanie signal(writer); wait(mutex_w); writecount--; if (writecount== 0) signal(reader); signal(mutex_w) ; Czytelnik while(true) { } wait(reader); wait(mutex_r); readcount++; if (readcount== 1) wait(writer); signal(mutex_r); signal(reader); czytanie wait(mutex_r); readcount--; if (readcount== 0) signal(writer); signal(mutex_r) ;
Rozwiązanie drugiego problemu czytelników i pisarzy (3) Pisarz while(true) { wait(mutex_w); writecount++; if (writecount== 1) wait(reader); signal(mutex_w); } wait(writer); pisanie signal(writer); wait(mutex_w); writecount--; if (writecount== 0) signal(reader); signal(mutex_w) ; Czytelnik while(true) { wait(mutex_q); wait(reader); wait(mutex_r); readcount++; if (readcount== 1) wait(writer); signal(mutex_r); signal(reader); signal(mutex_q); } czytanie wait(mutex_r); readcount--; if (readcount== 0) signal(writer); signal(mutex_r) ;
Problem posilających się filozofów ryŝ
Przykładowe rozwiązanie problemu Semafory paleczki[5] - zainicjowane wartością 1 Filozof while(true) { wait(paleczki[i]); wait(paleczki[(i + 1)%5]); jedzenie signal(paleczki[i]); signal(paleczki[(i + 1)%5]); myślenie }
Synchronizacja w systemie UNIX Synchronizacja w tradycyjnym jądrze systemu UNIX dla systemów jednoprocesorowych niewywłaszczalne jądro wątek wykonywany w trybie jądra nie moŝe być wywłaszczony wyłączanie przerwań usypianie i budzenie
Synchronizacja w systemach wieloprocesorowych Określenie problemu procesory mogą jednocześnie wykonywać kod jądra (wiele wątków ma jednoczesny dostęp do danych jądra) jeden wątek nie moŝe wyłączyć przerwań na wszystkich procesorach Typowe mechanizmy blokady wyłączności dostępu (mutexy) zmienne warunkowe blokady typu czytelnicy - pisarze semafory
Blokady wyłączności dostępu Mutex (mutual exclusion lock) Blokada wirująca (prosta) wątek wykonuje aktywne czekanie wykorzystuje sprzętowe rozkazy synchronizacji Blokada wstrzymująca wątek jest wstrzymywany (usypiany) Blokada adaptacyjna wątek czeka aktywnie, gdy wątek blokujący jest aktywny wątek jest wstrzymywany, gdy wątek blokujący jest wstrzymany
Zmienne warunkowe Wykorzystywane są do synchronizacji zdarzeń, które współdzielą dostęp do mutexu, ale nie koniecznie do danych UmoŜliwiają wątkom oczekiwanie na spełnienie określonych warunków pod ochroną mutexu wątek zajmuje mutex i sprawdza warunek jeśli warunek nie jest spełniony, to wątek zwalnia czasowo mutex i blokuje się na zmiennej warunkowej inny wątek moŝe zająć mutex, zmienić warunek i zasygnalizować jego zmianę oraz zwolnić mutex czekający wątek jest budzony, zajmuje mutex, sprawdza ponownie warunek i zwalnia mutex
Blokady typu czytelnicy-pisarze ZłoŜony mechanizm synchronizacji wielu wątków czytelnicy mogą jednocześnie czytać dane współdzielone pisarz musi mieć dane na wyłączność Tryby dostępu do zasobu współdzielony (czytelnicy) wyłączny (pisarz)
Blokady typu czytelnicy-pisarze Operacje blokowanie wyłączne blokowanie współdzielone zamiana typu blokady zwalnianie blokady wyłącznej czy budzić czytelnika lub innego pisarza przykładowe rozwiązanie: budzić wszystkich czytelników, a jeśli Ŝaden nie czeka, to jednego z czekających pisarzy zwalnianie blokady współdzielonej ostatni czytelnik budzi pisarza
Zakleszczenia procesów i wątków Problem zakleszczenia Warunki konieczne Rozwiązania w systemach scentralizowanych
Problem zakleszczenia Zbiór zakleszczonych procesów kaŝdy proces przetrzymuje zasób kaŝdy proces oczekuje na przydział zasobu przetrzymywanego przez inny proces ze zbioru
Przykład: dwa procesy i dwa semafory P1 wait(s1); wait(s2); sekcja krytyczna signal(s1); signal(s2); P2 wait(s2); wait(s1); sekcja krytyczna signal(s2); signal(s1);
Przykład: samochody na skrzyŝowaniu
Przykład: problem filozofów Semafory paleczki[5] - zainicjowane wartością 1 while(true) { wait(paleczki[i]); wait(paleczki[(i + 1)%5]); jedzenie signal(paleczki[i]); signal(paleczki[(i + 1)%5]); myślenie }
Przykład: problem filozofów c.d. ryŝ
Warunki konieczne wystąpienia zakleszczenia 1) Wzajemne wyłączanie procesów zasoby niepodzielne 2) Przetrzymywanie i oczekiwanie 3) Brak wywłaszczeń 4) Czekanie cykliczne istnieje cykliczna kolejka procesów czekających na zasoby zajmowane przez procesy z tego zbioru P 1 R 1 P 2 R 2 P 3... P n R n P 1 P i proces R i zasób
Graf alokacji zasobów Krawędź zamówienia P 1 R 1 Krawędź przydziału R 1 P 2
Cykl w grafie alokacji zasobów z zakleszczeniem bez zakleszczenia
Rozwiązania problemu zakleszczeń Niedopuszczanie do powstania zakleszczenia zapobieganie zakleszczeniom unikanie zakleszczeń Wykrywanie i wychodzenie z zakleszczenia Ignorowanie problemu reboot
Zapobieganie zakleszczeniom Zapewnienie, Ŝe co najmniej jeden z warunków zakleszczenia nie będzie spełniony Wzajemne wyłączanie konieczne dla zasobów niepodzielnych Przetrzymywanie i oczekiwanie przed rozpoczęciem kaŝdy proces musi otrzymać wszystkie potrzebne zasoby wady: słabe wykorzystanie zasobów, moŝliwość głodzenia
Zapobieganie zakleszczeniom Brak wywłaszczeń jeśli proces musi czekać na jakiś zasób, to zwalnia dotychczas przydzielone zasoby zwolnione zasoby są dodawane do listy zasobów, na które proces czeka proces jest wznawiany tylko wtedy, gdy moŝe otrzymać wszystkie potrzebne zasoby Czekanie cykliczne zasoby uporządkowane są liniowo proces moŝe zamawiać zasoby tylko w określonej kolejności
Unikanie zakleszczeń Konieczna informacja a priori o planowanym wykorzystaniu zasobów przez wszystkie procesy co najmniej maksymalna liczba zasobów kaŝdego typu Stany systemu bezpieczny istnieje co najmniej jeden ciąg bezpieczny procesów, który nie prowadzi do zakleszczenia ciąg procesów <P 1, P 2,, P n > jest bezpieczny, jeśli dla kaŝdego procesu P i jego zapotrzebowanie na zasoby moŝe być zaspokojone przez aktualnie dostępne zasoby oraz zasoby wszystkich procesów P j przy j<i zagroŝony zakleszczenie
Algorytmy unikania zakleszczeń Algorytm unikania zakleszczenia utrzymuje system w stanie bezpiecznym nie dopuszcza do wystąpienia czekania cyklicznego Algorytm grafu przydziału zasobów system z pojedynczymi egzemplarzami zasobów Algorytm bankiera system z wieloma egzemplarzami zasobów
Algorytm grafu przydziału zasobów Krawędź deklaracji P 1 R 1 Krawędź zamówienia P 1 R 1 Krawędź przydziału R 1 P 2 Stan zagroŝenia
Wykrywanie zakleszczenia Dopuszczenie do zakleszczenia Wykrycie zakleszczenia Algorytm wykrywania moŝe być wywoływany przy zamówieniach na zasoby, które nie mogą być od razu zrealizowane gdy efektywność wykorzystania procesora zmniejsza się
Wychodzenie z zakleszczenia Zakończenie procesów wszystkich zakleszczonych pojedynczo aŝ do usunięcia zakleszczenia Wywłaszczanie zasobów wybór procesu-ofiary w celu odebrania zasobów wycofanie procesu do jakiegoś bezpiecznego stanu (przed przydzieleniem zasobów) i wznowienie go z tego stanu Niebezpieczeństwo głodzenia procesu, który jest wciąŝ wybierany jako ofiara