Sunteți pe pagina 1din 10

Semnale pentru comunicarea intre procese

1. Semnale

In acest document vor fi prezentate facilitatile UNIX referitoare la semnale pentru a realiza comunicarea intre procese. Semnalele sunt o modalitate de exprimare a evenimentelor care apar sincron sau asincron in sistem. Un proces oarecare poate atat sa genereze, cat si sa primeasca semnale. In cazul in care un proces primeste un semnal, el poate alege sa reactioneze la semnalul respectiv intr-unul din urmatoarele trei moduri: sa le capteze, executand o actiune oarecare, prin intermediul unei functii de tratare a semnalului (signal handler) sa le ignore sau sa le blocheze sa execute actiunea implicita la primirea unui semnal, care poate fi, dupa caz, terminarea procesului sau ignorarea semnalului respectiv. Semnalele pot fi de mai multe tipuri, care corespund in general unor actiuni specifice. Fiecare semnal are asociat un numar, iar acestor numere le corespund unele constante simbolice definite in bibliotecile sistemului de operare (headerul <signal.h>). Lista semnalelor poate fi obtinuta si prin comanda kill l. 1.1 Semnale uzuale

Semnale de terminare SIGTERM - un mod politicos de a cere programului s-i termine execuia SIGINT - program nterrupt; acest semnal este trimis procesului atunci cnd utilizatorul folosete CTRL-C SIGQUIT - similar cu SIGINT doar c este folosit combinaia de taste CTRL\ SIGKILL - procesul este imediat terminat; acest semnal NU poate fi tratat de ctre proces SIGHUP - semnal generat de ctre sistemul de operare atunci cnd procesul este deconectat de la terminal (de exemplu atunci cnd lsm un proces pentru execuie n background si apoi facem logout; actiunea implicit pentru SIGHUP este de terminare a procesului; dac vrem ca procesul s supravieuiasc trebuie s blocam acest semnal, folosind utilitarul nohup). Semnale cauzate de erori ale programelor SIGFPE - floating point exceptions, mprtire la zero, depsire. SIGILL - illegal instruction; se ncearc executia unei non-instructiuni sau a unei instructiuni privilegiate.

SIGSEGV - segmentation violation; programul ncearc s citeasc sau s scrie n afara regiunii de memorie alocate sau s scrie ntr-o regiune de memorie read-only. (genereaza celebrul mesaj Segmentation fault) SIGBUS - ncercare de a accesa o adres invalid. SIGABRT - procesul a detectat o eroare intern si a apelat functia abort() Toate aceste semnale au ca actiune implicit terminarea procesului si generarea unui fisier core dump. Acest fisier poate fi folosit de ctre un debugger mai trziu pentru analizarea cauzelor care au dus la terminarea anormal a procesului. Semnale de alarm SIGALRM - expirarea unui timer care msoar timp real; timerul poate fi armat de funcia alarm SIVTALRM - virtual time alarm; expirarea unui timer care msoar timpul de procesor al procesului Semnale generate de operatii de I/E asincrone SIGIO - trimis de sistemul de operare procesului, atunci cnd nu se poate citi sau scrie ntr-un descriptor de fisier. SIGURG - trimis de sistemul de operare atunci cnd date urgente (out-ofband) ajung pe un socket Semnale pentru controlul job-urilor SIGCHLD - se trimite atunci cnd unul din copiii procesului se termin sau esteoprit; actiunea implicit este de a ignora semnalul. SIGCONT - se trimite atunci cnd procesul a fost pornit, dup ce a fost oprit cu SIGSTOP; acest semnal nu poate fi blocat; se poate seta un handler, dar indiferent de rezultatul executiei sale, procesul va fi pornit. SIGSTOP opreste un proces; semnalul nu poate fi blocat, ignorat sau tratat SIGTSTP - similar cu SIGSTOP, dar poate fi tratat sau ignorat; se genereaz aunci cnd se tasteaz CTRL-Z. Atunci cnd procesul este oprit, numai SIGKILL i SIGCONT pot fi trimise procesului. Alte semnale sunt marcate ca pending (n asteptare) si vor fi trimise cnd procesul isi continu rularea. Alte semnale SIGPIPE - broken pipe; atunci cnd se folosesc pipe-uri, aplicatia trebuie proiectat astfel nct procesele s deschid pipe-ul mai nti pentru citire; dac un pipe se deschide mai nti pentru scriere sau procesul care citeste se termin sau nchide pipe-ul se va genera acest semnal; actiunea implicit este terminarea procesului; dac procesul blocheaz, trateaz sau ignor semnalul, operatia de scriere va ntoarce eroarea EPIPE

SIGUSR1 si SIGUSR2 - pentru clase de evenimente definite de utilizator; actiunea implicit este de terminare a procesului Diferitele variante de UNIX existente implementeaza aceste semnale, adaugand si altele noi. Pentru a obtine o lista cu toate semnalele existente in Linux, de exemplu, consultati pagina de manual signal(7).

2. Tratarea semnalelor
2.1 Apelul sistem signal() Modalitatea prin care se poate realiza captarea semnalelor in scopul tratarii lor este data de apelul sistem signal(). Acest apel sistem are urmatoarea forma:
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);

Executia acestui apel sistem va face ca, pentru semnalul cu numarul signum specificat ca si parametru, sa se instaleze o noua rutina de tratare specificata prin parametrul handler. Acest parametru trebuie sa aiba tipul sighandler_t, care inseamna de fapt adresa unei functii care nu returneaza nimic si are un singur parametru formal de tip intreg. Acest parametru va primi ca valoare, in momentul executiei rutinei de tratare a semnalului, numarul semnalului care se trateaza. Acest lucru este util, de exemplu, atunci cand se doreste tratarea mai multe tipuri de semnale folosind aceeasi rutina. Exista doua valori speciale pentru parametrul handler, si anume: 1. SIG_IGN, care instruieste procesul sa ignore semnalul specificat 2. SIG_DFL, care reseteaza tratarea semnalului la comportamentul implicit. Apelul sistem signal() returneaza rutina anterioara de tratare a semnalului, sau SIG_ERR in caz de eroare. SIG_ERR se returneaza de exemplu atunci cand signum este SIGKILL sau SIGSTOP, care sunt semnale ce nu pot fi nici captate, nici ignorate. De mentionat faptul ca pe durata rutinei de tratare, semnalul care a fost receptionat este blocat si va fi eventual tratat la iesirea din aceasta rutina. Un exemplu de utilizare semnale :
void handler(int signum){ // in acest moment semnalele de acelasi tip sunt blocate // totusi celelate tipuri de semnale sunt active printf("Se trateaz semnalul %d\n", signum); // rearmez handler cu SIG_DFL signal(SIGINT, SIG_DFL); }

int main(int argc, char **argv){ signal(SIGINT,handler); raise(SIGINT); }

Urmatorul subparagraf va prezenta o metoda de captare a semnalelor care ofera mai multa flexibilitate decat cea descrisa anterior, prin folosirea apelului sistem sigaction() si a apelurilor asociate cu acesta. 2.2 Apelul sistem sigaction()
#include <signal.h> int sigaction(int signum, struct sigaction *act, sigaction *oldact);

Apelul sistem sigaction() este similar cu apelul sistem signal(). El are menirea de a defini comportarea procesului la primirea unui semnal. Acest apel primeste ca parametri numarul semnalului (signum), si doua structuri de tip struct sigaction * (act si oldact). Executia sa va avea ca efect instalarea noii actiuni pentru semnalul specificat din act (daca acest parametru este nenul), si salvarea actiunii curente in oldact (la fel, daca parametrul este nenul). Structura de tip struct sigaction este definita in felul urmator:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }

Parametrul sa_handler reprezinta noua rutina de tratare a semnalului specificat, similar cu parametrul handler prezentat la signal(). Alternativ, daca in sa_flags este setat indicatorul SA_SIGINFO (prezentat mai jos), se poate defini o rutina care primeste trei parametri in loc de unul singur, si in acest caz ea se specifica prin parametrul sa_sigaction. Pentru mai multe detalii legate de folosirea acestei din urma modalitati, consultati pagina de manual sigaction(2). Parametrul sa_sigaction reprezinta handlerul ce trebuie setat n structur n cazul n care s-a specificat flagul SA_SIGINFO . Handlerul primeste trei parametri. Primul parametru este numarul semnalului, al doilea este un pointer la o structur de tip siginfo_t ce contine informatii aditionale despre semnal. Ultimul parametru este un pointer la o structur ce indic contextul de dinaintea primirii semnalului (rar utilizat). Definiia structurii siginfo_t este urmtoarea :
siginfo_t { int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */

int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void * si_ptr; /* POSIX.1b signal */ void * si_addr; /* Memory location which caused fault */ int si_band; /* Band event */ int si_fd; /* File descriptor */ }

Parametrul sa_mask va specifica setul de semnale care vor fi blocate in timpul executiei rutinei de tratare a semnalului dat. Acest parametru este de tipul sigset_t, care este de fapt o masca de biti, cu cate un bit pentru fiecare semnal definit in sistem. Operatiile asupra acestui tip de masca se fac folosind functiile din familia sigsetops(3).
int int int int int sigemptyset(sigset_t *set) sigfillset(sigset_t *set) sigaddset(sigset_t *set, int signo) sigdelset(sigset_t *set, int signo) sigismember(sigset_t *set, int signo)

Mastile de semnale trebuie initializate folosind fie sigemptyset, fie sigfillset. Un exemplu care adaug si apoi scoate semnalul SIGUSR2:
sigset_t masca; sigemptyset(&masca); semnale.sa_mask = masca; sigaddset(&masca, SIGUSR2); sigdelset(&masca, SIGUSR2);

Parametrul sa_flags va specifica un set de indicatori care afecteaza comportarea procesului de tratare a semnalelor. Acest parametru se formeaza prin efectuarea unei operatii de SAU logic folosind una sau mai multe din urmatoarele valori: SA_NOCLDSTOP - daca signum este SIGCHLD, procesul nu va primi un semnal SIGCHLD atunci cand procesul fiu este suspendat (de exemplu cu SIGSTOP), ci numai cand acesta isi termina executia; SA_ONESHOT sau SA_RESETHAND - va avea ca efect resetarea rutinei de tratare a semnalului la SIG_DFL dupa prima rulare a rutinei, asemanator cu comportamentul implementarii originale a apelului signal(); SA_NOMASK sau SA_NODEFER - semnalul in discutie nu va fi inclus in sa_mask (comportamentul implicit este acela de a impiedica aparitia unui semnal in timpul executiei rutinei de tratare a semnalului respectiv); SA_SIGINFO - se specifica atunci cand se doreste utilizarea lui sa_siginfo in loc de sa_handler..

In continuare este prezentat un exemplu de utilizare sigaction :


// handler pt SIGUSR2 void handler1(int signum,siginfo_t * info,void * param){ ... } int main(void) { struct sigaction semnale; sigset_t masca,mask,oldmask; sigemptyset(&masca); sigaddset(&masca,SIGUSR2); semnale.sa_flags=SA_SIGINFO; semnale.sa_mask=masca; semnale.sa_sigaction=handler1; sigaction(SIGUSR1,&semnale,(struct sigaction*)0); }

2.3 Blocarea semnalelor. Apelul sistem sigprocmask()


int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

Apelul sigprocmask() este folosit pentru a modifica lista semnalelor care sunt blocate la un moment dat. Acest lucru se face in functie de valoarea parametrului how, in felul urmator: 1. SIG_BLOCK - adauga la lista semnalelor blocate semnalele din lista set data ca parametru; 2. SIG_UNBLOCK - sterge din lista semnalelor blocate semnalele aflate in lista set; 3. SIG_SETMASK - face ca doar semnalele din lista set sa se regaseasca in lista semnalelor blocate. Daca parametrul oldset este nenul, in el se va memora valoarea listei semnalelor blocate anterioara executiei lui sigprocmask().
int sigpending(sigset_t *set);

Apelul sistem sigpending() permite examinarea semnalelor care au aparut in timpul in care ele au fost blocate, prin returnarea acestor semnale in masca set data ca parametru.
int sigsuspend(const sigset_t *mask);

Apelul sigsuspend() inlocuieste temporar masca semnalelor blocate a procesului cu cea data in parametrul mask si suspenda procesul pana la primirea unui semnal.

Apelurile sigaction(), sigprocmask() si sigpending() intorc valoarea 0 in caz de succes si -1 in caz de eroare. Apelul sigsuspend() intoarce intotdeauna -1, iar in mod normal variabila errno este setata la valoarea EINTR.

3. Alte functii pentru lucrul cu semnale


3.1 Apelul sistem kill()
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);

Acest apel sistem este folosit pentru a trimite un semnal unui anumit proces sau grup de procese. In functie de valoarea parametrului pid, executia apelului va avea mai multe efecte; pentru mai multe detalii accesati pagina manual kill(2). 3.2 Functia raise()
#include <signal.h> int raise(int sig);

Aceasta functie este folosita pentru a trimite un semnal catre procesul curent. Executia ei este similara cu executia urmatorului apel: kill(getpid(), sig). 3.3 Functia abort()
#include <stdlib.h> void abort(void);

Aceasta functie are ca efect trimiterea catre procesul curent a unui semnal SIGABRT, care are ca efect terminarea anormala a procesului, mai putin daca semnalul este tratat de o rutina care nu se termina. Daca executia lui abort() are ca efect terminarea procesului, toate fisierele deschise in interiorul acestuia vor fi inchise. Este important de notat ca, daca semnalul SIGABRT este ignorat sau blocat, executia functiei nu va tine cont de acest lucru si procesul va fi terminat in mod anormal. 3.4 Functia alarm()
#include <unistd.h> unsigned int alarm(int seconds);

Executia acestei functii are ca rezultat trimiterea, dupa scurgerea unui numar de secunde dat de seconds, a unui semnal de tipul SIGALRM catre procesul curent. Daca o alarma a fost deja programata, ea este anulata in momentul executiei ultimului apel, iar daca valoarea lui seconds este zero, nu va fi programata o

alarma noua. In urma executiei, se returneaza numarul de secunde ramase din alarma precedenta, sau 0 daca nu era programata nici o alarma. 3.5 Semnalarea proceselor cu funcia sigqueue() Funcia kill nu garanteaz faptul c semnalul a ajuns la destinatie si nu ofer niciun mecanism prin care s se verifice acest lucru. Dac avem nevoie s trimitem un semnal unui proces si s stim sigur c a ajuns putem utiliza functia sigqueue :
int sigqueue(pid_t pid, int signo, const union sigval value);

Parametri acesteia sunt urmatorii:


pid - PID-ul procesului cruia i se va trimite semnalul signo - semnalul ce va fi trimis value - o valoare ce va fi trimis odat cu semnalul

Functia trimite semnalul signo cu valoarea specificat de value procesului cu identificatorul pid. Valoarea ce poate fi trimisa odata cu semnalul este un union care arata astfel:
union sigval { int sival_int; void *sival_ptr; };

Un parametru trimis astfel apare in campul si_value al structurii siginfo_t primite de handler-ul de semnal. Dac semnalul specificat este blocat n acel moment, functia va iesi imediat; dac flagul SA_SIGINFO este setat si exist resurse necesare, semnalul va fi pus n coad n starea pending (un proces poate avea n coad de pending maxim SIGQUEUE_MAX semnale). Atunci cnd semnalul este primit, cmpul si_code pasat structurii siginfo va fi setat la SI_QUEUE. Dac nu exist resurse necesare pentru a trimite semnalul (i.e., dac toate cele SIGQUEUE_MAX locuri n coad sunt ocupate) functia esueaza. Dac flagul SA_SIGINFO nu este setat, atunci signo, dar nu n mod necesar si value vor fi trimise cel putin o dat procesului care trebuie s primeasc semnalul.

4. Exercitii
1. Sa se scrie un program care sa aiba comportamentul urmator: la primirea semnalului SIGINT (CTR-C), programul intra intr-o bucla infinita din care va iesi doar la receptionarea unui semnal ce nu poate fi mascat. In aceasta bucla, procesul va afisa PID-ul sau la fiecare 5 secunde. 2. Sa se scrie un cod care sa ilustreze utilizarea primitivelor signal, pause, wait si kill. Se va avea in vedere: ilustrarea mostenirii semnalelor intr-un process fiu (apel fork) tratarea semnalelor dupa executia apelului exec.

Se vor considera cele trei tipuri de comportament la receptia unui semnal: comportament implicit (SIG_DFL), ignorare (SIG_IGN) si tratarea de catre o rutina. 3. Realizati un program care afiseaza numarul oricarui semnal receptionat. Programul va indica la fiecare 3 secunde ca este activ si ca asteapta primirea de noi semnale. Daca s-au primit 5 semnale sau daca s-au scurs 27 secunde, programul se va opri. Semnalele SIGALRM vor fi contate doar daca provin de la un alt proces. 4. Sa se implementeze un program timeout cu urmatorii parametri: timeout time command arg1 arg2 argN, care sa respecte urmatoarea functionare: procesul tata lanseaza un proces fiu care executa comanda data ca parametru, armand in acelasi timp o alarma de time secunde. Daca la expirarea timpului indicat, procesul fiu nu s-a terminat, acesta este terminat de procesul tata cu afisarea unui mesaj corespunzator. 5. Un process creeaza trei fii F1, F2 si F3. Activitatile celor 4 procese sunt: tatal asteapta sfarsitul fiilor si afiseaza un mesaj despre terminarea lor; F1 creeaza un fiu F1 si se pune in asteptare printr-un pause(). F1 asteapta 2 secunde, ucide procesul sau tata si se termina cu un cod de retur 2. F2 doarme 10 secunde si apoi se termina cu codul 3. F3 asteapta 15 secunde si se termina cu codul de retur 6.

Toate procesele vor executa o functie handler_child la receptia semnalului SIGCHLD. Aceasta functie va afisa PID-ul procesului intrerupt. 6. Realizati un program denumit mynohup care simuleaz comanda nohup(1). Programul primeste ca prim parametru numele unei comenzi de executat. Restul parametrilor reprezint argumentele cu care trebuie invocat comanda respectiv; lista de argumente poate fi nul. Programul executat de mynohup trebuie s nu fie instiintat de nchiderea terminalului la care era conectat. Dac fisierul standard de iesire era legat la un terminal acesta trebuie redirectat n fisierul nohup.out. Pentru a determina dac un descriptor de fisier este conectat la un terminal folositi functia isatty(3).

7. Realizati un program numit nozombie care va crea un proces copil nou care se va termina imediat dup rulare apelnd exit(3). Procesul tata va astepta 5 secunde si apoi va iesi. Implementarea se va face astfel nct procesul copil s nu treac n starea de zombie, fr insa a folosi functiile de asteptare wait(2)/waitpid(2). 8. Realizati un program denumit askexit care face busy waiting afisand la consol numere consecutive. Programul va intercepta semnalele generate de CTRL+Z si CTRL+C si va ntreba utilizatorul dac doreste s ncheie executia sau nu. 9. Realizati un program denumit monitor care creeaz 3 subprograme workeri si apoi rmne suspendat (n pause() sau sleep()) pn cnd primeste un semnal de terminare. La nchiderea procesului printe toti copii si sunt semnalizati si isi vor incheia si ei executia. Observatie: nu terminati procesul printe cu CTRL+C din consol. Semnalul SIGINT este transmis tuturor programelor care ruleaz n foreground pe terminalul la care s-a tastat CTRL+C si va nchide si procesele copii n mod automat. Monitorul ar trebui s functioneze si n conditiile n care nu ar exista un terminal asociat acestor procese. Folositi kill(1) pentru a termina procesul printe. Asigurati-v n signal handler-ul din fiecare proces worker c sursa semnalului este chiar terminarea procesului printe si c nu s-a primit acel semnal pe alt cale (testati implementarea corect trimitand semnalul respectiv cu comanda kill(1) dintr-o alt consol). 10. Implementai un server si un client care comunic prin semnale. Clientul primete ca parametri pid-ul serverului si un numr n si trimite un semnal serverului cu un mesaj coninnd o cerere de calcul al succesorului lui n. Serverul va rspunde clientului prin aceeai metod. La generarea semnalului va trebui s folosii sigqueue(2) i s primii mesajul folosind paramentrul de tip siginfo_t trimis handlerului de semnal. Serverul i va ncheia execuia cnd primete n <= 0. 11. Implementai problema productor consumator folosind semnalele SIGUSR1 i SIGUSR2. Pe Linux de exemplu se poate folosi un singur proces in care productorul este handlerul de semnal SIGUSR1 si consumatorul este SIGUSR2 si cele dou entitati se semnalizeaz alternativ. Pentru simplificare, puteti considera c bufferul este de un singur element.

S-ar putea să vă placă și