Procesy komunikują się z jadrem systemu,
a także między sobą aby koordynować swoją dzialalność. Linux wspiera kilka
mechanizmów komunikacji zwanych IPC (Inter-Process Communication mechanisms).
Jednym z nich są sygnały, zwane inaczej przerwaniami programowymi.
Sygnały mogą być generowane bezpośrednio
przez użytkownika (funkcja kill()), może wysyłać je
jądro oraz procesy między sobą (funkcja systemowa kill())
. Dodatkowo pewne znaki z terminala powodują wygenerowanie sygnałów. Na
przykład na każdym terminalu istnieje tak zwany znak przerwania (ang. interrupt
character) i znak zakończenia (ang. quit character). Znak przerwania
(zazwyczaj Ctrl-C lub Delete) służy do zakończenia bieżącego procesu (wygenerowani
SIGINT). Wygenerowanie znaku zakończenia (zazwyczaj Ctrl-\) powoduje wysłanie
sygnału SIGQUIT powodującego zakończenie wykonywania bieżącego procesu
z zapisaniem obrazu pamięci.
Istnieją oczywiście pewne ograniczenia - proces
może wysyłać je tylko do procesów mających tego samego właściciela oraz
z tej samej grupy (te same uid i gid). Bez ograniczeń moze to czynić jedynie
jądro i administrator.
Jedynym procesem, który nie odbiera
sygnałów jest init (pid równy 1).
Sygnały są mechanizmem asynchronicznym
- proces nie wie z góry kiedy sygnał może nadajść i glownym ich zadaniem
jest informowanie procesu o zaistnieniu w systemie wyjątkowej sytuacji
(np.: spadek napięcia w sieci). Ponadto są wykorzystywane przez shelle
do kontroli pracy swoich procesów potomnych.
Implementacja struktur danych związanych
z sygnałami, ustalenie ich liczby, numerów i nazw znajduje się w pliku
naglówkowym signal.h.
Natomiast informacje o tym, jakie sygnały dany proces otrzymał, które
z nich chce ignorować, a które obsłużyć i w jaki sposób zapamiętane są
w specjalnych polach struktury task_struct. (szczegółów szukaj w opisie
struktur danych procesu).
Ilość dostepnych sygnałów jest
ograniczona przez rozmiar słowa procesora. Stąd komputery 32 bitowe mają
maksymalną ilość 32 sygnałów, podczas gdy maszyny 64 bitowe jak Alpha AXP
mogą miec zaimplementowane do 64 sygnałów. (pl)
(1) SIGHUP - Zerwanie łączności - sygnał zawieszenia (ang. hangup)
Jest wysyłany przy zerwaniu łączności z terminalem (np.: terminal jest
zamykany) do wszystkich procesów, dla których jest on terminalem sterującym.
Generowany również do wszystkich procesów grupie, gdy zakończy działanie
jej pzrywódca (symulowanee zerwania łącznosci z terminalami, których nie
można odlaczyć fizycznie, np.: komputerów osobistych).
Domyślnie powoduje zakończenie procesu.
(2) SIGINT - Znak przerwania (ang. interrupt)
Jest zazwyczaj wysyłany do wszystkich procesów związanych z danym terminalem,
gdy użytkownik naciśnie klawisz przerwania. Można znieść takie działanie
klawisza przerwania, a także można zmienić sam klawisz, wywołujac funkcję
systemową ioctl().
Domyślnie powoduje zakończenie procesu.
(3) SIGQUIT - Znak zakończenia
Generowany zazwyczaj wtedy, gdy użytkownik nacisnie na terminalu klawiszzakończenia
pracy. Jest bardzo podobny do sygnału SIGQUIT, ale dodatkowo powoduje wygenerowanie
obrazu pamięci.
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(4) SIGILL - Niedozwolony rozkaz
Sygnał ten jest wysyłany po wystąpieniu wykrywanej sprzętowo sytuacji
wyjątkowej, spowodowanej przez niewłaściwą implementację systemu.
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(5) SIGTRAP - Pułapka śledzenia
Sygnału tego używa się w funkcji systemowej ptrace(), zezwalającej
na wykonywanie procesu z równoczesnym tworzeniem jego śladu. Z własności
tej korzysta się w programach diagnostycznych, wspomagających testowanie
programów.
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(6) SIGIOT,SIGABRT - Błąd sprzętowy (rozkaz IOT)
Sygnał ten jest generowany po wystąpieniu błędu sprzętowego spowodowanego
niewłaściwą implementacją systemu. Ponadto wersja funkcji abort() w
Systemie V oraz systemie 4.3BSD wysyła ten sygnał do bieżącego procesu
(jest to przypadek wysyłania sygnału przez proces do samego siebie).
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(7) SIGBUS - Błąd szyny
Sygnał ten jest generowany po wystąpieniu błędu
sprzętowego spowodowanego przez niewłaściwą implementację systemu.
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(8) SIGFPE
- Błąd sprzętowy (rozkaz FBE)
Sygnał wysyłany po wystąpieniu wykrywanej sprzętowo sytuacji wyjątkowej,
spowodowanej przez niewłaściwą implementację systemu. Na przykład system
4.3BSD dla maszyny VAX używa tego sygnału do wskazywania sytuacji wyjątkowych
podczas wykonywania operacji zmiennopozycyjnych (np. niedomiar zmiennopozycyjny)
i sytuacji wyjątkowych podczas wykonywania operacji na liczbach całkowitych
(np. dzielenie przez zero).
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(9) SIGKILL - Zakończenie procesu
Jest to jedyny całkowicie pewny sposób zakończenia wykonywania procesu,
ponieważ proces odbierający ten sygnał nie może go ani zignorować, ani
przechwycić (dostarczając własną funkcję obsługi sygnału). Sygnału tego
powinno sie używać tylko w sytuacjach wyjątkowych, w pozostałych natomiast
zalecany jest sygnał SIGTERM.
(10) SIGUSR1 - Sygnał definiowany przez użytkownika
Jeden z dwóch sygnałów definiowanych przez użytkownika (drugim jest
SIGUSR2). Można próbować użycia tego sygnału w programach użytkowych
w celu komunikacji między procesami nie jest to jednak zbyt wygodny mechanizm
(patrz w dalszej części ogólnego opisu).
Domyślnie powoduje zakończenie procesu.
(11) SIGSEGV - Naruszenie segmentacji
Sygnał jest generowany po wystąpieniu błędu sprzętowego spowodowanego
niewlasciwą implementacją systemu. Pojawia się na ogól wtedy, kiedy proces
odwoła się do takiego adresu w pamięci, do którego nie ma dostępu.
Domyślnie powoduje zakończenie procesu z zapisem obrazu pamięci.
(12) SIGUSR2 - Definiowany przez użytkownika
Drugi z sygnałów pozostawiony do zdefiniowania przez użytkownika - analogiczny
do SIGUSR1.
Domyślnie powoduje zakończenie procesu.
(13) SIGPIPE - Dane nie są odbierane z łącza komunikacyjnego
Kiedy proces wysyła dane do łącza komunikacyjnego lub kolejki FIFO,
a nie isnieje proces odczytujący te dane, wtedy do procesu piszącego wysylany
jest właśnie tensygnał. Ponadto jest sygnał jest generowany w systemie
4.3BSD, gdy proces wysyla dane do odłączonego gniazda.
Domyślnie powoduje zakończenie procesu.
(14) SIGALRM - Budzik
Proces może nastawić budzik używając funkcji systemowej alarm() określającej
kiedy proces ma być obudzony (wtedy jadro wyśle mu ten właśnie sygnał).
Z sygnałem tym związana jest także funkcja alarm(), która sama nastawia
budzik i oczekuje na nadejście tego sygnału by go następnie przechwycić.
Domyślnie powoduje zakończenie procesu.
(15) SIGTERM - Programowe zakończenie procesu
Jest to standardowy sygnał zakończenia procesu, generowany przez oprogramowanie,
wysyłany domyślnie do procesu wtedy, kiedy użyje się polecenia kill. Sygnału
tego używa się także podczas wyłączania całego systemu w celu zakończenia
wszystkich aktywnych procesów. Programy powinny przyjmować domyślną obsługę
tego sygnału, bądź też błyskawicznie robić po sobie porządek (np. usunąć
pliki tymczasowe) i wywoływać funkcję exit().
Domyślnie powoduje zakończenie procesu.
(16) SIGSTKFLT
W Linuxie nie zaimplementowany.
(17) SIGCHLD - Zakończenie procesu potomnego
Sygnał ten wysyłany jest do procesu macierzystego
wtedy, gdy zakończy się działanie jego procesu potomnego. W systemach
4.3BSD i Linux sygnał ten wskazuje rownież, iż zmienił się stan procesu
potomnego. Jest to ogólniejsze niż tylko wskazywanie śmierci potomka. Zmiana
stanu może oznaczać jego smierć, ale może także wynikać z zatrzymania tego
procesu przez sygnały SIGSTOP, SIGTTIN, SIGTTOU lub też SIGTSTP.
Domyślnie ignorowany.
(18) SIGCONT - Wznowienie wstrzymanego procesu
Sygnał ten wysyłany jest, kiedy w systemie 4.3BSD
podejmuje się dalszy ciąg działania procesu, który został wstrzymany (patrz
opis sygnałów SIGSTOP i SIGTSTP). Jeśli np. zatrzymano
pracę edytora pełnoekranowego, to przed jej wznowieniem może on odtworzyć
wyświetlany uprzednio obraz.
W Lnuxie domyślnie ignorowany.
(19) SIGSTOP - Zatrzymanie procesu
Sygnał ten zatrzymuje proces. Podobnie jak sygnału SIGKILL, nie można
go zignorowac, ani przechwycić (obslużyc za pomocą wlasnej funkcji). Umożliwia
on administratorowi systemu zatrzymanie procesu.
Domyślnie powoduje zatrzymanie procesu.
(20) SIGTSTP - Wprowadzenie znaku zatrzymania
W systemie 4.3BSD sygnał ten jest wysyłany do procesu po naciśnięciu
klawisza zawieszenia (na ogół Ctrl-Z) lub klawisza zawieszenia z opóźnieniem
(na ogół Ctrl-Y).
Domyślnie powoduje zatrzymanie procesu.
(21) SIGTTIN - Czytanie w tle z terminala sterującego
W systemie 4.3BSD sygnał ten jest wysyłany wtedy, gdy proces drugoplanowy
przystępuje do czytania ze swego terminala sterującego. System generuje
ten sygnał, aby uniknąć zamieszania spowodowanego tym, że więcej niż jeden
proces czyta z tego samego urządzenia.
Domyślnie powoduje zatrzymanie procesu.
(22) SIGTTOU - Pisanie w tle na terminalu sterującym
Sygnał zbliżony do sygnału SIGTTIN, wysyłany wtedy, gdy proces drugoplanowy
przystępuje do pisania na swoim terminalu sterującym. W systemie 4.3BSD
jest to działanie dozwolone, więc jest tam domyślnie ignorowany.
W Linuxie domyślnie powoduje zatrzymanie procesu.
(23) SIGURG - Dane wysokopriorytetowe w gnieździe
W systemie 4.3BSD sygnał ten wysyłany jest po powstaniu sytuacji wyjątkowej
zwiazanej z nadejściem danych wysokopriorytetowych do gniazda albo z obecnością
informacji o stanie sterowania, która trzeba odebrać od cześci nadrzędnej
pseudoterminala pracującego w trybie pakietowym. Sygnał jest wysyłany do
grupy procesów dla tego gniazda.
W Linuxie domyślnie ignorowany.
(24) SIGXCPU - Przekroczenie czasu procesora
W systemie 4.3BSD oraz w Linuxie można w procesie ustalić ograniczenia
zasobów, z których bedzie korzystał dany proces i każdy utworzony przez
niego proces. Sygnał SIGXCPU wskazuje, że proces przekroczył przydzielony
mu czas procesora. Podobna jest idea sygnału SIGXFSZ.
Domyślnie powoduje zakończenie procesu.
(25) SIGXFSZ - Przekroczenie rozmiaru pliku
Oznacza, ze proces przekroczył ograniczenie rozmiaru swoich plików (patrz
opis sygnału SIGXCPU).
Domyślnie powoduje zakończenie procesu.
(26) SIGVTALRM - Budzik zegara wirtualnego
W systemie 4.3BSD i w Linuxie istnieją trzy rodzaje mechanizmów zegarowych
slużących do sterowania budzikami: zegar związany z sygnalem SIGALRM odmierza
czas rzeczywisty dla procesu; zegar wirtualny związany z sygnalem SIGVTALRM
odmierza czas wirtualny procesu (czas wykonywania procesu); wreszcie zegar
profilometra zwiazany z sygnalem SIGPROF odmierza zarówno czas wirtualny
procesu, jak i czas, w którym jądro pracuje na rzecz procesu.
Domyślnie powoduje zakończenie procesu.
(27) SIGPROF - Budzik profilometra
Patrz opis SIGVTALRM.
Sygnału SIGPROF używa się w interpretatorach do profilowania wykonania
interpretowanego programu.
Domyślnie powoduje zakończenie procesu.
(28) SIGWINCH - Zmiana rozmiaru okna
Sygnał oznacza, że zmienił się rozmiar okna na terminalu.
Domyślnie ignorowany.
(29) SIGIO - Wejście-wyjście dozwolone dla deskryptora pliku
W systemie 4.3BSD s ygnał ten wskazuje, że można wykonywać operacje wejścia-wyjścia w odniesieniu do deskryptora pliku. Sygnału tego używa sie do stworzenia pewnej postaci asynchronicznego wejścia-wyjścia dla procesu.
SIGPOLL - Zdarzenie dotyczące urządzenia strumieniowego
Sygnał ten w systemie V umożliwia procesowi wykonywanie asynchronicznych operacji wejścia-wyjścia na jednym lub większej liczbie urządzeń strumieniowych.
W Linuxie o oba te sygnały są utożsamiane i domyślnie ignorowane.
(30) SIGPWR - Niedobór mocy
Charakterystyczny dla Systemu V. Dokładne znaczenie tego sygnału zależy
od konkretnej implementacji. Jedną z możliwości polega na wysyłaniu go
po wykryciu znacznego spadku napięcia w sieci elektrycznej (np. napiecie
spadło o 100V i nadal spada). Proces ma wówczas bardzo mało czasu na kontynuacje
pracy - powinien więc zrobić porządek i zakonczyć się.
Domyślnie powoduje zakończenie procesu.
(31) SIGUNUSED
W Linuxie nie zaimplementowany.
(pl)
Proces może w różny sposób zareagować na przyjście
sygnału. W szczególnosci może ignorować większość z nich z tym, że istnieją
dwa wyjątki. Nigdy nie może być ignorowany sygnał SIGSTOP powodujący zatrzymanie
wykonywanego procesu, jak również SIGKILL powodujący zabicie procesu. Jest
to sposób na pozostawienie administratorowi kontroli nad systemem.
Możliwe jest także blokowanie sygnałów (z tymi samymi wyjatkami co
ignorowanie), które oznacza, że proces nie będzie reagował na sygnał danego
typu, ale informacja o tym, że on nadszedł jest pamiętana i zostanie on
obsłużony w momencie, gdy go odblokujemy.
Informacje o tym jakie sygnały nadeszły, a
których proces jeszcze nie obsłużył przechowywane są w polu signal w
task_struct. Pole to jest jest typu unsigned long i nadejście sygnału
powoduje zapalenie bitu o numerze odpowiedającym numerowi tego sygnału.
W podobny sposób przechowywana jest maska sygnałów blokowanych - pole blocked
typu unsigned long w task_struct.
Taki sposób implementacji ma swoje konsekwencje - w przypadku odebrania
kilku sygnałów proces nie jest w stanie stwierdzić kolejności ich przyjścia.
Dodatkowo nie ma mechanizmu wielokrotnej obsługi sygnałów tego samego typu,
gdyż proces po prostu nie rozróżnia, czy przyszedł na przykład jeden sygnał
SIGCONT, czy może 10 albo 14 takich sygnałów. Także nic nie wiadomo o tożsamości
nadawców.
Stąd sygnały słabo sprawdzają się jako mechanizm komunikacji między
procesami.
Jeśli proces decyduje się nie blokować sygnału może
wybrać sposób jego obsługi. Każdy sygnał prócz SIGSTOP i SIGKILL proces
może obsługiwać samodzielnie dostarczając własną funkcję (zwaną funkcją
obsługi sygnału - ang. signal handler), która będzie wykonywana zawsze
po nadejściu sygnału danego typu. Wykonywanie akcji przez tę dostarczoną
przez proces funkcję nazywa się przechwyceniem sygnału przez proces. Proces
może również pozostawić obsługę sygnału jądru systemu, które wykonuje wtedy
akcję domyślną - najczęściej zakończenie wykonywania procesu z ewentualnym
zapisem zawartości pamięci (ang. core dump). Może to być również zatrzymanie
procesu (SIGSTOP), ale sygnał może być również domyślnie ignorowany
(zob. opis poszczególnych sygnałów). Linux przechowuje informacje jak dany
proces obsługuje sygnały w polu sig w task_struct każdego procesu. Pole
to stanowi dowiązanie do tablicy sigaction, która dla każdego sygnału zawiera
adres funkcji obsługi sygnału, flagę jak sygnał ma być obsłużony (ignorowany
albo obsłużony przez jądro), a także maskę sygnałów blokowanych. Standard
POSIX wymaga, aby proces mógł decydować jakie sygnały mają być blokowane
podczas wywołania funkcji obsługi. To oznacza modyfikację pola blocked
na czas tej akcji, a wszystko powinno wrócić do swej pierwotnej wartości.
Ostatnim polem w task_struct procesu związanym z sygnałami jest exit_code.
Pole to służy do zapamiętania sygnału, który spowodował zatrzymanie procesu
podczas śledzenia tego procesu. Kiedy proces jest śledzony przyjście każdego
sygnału powoduje jego zatrzymanie tak, by proces śledzacy mógł to odnotować.
Kiedy proces śledzony zostanie obudzony (sygnałem SIGCONT) to zostanie
podmieniony aktualnie obsługiwany sygnał. Zamiast SIGCONT zostanie więc
obsłużony wcześniej zapamiętany a nie obsłużony sygnał. (szczegóły w opisie
kodu źródłowego funkcji do_signal).
Proces otrzymując sygnał może znajdować się
w różnych stanach, stąd różne będą jego reakcje.
Jeżeli proces otrzymał nieblokowany sygnał, a oczekuje właśnie na operację
wejścia-wyjścia (stan TASK_INTERRUPTIBLE - szczegółów szukaj w rozdziale
o stanach procesu) to jest budzony przez zmianę stanu na TASK_RUNNING i
umieszczany w kolejce run_queue jako kandydat do wykonania. Kiedy
dostanie procesor wykona akcje związane z obsługą sygnału po czym wywoływana
zawsze na końcu obsługi sygnału funkcja sigreturn()
spowoduje zrestartowanie funkcji systemowej, na którą oczekiwał nasz proces
. W ten sposób wróci on dokładnie do miejsca, w którym jego działanie zostało
przerwane.
Jeśli proces jest wykonywany (stan TASK_RUNNING) to nie może
zareagować natychmiast po otrzymaniu sygnału, ale dopiero gdy proces będzie
wykonywany po raz kolejny. Jest to spowodowane faktem, że funkcja do_signal()
odpowiedzialna za sprawdzanie pól signal i blocked oraz obsłużenie
sygnałów jest wołana tylko w momencie zakończenia dowolnej z funkcji
systemowych.
Taka metoda może wydawać się na pierwszy rzut oka nieco dziwna, ale
przecież każdy z procesów przez cały czas wykonuje funkcje systemowe, np.:
czytanie czy wypisywanie znaków.
Ponieważ sygnały obsługiwane są w pętli, gdzie funkcja
sprawdzająca czy są jeszcze nie obsłużone sygnały (patrz dokładny opis
funkcji do_signal()) jest uruchamiana tylko przy
powrocie z funkcji systemowej pewna trudność pojawia się, gdy proces dostarczy
własną funkcję, którą jądro ma wywołać do obsługi danego sygnału. Jak to
się odbywa jest w dużej mierze zależne od sprzętu, ale każdy procesor musi
poradzić sobie z faktem, że wywołanie funkcji do_signal()
odbywa się w trybie jądra i wywołana bezpośrednio przez nią funkcja
dostarczona przez użytkownika wykonywałaby się w tym samym trybie (a do
tego nie należy dopuścić). Poza tym trzeba narzucić wywołanie funkcji systemowej
by było wołane do_signal() dla pozostałych sygnałów.
Problem jest rozwiązywany poprzez odpowiednią modyfikację rejestrów
i stosu procesu tak by wymusić
wołanie funkcji systemowej sig_return() (patrz
opis funkcji systemowych) po powrocie z wywołania handlera. (pl)
Z wysyłaniem i obsługą sygnałów związane jest kilka funkcji systemowych. Ich implementacja zawarta jest w plikach źródłowych:
asmlinkage int sys_sigprocmask(int how, sigset_t *set, sigset_t *oset)Funkcja ta pozwala wybrać, które sygnały będą blokowane przez wywołujący (bieżący) proces. W zależnosci od parametru how:
UWAGA: nie można zablokować sygnałów SIGSTOP i SIGKILL.
asmlinkage int sys_sigaction(int signum, const struct sigaction * action, struct sigaction * oldaction)Za pomocą sys_sigaction można zmienić lub zbadać obsługę sygnału. Jesli action jest poprawnym wskaźnikiem, obsługa sygnału signum zostanie ustawiona na action. Obsługa sprzed wywołania funkcji zostanie zapamiętana pod oldaction, jeżeli oldaction!=NULL. (Można więc skorzystać z sys_sigaction tylko po to, by wczytać oldaction).
Można więc za pomocą sys_sigaction zmienić:
asmlinkage int sys_kill(int pid,int sig)Za pomocą sys_kill można wysłać sygnał do pojedynczego procesu albo ich grupy. Parametr pid jest rozumiany w sposób następujący:
Wywołanie niekoniecznie kończy się sukcesem. Może się zdarzyć, że procesu nie ma (albo nie ma żadnego procesu w grupie). Wówczas zwracany jest błšd -ESRCH. Innym możliwym błędem jest brak uprawnień procesu do wysłania sygnału. Błąd związany z brakiem uprawnień to -EPERM. Powstaje on gdy wysyłamy sygnał do jednego procesu bez koniecznych uprawnień; albo gdy wysyłamy sygnał do grupy procesów i choć istnieje w niej choć jeden proces, do wysłania do którego sygnału nie mamy uprawnień. (mb)
Służy do ustawienia maski blokowania sygnałów i uśpienia procesu, który teraz czeka na dowolny nieblokowany sygnał, ale taki, który jest przez ten proces przechwytywany. Funkcja sigreturn()
Wołana zawsze oprzy powrocie z funkcji obsługi sygnału żeby odtworzyc oryginalną maskę blokowania sygnałów i dokończyć wywolanie funkcji systemowej.
Oprócz tych funkcji z sygnałami związane są funkcje wewnętrzne jądra:
Funkcja do_signal() (implementacja w pliku ../386i/signal.c)
Zajmuje się obsługą nieblokowanych sygnałów. Sprawdza czy odebrany jakiś sygnał i w zależności od tego czy proces dostarczył własną funkcję do jego obsługi zajmuje się jej wywołaniem lub też kończy działanie pozostawiając sygnał do domyślnej obsługi. Jeśli proces ma przechwycić sygnał to trzeba odpowiednio zmodyfikować rejestry i stos procesu (patrz ---opis ogólny) do czego służą pomocnicze funkcje: