Systemy Operacyjne semestr drugi
Wykład piętnasty
Obsługa sygnałów w Linuksie
Sygnały są krótkimi komunikatami, które są wysyłane do procesów użytkownika. Nadawcą komunikatu może być jądro systemu lub inny proces użytkownika. Sygnały
identyfikowane są za pomocą unikatowego numeru i nie zawierają dodatkowych informacji. Numer sygnałów wyrażone są za pomocą stałych (makrodefinicji), których
nazwy rozpoczynają się przedrostkiem SIG. Sygnały służą do powiadomienia procesu, że zaszło jakieś zdarzenie i/lub że musi on wykonać funkcję, która zajmie się jego
obsługą. Taka funkcja nazywa się procedurą obsługi sygnału (ang. signal handler). Linux na architekturach PC obsługuje 31 podstawowych sygnałów. Jeśli proces nie
posiada własnej procedury obsługi danego sygnału, to zostanie wykonana obsługa domyślna, która sprowadza się do jednej z pięciu czynności:
1. Przerwanie wykonania procesu (ang. terminate)
2. Przerwanie wykonania i zapisanie pamięci procesu do pliku core (ang. dump)
3. Zignorowanie sygnału (ang. ignore)
4. Wprowadzenie procesu w stan TASK_STOPPED (ang. stop)
5. Wznowienie wykonania procesu jeśli proces był w stanie TASK_STOPPED to wprowadzany jest w stan TASK_RUNNING (ang. continue)
Oprócz podstawowych sygnałów Linux obsługuje sygnały czasu rzeczywistego, które są częścią standardu POSIX. W Linuksie mają one przypisane numery od 32 do 64.
Sygnały te różnią się od podstawowych sposobem dostarczenia sygnały podstawowe nie są kolejkowane, co oznacza, że nawet jeśli sygnał został wielokrotnie wysłany,
to proces odbierający otrzyma tylko jedno powiadomienie, sygnały czasu rzeczywistego są kolejkowane.
Sygnał są wysyłane asynchronicznie, co oznacza że proces odbiorca może znajdować się w dowolnym stanie w chwili wysłania sygnału. Fakt nadania sygnału musi
więc być odnotowany przez jądro systemu, aby nie został on zagubiony. Transmisję sygnału można więc podzielić na dwa etapy: wysłanie sygnału i dostarczenie
sygnału. W pierwszym etapie jądro aktualizuje odpowiednie struktury danych procesu odbiorczego celem powiadomienia go, że otrzymał nowy sygnał. W drugim etapie
jądro wymusza na procesie obsługę sygnału poprzez zmianę jego stanu lub rozpoczęcie wykonania procedury obsługi sygnału lub poprzez wykonanie obu tych czynności.
Sygnały, które zostały wysłane, ale jeszcze nie dostarczone nazywane są sygnałami oczekującymi (ang. pending signals). Czas oczekiwania na obsługę przez sygnał nie
jest w żaden sposób determinowany. Większość sygnałów może być przez proces blokowana i ich obsługa odkładana jest do momentu, kiedy proces zdejmie blokadę.
Jeśli proces jest w trakcie wykonywania procedury obsługi sygnału to do momentu jej zakończenia ponowne dostarczenie sygnału związanego z tą procedura będzie
blokowane. Pomimo prostoty mechanizmu sygnałów jego implementacja w jądrze systemu jest dosyć skomplikowana. Jądro musi pamiętać które sygnały są blokowane
przez poszczególne procesy, sprawdzać czy sygnały zostały wysłane do procesu podczas zmiany kontekstu, określić czy sygnał może być zignorowany (co oznacza, że nie
tylko proces musi go ignorować, ale również nie może on być blokowany i odbiorca nie może być śledzony przez inny proces) oraz obsłużyć sygnał, co wiąże się
z koniecznością zapamiętania części bieżącego kontekstu procesu, uruchomieniem procedury obsługi sygnału i przywróceniu kontekstu procesu. Dodatkowo Linux musi
uwzględniać semantykę obsługi sygnałów, jaka obowiązuje w systemach BSD, System V i w standardzie POSIX. Ten ostatni między innymi specyfikuje sposób
dostarczania sygnałów do procesów wielowątkowych, który można scharakteryzować w następujących punktach:
1. Procedury obsługi sygnałów współdzielone są przez wszystkie wątki w obrębie jednego procesu, ale każdy wątek ma swoje pola bitowe określające sygnały
oczekujące i blokowane.
2. Funkcje kill() i sigqueue() muszą wysyłać sygnały do procesu, a nie do poszczególnych jego wątków. To samo odnosi się do sygnałów wysyłanych przez
jądro.
3. Jeśli sygnał śmiertelny (ang. fatal), czyli taki który powoduje przerwanie wykonania zostanie otrzymany przez proces, to przerywane jest działanie
wszystkich jego wątków, a nie tylko tego, który jest aktywny w chwili dostarczenia sygnału.
4. W przypadku innych sygnałów są one dostarczane tylko do jednego wątku wybranego arbitralnie przez jądro, spośród tych, które nie blokują wysłanego
sygnału.
Aby sprawnie obsługiwać sygnały jądro systemu z każdym procesem kojarzy klika struktur danych, które mu to zadanie umożliwiają. Te struktury powiązane są
z deskryptorem procesu lub są jego częścią, jak np. pole blocked które jest maską bitową opisującą sygnały które zostały zablokowane. Do najważniejszych
zewnętrznych struktur danych związanych z sygnałami zaliczają się deskryptory sygnałów i deskryptory procedur obsługi sygnałów. Deskryptor sygnałów zdefiniowany
jest strukturą struct signal_struct i wskazywany jest przez pole signal deskryptora procesu. Głównym zadaniem deskryptora sygnałów jest utrzymywanie informacji
o sygnałach oczekujących, które dotyczą wszystkich wątków procesu, dlatego też jest współdzielony przez te wątki. Zarówno deskryptor procesu, jak i deskryptor
sygnałów zawierają pola typu struct sigpending, które są wskaznikami na listy sygnałów oczekujących. Lista wskazywana przez deskryptor procesu zawiera informacje
o sygnałach oczekujących wysłanych do pojedynczego wątku (który w Linuksie jest rodzajem procesu), natomiast lista wskazywana przez deskryptor sygnałów zawiera
informacje o sygnałach dotyczących wszystkich wątków procesu. Deskryptor procedur obsługi sygnałów zdefiniowany jest za pomocą struktury struct sighand_struct
i zawiera informacje dotyczące obsługi sygnałów przez wszystkie wątki wchodzące w skład procesu. Dodatkowo ten deskryptor może być współdzielony nie tylko przez
wątki, ale również przez zwykłe procesy spokrewnione. Najważniejszym polem deskryptora procedur obsługi sygnałów jest pole action typu struct sigaction [64]. Jest to
zatem tablica struktur związanych z każdym z sygnałów jakie mogą zostać wysłane do procesu. Struktura sigaction posiada pole sa_handler, które zawiera adres
procedury obsługi sygnału zdefiniowanej przez twórcę aplikacji użytkowej lub adres domyślnej procedury obsługi sygnału. Pole sa_flags określa jakie dodatkowe
czynności muszą być wykonane podczas obsługi sygnału (użycie osobnego stosu, wznowienie wykonania przerwanego wywołania systemowego, itd.). Pole sa_mask
określa, które sygnały muszą zostać zablokowane podczas wykonania określonej procedury obsługi sygnału.
Wysłanie sygnału na poziomie jądra odbywa się poprzez wywołanie jednej z poniższych funkcji:
send_sig() - wysyła sygnał do pojedynczego procesu,
send_sig_info() - działa tak samo jak send_sig(), ale przesyła również dodatkowe informacje o sygnale, które są jej przekazywane przez strukturę siginfo_t,
force_sig() - wysyła sygnał, który nie może zostać wprost zablokowany lub zignorowany przez proces,
force_sig_info() - stanowi połączenie dwóch powyższych funkcji,
force_sig_specific() - działa jak force_sig(), ale jest zoptymalizowana dla sygnałów SIGSTOP i SIGKILL,
sys_tkill() - jest funkcją implementująca wywołanie tkill(), które wysyła sygnał do wątku,
sys_tgkill() - jest funkcja implementująca wywołanie tgkill(), które wysyła sygnał do grupy wątków.
Jądro systemu Linux udostępnia procesom użytkownika szereg wywołań systemowych, które umożliwiają korzystanie z sygnałów. Wywołanie kill(), które
implementowane jest przez funkcję sys_kill() wysyła sygnał do procesu. Oprócz numeru sygnału jako argument pobiera również PID procesu wysyłającego, którego
interpretacja zależy od jego wartości. Jeśli jest ona większa od zera to sygnał jest wysyłany do procesu (i wszystkich jego wątków) o takim PID, jeśli jest równa zero, to
sygnał wysyłany jest do wszystkich procesów (i ich wątków), które należą do tej samej grupy co nadawca, jeśli równa -1 to sygnał wysyłany jest do wszystkich procesów
oprócz bieżącego (wskazywanego przez makrodefinicję current) i procesów o PID równym O i 1. Jeśli wartość parametru PID jest mniejsza od -1, to sygnał wysyłany jest
do wszystkich procesów (i wątków) należących do tej samej grupy co proces o podanym PID. Wywołanie sigaction() pozwala zmienić procedurę obsługi określonego
sygnału. Pobiera trzy parametry numer sygnału, wskaznik na strukturę opisującą nową procedurę obsługi sygnału i wskaznika na strukturę w której zostaną
1
Systemy Operacyjne semestr drugi
zapamiętane informacje o starej procedurze obsługi sygnału. To wywołanie implementowane jest przez funkcję sys_sigaction(). Wywołanie sigpending() zwraca
informacje o sygnałach, które zostały wysłane w momencie, kiedy proces blokował ich przyjęcie. Sygnały blokowane można określić za pomocą wywołania sigprocmask(),
natomiast wywołanie sigsuspend() pozwala wprowadzić proces w stan TASK_INTERRUPTIBLE, z którego zostanie wybudzony po otrzymaniu sygnału, który nie będzie
przez niego ani zablokowany ani zignorowany. Istnieją również wywołania systemowe dla sygnałów czasu rzeczywistego, ale nie będą tutaj opisywane.
Dostarczenie sygnału obejmuje szereg czynności, które wykonuje jądro systemu celem wymuszenia na procesie obsługi sygnału. Konieczność ich wykonania jest
sygnalizowana za pomocą flagi TIF_SIGPENDING, której wartość sprawdzana jest przez jądro zanim przełączy ono procesor w tryb użytkownika. Dostarczeniem
sygnałów zajmuje się funkcja do_signal(). Jeśli z sygnałem, który został wysłany związana jest domyślna obsługa, to ta funkcja ją uruchamia. Obsługa sygnału dla
którego proces zdefiniował własną procedurę obsługi jest bardziej skomplikowana, gdyż ta procedura musi być wykonana w trybie użytkownika i dodatkowo może
korzystać z wywołań systemowych. Powoduje to konieczność przełączania się między stosami trybu jądra i trybu użytkownika. Linuks, aby przyspieszyć obsługę takich
sygnałów stosuje metodę kopiowania kontekstu sprzętowego między obydwoma stosami.
Materiały do tego wykładu zostały przygotowane w oparciu o książkę Daniela P. Boveta i Marco Cessatiego pt. Understanding the Linux Kernel , wydanie trzecie.
2
Wyszukiwarka
Podobne podstrony:
SO2 wyklad 9SO2 wyklad Warstwa operacji blokowychSO2 wyklad 1SO2 wyklad Przestrzeń adresowa procesówSO2 wykladSO2 wyklad 4 Wywołania systemoweSO2 wyklad 8SO2 wyklad Obsługa sieciSO2 wykladSO2 wyklad 7SO2 wyklad 3SO2 wykladSO2 wyklad 5SO2 wyklad 2SO2 wyklad 6SO2 wyklad 2 Zarządzanie procesamiSO2 wykladSO2 wyklad 4więcej podobnych podstron