Sygnaly sluza do zawiadamiania procesow o wystapieniu pewnych sytuacji wyjatkowych. Jedyna cecha sygnalu odebranego przez proces jest jego numer. Niemozliwe jest stwierdzenie nadawcy sygnalu, liczby wyslanych sygnalow, ani zadnej dodatkowej informacji na jego temat. Stad nazywanie sygnalow mechanizmem komunikacji jest lekka przesada. Za to doskonale nadaje sie on do informowania procesu o sytuacji wyjatkowej (patrz Sygnaly-opis).
Proces w momencie otrzymania sygnalu moze znajdowac sie w roznych stanach, przez co roznorodne sa takze jego reakcje na sygnal. Jesli sygnal, ktory jest wysylany, jest zablokowany, to zdarzenia, ktore wymieniam dalej, sa wstrzymywane az do wykonania przez proces odblokowania sygnalu. Gdy proces jest w stanie TASK_INSTERRUPTIBLE jest on budzony z kolejki oczekiwania i jego stan jest ustawiany na TASK_RUNNING (patrz Procesy-stany). W przypadku gdy proces znajduje sie w trybie uzytkownika i jest gotowy do wykonania lub wykonywany TASK_RUNNING (patrz Procesy-stany) to spodziewane, sekwencyjne wykonywanie procesu jest przerywane i wykonywana jest akcja zwiazana z tym sygnalem (patrz Sygnaly-spis). Po zakonczeniu wykonywania akcji zwiazanej z sygnalem proces powraca do miejsca, w ktorym jego dzialanie zostalo przerwane. Gdy proces jest w stanie TASK_RUNNING lecz znajduje sie w trybie jadra (wykonuje jakas funkcje systemowa) to akcja zwiazana z sygnalem jest wykonywana po powrocie z funkcji systemowej a przed wznowieniem programu. Struktury i funkcje, ktore sa tutaj opisane realizuja podana specyfikacje.
Wiekszosc typow i struktur danych zwiazanych z sygnalami jest zdefiniowana w pliku /asm/signal.h. Sa tam takze zdefiniowane stale zwiazane z numerami sygnalow (patrz Sygnaly-spis). Procz tego znajdziemy tam stale zwiazane ze zdefiniowanymi typami i strukturami danych.
Definicja typu sigset_t z pliku asm/signal.h:
typedef unsigned long sigset_t; /* co najmniej 32 bity */
Typ sigset_t sluzy do trzymania zbioru sygnalow (kazdemu sygnalowi odpowiada dokladnie jeden bit). Jak widac jest to liczba co najmniej 32-bitowa, stad stala ilosci sygnalow definiowana jest w nastepujacy sposob:
#define _NSIG 32 #define NSIG _NSIG
Typ __sighandler_t jest to typ funkcji obslugi przerwania zdefiniowany w pliku asm/signal.h nastepujaco:
typedef void (*__sighandler_t)(int);
Zdefiniowane sa takze stale, standardowe wartosci zmiennych tego typu:
#define SIG_DFL ((__sighandler_t)0) /* domyslna obsluga sygnalu */ #define SIG_IGN ((__sighandler_t)1) /* ignorowanie sygnalu */ #define SIG_ERR ((__sighandler_t)-1) /* blad (przy powrocie z funkcji signal) */
Oto dokladna definicja struktury sigaction z pliku asm/signals.h:
/* jedna struktura sigaction dla kazdego sygnalu */ struct sigacion { _sighandler_t *sa_handler; /* funkcja obslugi sygnalu */ sigset_t sa_mask; unsigned long sa_flags; void (*sa_restorer)(void); };
Dodatkowe wyjasnienia:
Struktura procesu zostala dokladnie opisana w proces-struktura procesu. Jednak wiele pol w strukturze procesu dotyczy sygnalow dlatego zostana one tutaj wymienione z krotkim opisem. Struktura procesu jest zdefiniowana w pliku include/linux/shed.h.
Pole to jest zadeklarowane:
unsigned long signal;
Maska sygnalow otrzymanych przez proces, ale jeszcze nie obsluzonych. Kazdemu sygnalowi odpowiada jeden bit liczby 32-bitowej. Dlatego niemozliwe jest stwierdzenie ile sygnalow danego rodzaju otrzymal proces. Pole to pokazuje czy proces otrzymal co najmniej jeden sygnal o danym numerze. Z opisu wynika ze proces nie zawsze reaguje natychmiast na przyjscie sygnalu. Ma to miejsce na przyklad gdy dany sygnal jest blokowany (patrz sygnaly-dzialanie).
Pole to jest zadeklarowane:
unsigned long blocked;
Maska sygnalow aktualnie blokowanych przez proces (ich obsluga jest odwlekana). Zapalony bit odpowiadajacy danemu sygnalowi oznacza ze ow sygnal jest blokowany. Mozliwe jest blokowanie wszystkich sygnalow procz SIGKILL i SIGSTOP.
Pole to jest zadeklarowane:
struct signal_struct *sig;
gdzie struktura signal_struct jest zadeklarowana w pliku include/linux/shed.h nastepujaco:
struct signal_struct { int count; /* licznik dowiazan do struktury */ struct sigaction action[32]; /* akcje zwiazane z poszczegolnymi sygnalami */ };
Pole sig zawiera informacje o akcjach podejmowanych przez proces przy otrzymaniu sygnalu. Jak widac w strukturze signal_struct jest licznik dowiazan do tej struktury-count (handlery sygnalow moga byc wspoldzielone przez kilka procesow a wlasciwie watkow - patrz funkcja-clone). Procz tego struktura zawiera tablice, w ktorej pojedynczy element opisuje akcje zwiazana z sygnalem (patrz struktura-sigaction).
Pole to jest zadeklarowane:
int exit_signal;
Pole to zawiera numer sygnalu, ktory ma byc wyslany do rodzica procesu w momencie jego smierci (standardowo wynosi SIGCHLD).
Istnieja cztery glowne funkcje systemowe zwiazane z obsluga sygnalow, ktore nie sa dostepne dla zwyklych procesow uzytkownika, ale za to sa uzywane czesto w roznych miejscach w jadrze systemu.
Funkcja ta jest zdefiniowana w /kernel/exit.c. Sluzy do wyslania sygnalu przez aktualny proces.
DEFINICJA: int send_sig( unsigned long sig, struct task_struct * p, int priv ) WYNIK: 0 w przypadku sukcesu, lub gdy proces docelowy jest zombie lub sygnal 0 -EINVAL gdy p==0 lub sig>32 -EPERM gdy nie ma przywilejow (priv==0) i to nie jest sygnal SIGCONT lub nie jest z tej samej sesji co p i p->suid i p->uid sa rozne od current->uid i current->euid (maja roznych wlascicieli) i proces wysylajacy nie ma praw zarzadcy systemu
Pierwszym argumentem funkcji jest numer sygnalu, ktory ma byc wyslany (musi sie zawierac w przedziale 1..32). Argument p jest wskaznikiem do struktury procesu procesu, do ktorego ma byc wyslany sygnal. Gdy argument trzeci jest niezerowy wtedy niezaleznie od praw aktualnego procesu (proces wysylajacy sygnal) nie bedzie mu zabronione wyslanie sygnalu.
Funkcja rozpoczyna swoje dzialanie od sprawdzenia poprawnosci argumentow i czy aktualny proces (current) moze wyslac sygnal do procesu p. Z definicji funkcji wynika iz wyslanie sygnalu jest dozwolone (funkcja nie zwraca -EPERM) gdy jest spelniony jeden z nastepujacych warunkow:
Nastepnie sprawdzane jest czy sygnal sig to jeden z sygnalow budzacych (SIGKILL lub SIGCONT). Jesli tak i proces jest zatrzymany (TASK_STOPPED)(patrz proces-stany) to proces jest budzony. Procz tego ustawiany jest kod wyjscia procesu na 0 (exit_code) i zerowane sa w polu signal procesu bity odpowiadajace sygnalom ktore moglyby zatrzymac proces (sa to: SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU)(patrz sygnaly-spis).
Jesli natomiast sygnal sig to jeden z sygnalow, ktore moglyby spowodowac zatrzymanie procesu (sa to: SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU)(patrz sygnaly-spis) to w polu signal procesu p zerowany jest bit odpowiadajacy za sygnal budzacy SIGCONT (patrz sygnaly-spis).
Jesli sygnal sig nie jest blokowany ani proces p nie jest sledzony (tj. gdy sygnal moze byc obsluzony natychmiast) to: gdy obsluga sygnalu to ignorowanie(SIG_IGN)(patrz typ-sighandler) i sygnal to SIGCHLD (patrz sygnaly-spis) nastepuje powrot z funkcji z rezultatem 0. Podobnie jest w przypadku gdy obsluga sygnalu jest domyslna (SIG_DFL)(patrz typ-sighandler) a sygnal to jeden z: SIGCONT,SIGCHLD,SIGWINCH,SIGURG.
Nastepnie gdy jeszcze nie zakonczono funkcji w polu signal procesu docelowego p ustawiany jest bit odpowiadajacy za sygnal wysylany sig. Dodatkowo gdy proces jest w stanie TASK_INTERRUPTIBLE (patrz proces-stany) i sygnal nie jest blokowany, proces p jest budzony. Funkcja zwraca 0.
- sluzy do bezwarunkowego wysylania sygnalu.
DEFINICJA: void force_sig( unsigned long sig, struct task_struct *p ) WYNIK: brak
Pierwszym argumentem funkcji jest numer wysylanego sygnalu. p to wskaznik do struktury procesu procesu docelowego.
Gdy proces docelowy jeszcze istnieje, w polu signal (patrz proces-struktura) jego struktury jest ustawiany bit odpowiadajacy za sygnal sig, a w polu blocked ten sam bit jest zerowany (to zapewni, ze sygnal zostanie obsluzony od razu po pobraniu procesu do wykonania). Gdy obsluga sygnalu jest ustawiona na ignorowanie (SIG_IGN) to przestawiana jest na obsluge domyslna (SIG_DFL)(patrz typ-sighandler). W przypadku gdy proces p jest w stanie TASK_INTERRUPTIBLE jest on budzony.
- sluzy do obslugi komunikatow ktore przyszly do procesu aktualnego (current), ale jeszcze nie zostaly obsluzone (ustawione bity na polu signal-patrz pole-signal). Funkcja ta jest wolana jedynie w momencie wyjscia z funkcji systemowej a takze w funkcji systemowej sigsuspend(). Wynika z tego, ze sygnaly sa obslugiwane jedynie w momencie wyjscia z funkcji systemowej.
DEFINICJA: int do_signal( unsigned long oldmask, struct pt_regs *regs ) WYNIK: 0 w przypadku gdy nie obsluzono zadnego sygnalu 1 gdy obsluzono jeden sygnal
Pierwszym argumentem jest stara maska sygnalow (sprzed wywolania handlera). Drugi argument to zachowane na stosie wartosci rejestrow procesora sprzed wywolania funkcji systemowej.
Dzialanie funkcji mozna przedstawic za pomoca nastepujacego pseudoalgorytmu:
{ while (istnieje jeszcze nie obsluzony sygnal nie blokowany) { signr=numer takiego sygnalu i bit sygnalu zerowany; sa=struktura sigaction odpowiadajaca za sygnal signr; if ( ( proces jest sledzony )&&( signr to nie SIGKILL ) ) { stan procesu ustalany na TASK_STOPPED , kod wyjscia /*exit_code*/ na signr; notify_parent(current); shedule(); if(!(signr=aktualny kod wyjscia procesu /*exit_code*/)) continue; kod wyjscia procesu /* exit code */ = 0; if( signr = SIGSTOP ) continue; if( sygnal signr jest blokowany ) ustawiany jest bit tego sygnalu /*w polu signal procesu*/ , continue; sa=struktura sigaction odpowiadajaca za sygnal signr; } if ( w sa handler jest ustawiony na ignorowanie ) { if ( signr to nie SIGCHLD ) continue; while ( sys_waitpid(-1,NULL,WNOHANG) > 0) ; /* wszystkie dzieci zombie usuwane */ continue; } if ( w sa handler syganalu ustawiony na obsluge domyslna ) { if ( czy to proces init ) continue; switch (signr) { case SIGCONT: case SIGCHLD: case SIGWINCH: continue; case SIGTSTP: case SIGTTIN: case SIGTTOU: if ( grupa procesu jest osierocona /* is_orphaned_pgrp */ ) continue; case SIGSTOP: if ( proces jest sledzony ) continue; stan procesu ustawiany na TASK_STOPPED, kod wyjscia na signr; if ( czy obsluga sygnalu SIGCHLD u rodzica ma ustawione zawiadamianie o zatrzymaniu dziecka ) notify_parent(); schedule(); continue; case SIGQUIT: case SIGILL: case SIGTRAP: case SIGABRT: case SIGFPE: case SIGSEGV: zrzut na dysk obrazu procesu /*CORE DUMP*/; default: signal|=bit sygnalu numer ( signr & 0x7f ); w strukturze procesu ustawiona flaga zabicia przez sygnal; do_exit(signr); } /* case */ } /* if */ obsluga sygnalu signr handlerem sa z maska oldmask i rejestrami regs; return 1; } return 0; }
- wolana gdy nalezy poinformowac rodzica jakiegos procesu o jego zatrzymaniu lub zakonczeniu.
DEFINICJA: void notify_parent( struct task_struct *tsk ) WYNIK: brak
Argumentem funkcji jest struktura procesu, ktory zmienil stan i chce poinformowac rodzica.
Funkcja rozpoczyna swoje dzialanie od sprawdzania czy rodzicem procesu jest Init. Jesli tak, to pole exit_signal procesu tsk jest ustawiane na SIGCHLD.
Po tym do procesu rodzica wysylany jest sygnal exit_signal (funkcja send_sig z przywilejami ustawionymi). Nastepnie kolejka czekajacych na smierc tego procesu jest budzona.
Funkcje systemowe opisane ponizej wystepuja w zrodlach pod podanymi nazwami z dodanymi przedrostkami sys_. Wiele z nich korzysta z funkcji wewnetrznych jadra podanych powyzej.
- sluzy do wysylania sygnalu.
DEFINICJA: int kill( int pid, int sig ) WYNIK: 0 w przypadku sukcesu -1 gdy nastapil blad; wtedy errno przybiera nastepujace wartosci: EINVAL bledny parametr (nr sygnalu) ESRCH nie znaleziony proces do zabicia EPERM peoces nie posiada praw do wykonania tej funkcji
Drugim argumentem funkcji jest numer sygnalu, ktory ma byc wyslany do procesu lub grupy procesow opisanych przez pierwszy argument. W zaleznosci od wartosci argumentu pid zmienia sie adresat:
Niezaleznie od rodzaju parametrow funkcja korzysta z funkcji jadra send_sig z parametrem perm rownym 0. Stad blad EPERM wystapi tylko wtedy, gdy wystapilby w przypadku wywolania funkcji send_sig.
- sluzy do ustawiania maski sygnalow blokowanych i oczekiwania na nadejscie ktoregos z sygnalow nieblokowanych.
DEFINICJA: int sigsuspend( unsigned long set ) WYNIK: -1 z errno ustawionym na EINTR
Argumentem funkcji jest maska blokowania sygnalow.
Dzialanie funkcji:
{ mask=aktualna maska blokawania sygnalow; ustaw maske blokowania sygnalow na set; while (1) { ustaw stan procesu na TASK_INTERRUPTIBLE; schedule(); if ( do_signal(mask,...) ) return -1(EINTR); } }
- sluzy do ustawiania maski blokowania sygnalow.
DEFINICJA: int sigprocmask( int how, sigset_t *set , sigset_t *oset ) WYNIK: 0 w przypadku sukcesu -1 gdy wystapil blad; ustawia ERRNO na: EINVAL gdy how mialo wartosc nieznanej opcji;
Pierwszym argumentem funkcji jest numer sposobu ustawiania maski. Moze przybrac wartosc jednej ze stalych zdefiniowanych w asm/signal.h:
#define SIG_BLOCK 0 /* blokowanie sygnalow */ #define SIG_UNBLOCK 1 /* odblokowywanie sygnalow */ #define SIG_SETMASK 2 /* ustawianie nowej maski */
Argument set jest wskaznikiem na nowa maske blokowania sygnalow. Gdy wskaznik oset jest niezerowy to jest tam wpisywana stara maska blokowania syganlow sprzed wywolania tej funkcji.
- pobiera maske blokowania sygnalow.
DEFINICJA: int sig( ) WYNIK: maska blokowania sygnalow
- ustawia maske blokowania sygnalow.
DEFINICJA: int ssetmask( int newmask ) WYNIK: poprzednia maska blokowania sygnalow
pobiera zbior wstrzymywanych (przez maske blokowania) sygnalow .
DEFINICJA: int sigpending( sigset_t *set ) WYNIK: 0 w przypadku sukcesu -1 gdy nastapil blad zapisu pod adres set
Zbior blokowanych sygnalow jest wstawiany pod set.
ustawia nowa procedure obslugi sygnalu.
DEFINICJA: _sighandler_t signal( int signum , _sighandler_t handler ) WYNIK: poprzedni handler obslugi tego sygnalu w przypadku sukcesu SIG_ERR gdy nastapil blad
Argument signum oznacza numer sygnalu, a handler to nowa procedura obslugi sygnalu (patrz typ _sighandler_t).
ustawia nowa akcje zwiazana z danym sygnalem.
DEFINICJA: int sigaction( int signum , const struct sigaction *action , struct sigaction *oldaction) WYNIK: 0 w przypadku sukcesu -1 gdy nastapil blad (errno=EINVAL)
Ustawia nowa akcje action zwiazana z sygnalem signum. Stara akcja zwiazana z tym sygnalem jest wpisywana na oldaction. Oba parametry sa typu struct sigaction.
jest wywolywana przy powrocie z procedury obslugi handlera sygnalu.
DEFINICJA: int sigreturn( ) WYNIK: brak
Funkcja powinna byc wywolywana przy powrocie z kazdej procedury obslugi sygnalu.
Biorac pod uwage definicje stanu uspienia z ksiazki Bacha (patrz Bibliografia) najblizszy temu opisowi wydaje sie stan TASK_INTERRUPTIBLE (patrz proces-stany). Jest to taki stan zatrzymania procesu, ktory moze byc przerwany przez sygnal. Procesy przebywajace w tym stanie sa w wiekszosci przypadkow zatrzymane na strukturze kolejki oczekiwania (struct wait_queue). Do usypiania i budzenia calych kolejek oczekiwania sluza funkcje opisane ponizej.
Definicja typu sigset_t z pliku linux/wait.h:
struct wait_queue { struct task_struct * task; struct wait_queue * next; };
Na pojedynczej strukturze tego typu sa wstrzymywane wszystkie procesy, ktore maja byc obudzone w przypadku wystapienia zdarzenia z pewnej grupy zdarzen.
Funkcja wake_up jest zaimplementowana w pliku linux/kernel/sched.c.
DEFINICJA: void wake_up( struct wait_queue **q ) WYNIK: brak
Funkcja budzi wszystkie procesy znajdujace sie na kolejce oczekiwania a bedace w stanie TASK_INTERRUPTIBLE lub TASK_UNINTERRUPTIBLE. Budzenie procesu polega na ustawieniu stanu procesu na TASK_RUNNING i wlozeniu go do kolejki procesow gotowych. O usuniecie z kolejki oczekiwania zadba sam proces obudzony.
Funkcja wake_up_interruptible jest zaimplementowana w pliku linux/kernel/sched.c.
DEFINICJA: void wake_up_interruptible( struct wait_queue **q ) WYNIK: brak
Funkcja budzi wszystkie procesy znajdujace sie na kolejce oczekiwania a bedace w stanie TASK_INTERRUPTIBLE. Budzenie procesu polega na ustawieniu stanu procesu na TASK_RUNNING i wlozeniu go do kolejki procesow gotowych. O usuniecie z kolejki oczekiwania zadba sam proces obudzony (czyli jak wyzej).
Funkcja interruptible_sleep_on jest zaimplementowana w pliku linux/kernel/sched.c.
DEFINICJA: void interruptible_sleep_on( struct wait_queue **q ) WYNIK: brak
Funkcja usypia proces aktualny na kolejce q ustawiajac jego stan na TASK_INTERRUPTIBLE.
Funkcja rozpoczyna dzialanie od dodania procesu aktualnego do kolejki q i ustawieniu stanu procesu na TASK_INTERRUPTIBLE. Nastepnie wywolywana jest funkcja schedule() zmiany procesu aktualnego. Po obudzeniu procesu jest on usuwany z kolejki q.
Funkcja sleep_on jest zaimplementowana w pliku linux/kernel/sched.c.
DEFINICJA: void sleep_on( struct wait_queue **q ) WYNIK: brak
Funkcja usypia proces aktualny na kolejce q ustawiajac jego stan na TASK_UNINTERRUPTIBLE (patrz wyzej).