Obsługa sygnałów zgodna ze
standardem Posix
Wprowadzenie
Sygnały są formą komunikacji między procesami w systemie
Unix.
Sygnał jest to pewien komunikat wysyłany do procesu,
oznaczający że proces ma chwilowo przerwać wykonywanie.
Pojawia się on asynchronicznie, tzn. w dowolnym momencie
w czasie pracy procesu, niezależnie od tego co dany proces
akurat robi.
Proces nie wie z góry że otrzyma sygnał.
Wprowadzenie
Otrzymanie sygnału zazwyczaj znaczy, że nastąpiło jakieś
zdarzenie
wyjątkowe,
które
wymaga
od
procesu
natychmiastowej reakcji.
Sygnały są zazwyczaj używane do takich zadań jak kończenie
działania procesów, czy informowanie demonów, że mają
ponownie odczytać pliki konfiguracyjne.
Proces może otrzymać sygnał od:
jądra - np. w wyniku zdarzenia związanego ze sprzętem
innego procesu - za pomocą funkcji opisanych dalej
użytkownika - użycie polecenia kill, albo naciśnięcie kombinacji
klawiszy (np.ctrl-c)
Wprowadzenie
W zależności od rodzaju zdarzenia i żądanej reakcji,
wysyłane może być wiele różnych typów sygnałów.
Każdy sygnał ma przypisany numer oraz nazwę zaczynającą
się od "SIG". Nazwy te są zdefiniowane w pliku <signal.h>
W momencie otrzymania sygnału proces wstrzymuje
aktualnie wykonywaną pracę, i reaguje na sygnał na jeden z
trzech sposobów (tzw. akcja lub dyspozycja dla sygnału):
przechwycenie sygnału, tzn. wykonanie zdefiniowanej w
programie akcji
zignorowanie sygnału
wykonanie akcji domyślnej, skojarzonej z danym typem sygnału
- najczęściej zakończenie procesu w pewien sposób
Wprowadzenie
Po obsłużeniu sygnału przerwany proces kontynuuje
działanie od miejsca przerwania (o ile nie został
zakończony).
Dwa sygnały - SIGKILL i SIGSTOP - nie mogą zostać
przechwycone ani zignorowane. Po otrzymaniu któregoś z
nich proces musi wykonać akcję domyślną. Daje nam to
możliwość
bezwarunkowego
zatrzymania/zakończenia
dowolnego procesu, jeżeli zajdzie taka potrzeba.
Nie powinno się ignorować również niektórych sygnałów
związanych z błędami sprzętowymi (np. dzielenie przez 0,
czy niepoprawne odwołanie do pamięci).
Wprowadzenie
Jeśli chcemy przechwytywać jakiś sygnał, musimy
zdefiniować funkcję która będzie wykonywana po
otrzymaniu takiego sygnału, oraz powiadomić jądro za
pomocą funkcji signal(), która to funkcja.
Dzięki temu możemy np. sprawić że po naciśnięciu przez
użytkownika ctrl+c, nasz program zdąży jeszcze zamknąć
połączenia sieciowe czy pliki, usunąć pliki tymczasowe itd.
Rodzaje sygnałów
Są to wybrane sygnały, lista wszystkich sygnałów jest nieco
dłuższa. Zestawy sygnałów, numery im przypisane i sposób
ich obsługi mogą się nieco różnić w różnych wersjach
Unixów.
SIGHUP (1) - zerwanie łączności z terminalem; jest wysyłany wtedy, gdy
użytkownik rozłączy się z terminalem z którym związany jest dany
proces (np. przez wylogowanie); służy do zakończenia wszystkich
programów w momencie zakończenia sesji na terminalu; inny sposób
wykorzystania tego sygnału to powiadomienie procesów demonów, że
powinny przeładować pliki konfiguracyjne (wybrano ten sygnał,
ponieważ rozłączenie z terminalem nie jest istotne dla demonów)
[standardowa akcja: zakończenie procesu]
Rodzaje sygnałów
SIGINT (2) - sygnał przerwania, generowany w momencie naciśnięcia
przez użytkownika specjalnej kombinacji klawiszy (zwykle ctrl+c lub
delete); służy np. do przerwania pracy procesu który wyprowadza na
ekran dużą ilość niepotrzebnych danych
[standardowa akcja: zakończenie procesu]
SIGQUIT (3) - generowany po naciśnięciu klawisza zakończenia,
najczęściej ctrl+\.
[standardowa akcja: zakończenie procesu i zrzut core]
(zrzut core oznacza że w bieżącym katalogu jest tworzony plik "core", a
w nim zapisywany jest obraz pamięci procesu. Plik ten może być
wykorzystany np. przez debuggery)
Rodzaje sygnałów
SIGILL (4) - proces wykonał nieprawidłową instrukcję sprzętową
[standardowa akcja: zakończenie procesu i zrzut core]
SIGTRAP (5), SIGIOT (6), SIGEMT (7) - błąd sprzętowy, zależny od
implementacji systemu
[standardowa akcja: zakończenie procesu i zrzut core]
SIGFPE (8) - wyjątek zmiennopozycyjny (np. dzielenie przez zero lub
inny błąd operacji arytmetycznej)
[standardowa akcja: zakończenie procesu i zrzut core]
SIGKILL (9) - sygnał bezwarunkowego zakończenia procesu, którego nie
da się przechwycić ani zignorować
[standardowa akcja: zakończenie procesu]
SIGBUS (10) - błąd magistrali (błąd sprzętowy)
[standardowa akcja: zakończenie procesu i zrzut core]
Rodzaje sygnałów
SIGSEGV (11) - odwołanie do nieprawidłowego adresu w pamięci
(segmentation violation czyli naruszenie zasad segmentacji)
[standardowa akcja: zakończenie procesu i zrzut core]
SIGSYS (12) - próba wykonania nieprawidłowej funkcji systemowej
(nieprawidłowe argumenty)
[standardowa akcja: zakończenie procesu i zrzut core]
SIGPIPE (13) - próba zapisu do łącza komunikacyjnego, kiedy proces
czytający z tego łącza zakończył już pracę (dotyczy także komunikacji
przez gniazda sieciowe)
[standardowa akcja: zakończenie procesu]
SIGALRM (14) - alarm (pobudka); wysyłany po upłynięciu
odpowiedniego okresu czasu, ustalonego przez funkcję alarm() ub
setitimer()
[standardowa akcja: zakończenie procesu]
Rodzaje sygnałów
SIGTERM (15) - sygnał zakończenia procesu; podobny do SIGKILL, ale
można go przechwycić albo zignorować; jest to domyślny sygnał
polecenia "kill"
[standardowa akcja: zakończenie procesu]
SIGUSR1 (16), SIGUSR2 (17) - sygnały zdefiniowane przez użytkownika
[standardowa akcja: zakończenie procesu]
SIGCLD / SIGCHLD (18) - zmiana stanu (zakończenie lub zatrzymanie)
procesu potomnego
[standardowa akcja: zignorowanie sygnału]
SIGPWR / SIGINFO (19) - niedobór mocy; sygnał jest wysyłany kiedy
komputer pracuje na zasilaniu awaryjnym (UPS lub bateria), którego
napięcie spadło do krytycznego poziomu i komputer musi zostać
zamknięty w ciągu kilkudziesięciu sekund
[standardowa akcja: zakończenie procesu]
Rodzaje sygnałów
SIGSTOP - nakaz wstrzymania pracy procesu; nie może zostać
przechwycony ani zignorowany
[standardowa akcja: zatrzymanie procesu]
SIGCONT - nakaz kontunuacji pracy, wysyłany po wstrzymaniu
przez SIGSTOP
[standardowa akcja: kontynuacja lub zignorowanie sygnału]
SIGTSTP - sygnał zatrzymania; podobny do SIGSTOP, ale może
zostać zignorowany lub przechwycony; można go wysłać z
terminalu przez naciśnięcie kombinacji ctrl+z
[standardowa akcja: zatrzymanie procesu]
SIGABRT - awaryjne zakończenie (wysyłany za pomocą funkcji
abort())
[standardowa akcja: zakończenie procesu]
Polecenia dotyczące sygnałów
Z poziomu powłoki do obsługi sygnałów służą polecenia:
trap (wbudowane polecenie shella) - służy do określania reakcji
shella na odebranie przez niego poszczególnych sygnałów
kill - wysłanie do procesu lub grupy procesów określonego
sygnału; polecenie to jest interfejsem do opisanej niżej funkcji
systemowej kill(); można podać numer sygnału lub jego nazwę;
sposób interpretacji podanego PID'a przez polecenie jest
analogiczne jak w funkcji kill()
Funkcje ANSI C
i systemowe dotyczące sygnałów
int kill(pid_t pid, int signo);
Funkcja służy do wysyłania sygnałów do innych procesów. Signo
to numer sygnału, a pid może zostać podany na kilka sposobów:
jeżeli pid > 0, to sygnał jest wysyłany do jednego procesu o
podanym identyfikatorze pid
jeżeli pid < 0, to jest wysyłany do grupy procesów o identyfikatorze
pgid=-pid. czyli podanie pid -48 spowoduje wysłanie sygnału do
wszystkich procesów należących do grupy 48
jeżeli pid == 0, to sygnał jest wysyłany do wszystkich procesów
należących do tej samej grupy, co aktualny proces
akcja dla pid == -1 jest różna na różnych systemach, może to być
np. wysłanie sygnału do wszystkich procesów należących do tego
samego użytkownika co proces wysyłający
Funkcje ANSI C
i systemowe dotyczące sygnałów
Wysłanie sygnału następuje tylko pod warunkiem,
że dany proces ma uprawnienia do wysyłania
sygnałów podanym innym procesom, oraz że
procesy docelowe nie należą do grupy procesów
systemowych (najczęściej: proces wymiany (0), init
(1) i demon stronicowania (2)). Jeżeli w polu signo
podamy 0 (tzw. sygnał pusty), to funkcja kill nie
wysyła żadnego sygnału, a tylko sprawdza czy
docelowy proces istnieje.
Funkcje ANSI C
i systemowe dotyczące sygnałów
int raise(int signo);
Służy do wysyłania sygnałów przez proces do siebie samego.
void (*signal (int signo, void (*func)(int)))(int);
Funkcja ta służy do ustalenia przechwytywania podanego
sygnału signo. W momencie otrzymania przez proces takiego
sygnału, o ile jest on przechwytywalny, zostanie wykonana
funkcja, do której wskaźnik przekazujemy jako parametr func.
Możemy w to miejsce wstawić także stałą SIG_IGN (sygnał
będzie wtedy ignorowany) lub SIG_DFL (zostanie wykonana akcja
domyślna). Funkcja przechwytująca sygnał musi przyjmować
jeden parametr typu int (numer sygnału) i nic nie zwracać.
Funkcje ANSI C
i systemowe dotyczące sygnałów
unsigned int alarm(unsigned int seconds);
Służy do ustawienia licznika czasu na podaną liczbę
sekund. Po upłynięciu zadanego czasu, jądro wyśle do
procesu sygnał SIGALRM. Może on zostać przechwycony,
w przeciwnym wypadku zostanie wykonana akcja
domyślna czyli zakończenie. Jeżeli seconds==0, to
poprzednio ustawiony licznik jest wyłączany.
int pause(void);
Wykonywanie procesu jest wstrzymywane, do czasu otrzymania
jakiegokolwiek sygnału (np. SIGALRM ustawionego wcześniej
funkcją alarm()). Może to być wykorzystane np. do wstrzymania
procesu na określony czas, jak w funkcji sleep().
Funkcje ANSI C
i systemowe dotyczące sygnałów
void abort(void);
Wywołanie tej funkcji powoduje awaryjne zakończenie procesu.
Jest to realizowane przez wysłanie do procesu sygnału SIGABRT,
którego nie może on zignorować. Może go przechwycić, ale tylko
w celu wykonania tzw. czynności porządkujących, później
program i tak zostanie zakończony.