Laboratoare:Semnale

Semnale în Linux
În lumea reală un proces se poate întâlni cu o multitudine de situații neprevăzute care-i afectează cursul de execuție normal. Dacă procesul nu le poate trata ele sunt trecute mai departe sistemului de operare. Cum sistemul de operare nu poate ști dacă procesul își poate continua execuția în mod normal fără efecte secundare nedorite este obligat să termine procesul în mod forțat. O rezolvare a acestei probleme o reprezintă semnalele.

Semnalele sunt un concept specific sistemelor de operare UNIX. Un semnal este o întrerupere software a unui proces din fluxul de execuție normal. Sistemul de operare le folosește pentru a semnala procesului apariția unor situații excepționale oferindu-i procesului posibilitatea de a interacționa. Fiecare semnal este asociat cu o clasă de evenimente care pot apărea și care respectă anumite criterii. Procesele pot trata, bloca, ignora sau lăsa sistemul de operare să efectueze acțiunea implicită la primirea unui semnal (de obicei acțiunea implicită este terminarea procesului).

Mulțimea tipurilor de semnale este finită; sistemul de operare ține pentru fiecare proces o tabelă cu acțiunile alese de acesta pentru fiecare tip de semnal. La fiecare moment de timp aceste acțiuni sunt bine determinate. La pornirea procesului tabela de acțiuni este inițializată cu valorile implicite. Modul de tratare al semnalului nu este decis la primirea semnalului de proces, ci se alege în mod automat din tabelă.

În continuare se va folosi noțiunea de semnal pentru a indica alternativ fie un anumit tip de semnal, fie efectiv obiectele de acest tip.

Semnalele pot fi de asemenea folosite de alte procese pentru a întrerupe anumite procese. Semnalele sunt sincrone/asincrone cu fluxul de execuție al procesului care primește semnalul dacă evenimentul care cauzează trimiterea semnalului este sincron/asincron cu fluxul de execuție al procesului.

Un eveniment este sincron cu fluxul de execuție al procesului dacă apare de fiecare dată la rularea programului, în același punct al fluxului de execuție. Exemple în acest sens sunt încercarea de accesare a unei locații de memorie invalide sau nepermisă, împărțire prin zero, etc. Un eveniment este asincron dacă nu este sincron. Exemple de evenimente asincrone : un semnal trimis de un alt proces - semnalul de terminare unui proces copil, sau o cerere de terminare externă - utilizatorul dorește să reseteze calculatorul.

Un semnal primit de un proces poate fi generat fie direct de sistemul de operare (în cazul în care acestea raportează diferite erori), fie de un proces (care-și poate trimite și singur semnale); evident semnalul trimis de un proces va trece tot prin sistemul de operare. Daca un proces dorește să ignore un semnal, sistemul de operare nu va mai trimite acel semnal procesului. Dacă un proces specifică că dorește să blocheze un semnal, sistemul de operare nu va mai trimite semnalele de acel tip spre procesul în cauză, și va salva numai primul semnal de acel tip, restul pierzându-se. Când procesul hotărăște că vrea să primească din nou semnale, dacă era vreun semnal în așteptare va fi trimis. Dacă două semnale sunt prea apropiate în timp ele se pot confunda într-unul singur. Astfel, în mod normal, nu există niciun mecanism care să garanteze celui care trimite semnalul că acesta a ajuns la destinație.

În anumite cazuri, există nevoia de a ști în mod sigur că un semnal trimis a ajuns la destinație și implicit că procesul va răspunde la el (efectuând una din acțiunile posibile). Pentru aceste situații, sistemul de operare oferă un alt mod de a trimite un semnal prin care se garantează fie că semnalul a ajuns la destinație, fie că această acțiune a eșuat. Acest lucru este realizat prin crearea unei stive de semnale, de o anumită capacitate (ea trebuie să fie finită pentru a nu produce situații de overflow). La trimiterea unui semnal, când cererea ajunge la sistemul de operare acesta verifică dacă stiva este plină. În acest caz cererea eșuează, altfel semnalul va fi pus în coadă și operația se termină cu succes. Modul anterior de a trimite semnale este analog cu acesta (stiva are dimensiunea unu) cu excepția faptului că nu se oferă informații despre ajungerea la destinație a unui semnal.

Conceptele generării semnalelor
În general, evenimentele care generează semnale se încadrează în trei categorii majore: erori, evenimente externe și cereri explicite.

O eroare indică faptul că un program a făcut ceva greșit și nu-și poate continua execuția. Însă nu toate tipurile de erori generează semnale - de fapt, cele mai multe nu o fac. De exemplu, deschiderea unui fișier inexistent este o eroare, dar nu generează un semnal; în schimb, apelul de sistem open returnează -1, indicând că apelul s-a terminat cu insucces( open returnează 0 în caz de succes, ca majoritatea funcțiilor în Unix). În general, erorile care sunt asociate cu anumite biblioteci sunt raportate returnând o valoare care indică o eroare. Erorile care generează semnale sunt cele care pot apărea oriunde în program, nu doar în apelurile din biblioteci. Ele includ împărțirea cu zero și adresele de memorie invalide.

Un eveniment extern este în general legat de I/O și alte procese. Ele includ apariția de noi date de intrare, expirarea unui timer, și terminarea execuției unui proces copil.

O cerere explicită indică utilizarea unei funcții de bibiliotecă cum ar fi kill pentru a genera un semnal.

Semnalele pot fi generate sincron sau asincron. Un semnal sincron se raportează la o acțiune specifică din program, și este trimis(dacă nu este blocat) în timpul acelei acțiuni. Cele mai multe erori generează semnale în mod sincron, la fel ca anumite cereri explicite făcute de către un proces de a genera un semnal pentru același proces. Pe anumite mașini, anumite tipuri de erori hardware ( de obicei excepțiile în virgulă mobilă) nu sunt raportate complet sincron, și pot ajunge câteva instrucțiuni mai târziu.

Semnalele asincrone sunt generate de evenimente necontrolabile de către procesul care le primește. Aceste semnale ajung la momente de timp impredictibile în timpul execuției. Evenimentele externe generează semnale în mod asincron, la fel ca și cererile explicite pentru alte procese.

Un tip de semnal dat este fie sincron, fie asincron. De exemplu, semnalele pentru erori sunt în general sincrone deoarece erorile generează semnale în mod sincron. Însă orice tip de semnal poate fi generat sincron sau asincron cu o cerere explicită.

Transmiterea și primirea semnalelor
Când un semnal este generat, el intră într-o stare de așteptare (pending). În mod normal el rămâne în această stare pentru o perioadă de timp foarte mică și apoi este trimis procesului care era destinația semnalului. Însă, dacă acel tip de semnal este în momentul de față blocat, el ar putea rămâne în starea de așteptare în mod indefinit - până când semnalele de acel timp sunt deblocate. Odată deblocat acel tip de semnale, el va fi trimis imediat.

Când semnalul a fost primit, fie imediat sau după o întârziere mare, acțiunea specificată pentru acel semnal este executată. Pentru anumite semnale. cum ar fi SIGKILL și SIGSTOP, acțiunea este fixată (procesul este terminat), dar pentru majoritatea semnalelor programul poate alege să ignore semnalul, să specifice o funcție de tip handler, sau să accepte acțiunea implicită pentru tipul acela de semnal. Programul își specifică alegerea utilizând funcții precum signal sau sigaction. În timp ce handler-ul rulează, acel tip de semnale este în mod normal blocat (deblocarea se va face printr-o cerere explicită în handlerul care tratează semnalul).

Dacă acțiunea specificată pentru un tip de semnal este să îl ignore, atunci orice semnal de acest tip care este generat pentru procesul în cauză este ignorat. Același lucru se întâmplă dacă semnalul este blocat în acel moment. Un semnal neglijat în acest mod nu va fi primit niciodata. nici dacă programul specifică ulterior o acțiune diferită pentru acel tip de semnal și apoi îl deblochează.

Dacă este primit un semnal pentru care nu s-a specificat niciun tip de acțiune, se execută acțiunea implicită. Fiecare tip de semnal are propria lui acțiune implicită. Pentru majoritatea semnalelor acțiunea implicită este terminarea procesului. Pentru anumite tipuri de procese care reprezintă evenimente fără consecințe mari, acțiunea implicită este să nu se facă nimic.

Când un semnal forțează terminarea unui proces, părintele său poate determina cauza terminării sale examinând codul de terminare raportat de funcțiile wait și waitpid. Informațiile pe care le poate obține includ faptul că terminarea procesului s-a datorat unui semnal, precum și tipul semnalului. Dacă un program pe care îl rulați din linia de comandă este terminat de un semnal, shell-ul printează de obicei niște mesaje de eroare.

Semnalele care în mod normal reprezintă erori de program au o proprietate specială: când unul din aceste semnale termină procesul, el scrie și un fișier core dump care înregistrează starea procesului în momentul terminării. Puteți examina fișierul cu un debugger pentru a afla ce anume a cauzat eroarea.

Dacă generați un semnal care e in mod normal de tip eroare de program prin cerere explicită, și acesta termină procesul, fișierul este generat ca și cum semnalul ar fi fost generat de o eroare.

Important: În cazul în care un semnal este trimis procesului în timp ce acesta executa un apel de sistem blocant, procesul va suspenda apelul, va executa handler-ul de tratare și apoi fie operația va eșua (cu errno setat pe EINTR), fie se va restarta operația. Sistemele System V se comportă ca în primul caz, cele BSD ca în cel de al doilea.

Tipuri standard de semnale
Această secțiune prezintă numele pentru diferite tipuri standard de semnale și descrie ce fel de evenimente indică. Fiecare nume de semnal este o macrodefiniție care reprezintă de fapt un număr întreg pozitiv - numărul pentru acel tip de semnal. Un program nu ar trebui să facă niciodată presupuneri despre codul numeric al unui tip particular de semnal, ci mai degrabă să le refere întotdeauna prin nume. Acest lucru se datorează faptului că un număr pentru un tip de semnal poate varia de la sistem la sistem, dar numele sunt standard. Pentru lista completă de semnale suportate de un sistem se poate rula în linia de comandă kill -l

$ kill -l

1) SIGHUP      2) SIGINT       3) SIGQUIT      4) SIGILL 5) SIGTRAP     6) SIGABRT      7) SIGBUS       8) SIGFPE 9) SIGKILL    10) SIGUSR1     11) SIGSEGV     12) SIGUSR2 13) SIGPIPE    14) SIGALRM     15) SIGTERM     17) SIGCHLD 18) SIGCONT    19) SIGSTOP     20) SIGTSTP     21) SIGTTIN 22) SIGTTOU    23) SIGURG      24) SIGXCPU     25) SIGXFSZ 26) SIGVTALRM  27) SIGPROF     28) SIGWINCH    29) SIGIO 30) SIGPWR     31) SIGSYS      33) SIGRTMIN    34) SIGRTMIN+1 35) SIGRTMIN+2 36) SIGRTMIN+3  37) SIGRTMIN+4  38) SIGRTMIN+5 39) SIGRTMIN+6 40) SIGRTMIN+7  41) SIGRTMIN+8  42) SIGRTMIN+9 43) SIGRTMIN+10 44) SIGRTMIN+11 45) SIGRTMIN+12 46) SIGRTMIN+13 47) SIGRTMIN+14 48) SIGRTMIN+15 49) SIGRTMAX-15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX

Numele de semnale sunt definite în header-ul signal.h. În general semnalele au roluri predefinite, dar acestea pot fi suprascrise de programator. Semnalul SIGINT este transmis la apăsarea combinației CTRL+C, semnalul SIGQUIT în momentul apăsării combinației de taste CTRL+\, SIGSEGV în momentul accesării unei locații invalide de memorie etc. Semnalul SIGKILL nu poate fi ignorat sau suprascris. Transmiterea acestui semnal are ca efect terminarea procesului indiferent de context.

<!--

Semnale cauzate de erori ale programelor

 * SIGFPE - floating point exceptions, împărțire la zero, depășire
 * SIGILL - illegal instruction; se încearcă execuția unei non-instrucțiuni sau a unei instrucțiuni 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. (Acesta este semnalul care genereaza mesajul celebru printre programatorii Linux - Segmentation fault
 * SIGBUS - încercare de a accesa o adresă invalidă (citire/scriere/execuție de la adrese nealiniate)
 * SIGABRT - procesul a detectat o eroare internă și a apelat funcția abort
 * SIGTRAP - folosit de debuggere; procesul pe care se face debug nu vede aceste semnale, decât în cazul în care execută instrucțiuni invalide
 * SIGSYS - apel de sistem invalid

Toate aceste semnale au ca acțiune implicită terminarea procesului și generarea unui fișier core dump. Acest fișier poate fi folosit de către un debugger mai târziu pentru analizarea cauzelor care au dus la terminarea anormală a procesului.

Semnale de terminare

 * SIGTERM - un mod politicos de a cere programului să-și termine execuția
 * SIGINT - program înterrupt; acest semnal este trimis procesului atunci când utilizatorul folosește CTRL-C
 * SIGQUIT - similar cu SIGINT doar că este folosită combinația de taste CTRL-\
 * SIGKILL - procesul este imediat terminat; acest semnal NU poate fi tratat de către proces
 * SIGHUP - acest semnal este generat de către sistemul de operare atunci când procesul este deconectat de la terminal (de exemplu atunci când lăsăm un proces pentru execuție în background și apoi facem logout; întrucat acțiunea implicită pentru SIGHUP este de terminare a procesului, dacă vrem ca procesul să supraviețuiască trebuie să blocam acest semnal, folosind utilitarul nohup).

Aceste semnale au ca acțiune implicită terminarea procesului. A se reține faptul că SIGKILL nu poate fi tratat.

Semnale de alarmă

 * SIGALRM - expirarea unui timer care măsoară timp real; timerul poate fi armat de funcția alarm
 * SIVTALRM - virtual time alarm; expirarea unui timer care măsoară timpul de procesor al procesului
 * SIGPROF - expirarea unui timer care măsoară timpul de procesor al procesului și timpul de procesor utilizat de sistemul de operare pentru îndeplinirea operațiilor cerute de proces; este utilizat pentru activități de code profiling

Semnale generate de operatii de I/E asincrone

 * SIGIO - trimis de sistemul de operare procesului, atunci când se poate citi sau scrie într-un descriptor de fișier
 * SIGURG - trimis de sistemul de operare atunci când date urgente (out-of-band) ajung pe un socket
 * SIGPOLL - similar cu SIGIO

Acțiunea implicită pentru aceste semnale este ignorarea lor.

Semnale pentru controlul job-urilor

 * SIGCHLD - se trimite atunci când unul din copiii procesului se termină sau se oprește; acțiunea implicită este de a ignora semnalul
 * SIGCONT - se trimite atunci când 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 execuției lui, procesul va fi pornit
 * SIGSTOP - oprește un proces; acest semnal nu poate fi blocat, ignorat sau tratat
 * SIGTSTP - similar cu SIGSTOP, dar poate fi tratat sau ignorat; se generează aunci când se tastează CTRL-Z
 * SIGTTIN - un proces ce se află în execuție în background nu poate citi de la terminal; dacă se încearcă acest lucru se generează acest semnal; acțiunea implicită este de a opri procesul
 * SIGTTOU - generat dacă un proces ce sa află în execuție în background încearcă să scrie la terminal, și terminalul are setat flagul TOSTOP; acțiunea implicită este de a opri procesul

Atunci când procesul este oprit, numai SIGKILL și SIGCONT pot fi trimise procesului. Alte semnale sunt marcate ca pending (în așteptare) și vor fi trimise când procesul își continuă rularea.

Alte semnale
-->
 * SIGPIPE - broken pipe; atunci când se folosesc pipe-uri, aplicația trebuie proiectată astfel încât procesele să deschidă pipe-ul mai întâi pentru citire; dacă un pipe se deschide mai întâi pentru scriere sau procesul care citește se termină sau închide pipe-ul se va genera acest semnal; acțiunea implicită este terminarea procesului; dacă procesul blochează, tratează sau ignoră semnalul, operația de scriere va întoarce eroarea EPIPE
 * SIGUSR1 - pentru clase de evenimente definite de utilizator; acțiunea implicită este de terminare a procesului
 * SIGUSR2 - pentru clase de evenimente definite de utilizator; acțiunea implicită este de terminare a procesului

Mesaje pentru descrierea semnalelor
Cel mai bun mod de a afișa un mesaj de descriere a unui semnal este utilizarea funcțiilor strsignal și psignal. Aceste funcții folosesc un număr de semnal pentru a specifica tipul de semnal care trebuie descris. Mai jos este prezentat un exemplu de folosire a acestor funcții:

Pentru compilare și rulare secvența este:

$ gcc -Wall -g -o strsignal_psignal strsignal_psignal.c $ ./strsignal_psignal signal 9 is Killed death and decay: Killed

Măști de semnale. Blocarea semnalelor
Pentru a putea efectua operații de blocare/deblocare semnale avem nevoie să știm la fiecare pas în fluxul de execuție starea fiecărui semnal la acel moment. Sistemul de operare are de asemenea nevoie de același lucru pentru a putea face o decizie asupra unui semnal care trebuie trimis unui proces (el are nevoie de acest gen de informație pentru fiecare proces în parte). În acest scop se folosește o mască de semnale proprie fiecărui proces.

O mască de semnale are fiecare bit asociat unui tip de semnal. Masca de biți este folosită de mai multe funcții, printre care și funcția sigprocmask folosită pentru schimbarea măștii de semnale a procesului curent.

Tipul de date folosit de sistemele UNIX pentru a reprezenta măștile de semnale este sigset_t</tt>. Variabilele de acest tip sunt neinițializate. Operațiile pe acest tip de date sunt de inițializare cu biți de 0 (toate semnalele neblocate) sau biți de 1 (toate semnalele blocate), de blocare a unui semnal, deblocare și detectare a blocării unui semnal:

Secvența de mai jos este un caz de utilizare a funcțiilor de lucru cu masca de semnale în care, la fiecare 5 secunde, se blochează/deblochează semnalul SIGINT:

Tratarea semnalelor
Tratarea semnalelor se realizează prin asocierea unei funcții (handler) unui semnal. Funcția va fi apelată în momentul în care procesul recepționează mesajul.

În mod tradițional, funcția folosită pentru asocierea de handlere pentru tratarea unui semnal era signal. Pentru a preîntâmpina deficiențele acestei funcții, standardul POSIX a definit funcția sigaction pentru asocierea unui handler cu un semnal. sigaction oferă mai mult control, cu prețul unui grad de complexitate mai mare.

Componenta importantă a funcției sigaction este structura omonimă, descrisă tot în pagina de manual a funcției:

În cazul în care în câmpul sa_flags se precizează flag-ul SA_SIGINFO</tt>, handler-ul folosit este cel specificat de sa_sigaction</tt>. Altfel, handler-ul folosit este sa_handler</tt>. sa_mask</tt> indică o mască de semnale care ar trebui blocate în timpul execuției handler-ului.

Un exemplu de asociere a unui handler de tratare a unui semnal este prezentat mai jos:

Programatorul poate opta pentru configurarea unui handler propriu sau poate folosi unul predefinit. Se poate folosi SIG_IGN</tt> pentru ignorarea semnalului sau SIG_DFL</tt> pentru rularea acțiunii implicite (terminarea procesului, ignorarea semnalului etc.)

Structura siginfo_t
În cazul prezenței SA_SIGINFO</tt> se folosește câmpul sa_sigaction</tt> al structurii sigaction</tt> pentru a specifica handler-ul asociat semnalului. Handler-ul folosit primește în acest caz trei parametri și poate fi folosit pentru a transmite o informație utilă o dată cu procesul. Al treilea argument (de tipul void *</tt>) este rar utilizat. Al doilea argument, de tipul siginfo_t</tt> definește o structură conține informații utile despre contextul apariției semnalului și alte informații pe care le poate furniza programatorul. Definiția structurii se găsește în pagina de manual a funcției sigaction.

Membrii structurii sunt inițializați numai atunci când valorile lor sunt utile. Membrii si_signo</tt>, si_errno</tt> și si_code</tt> sunt totdeauna definiți pentru toate semnalele. Restul structurii poate fi o uniune, așa că ar trebui citite numai câmpurile care au sens pentru semnalul primit. Spre exemplu, apelul de sistem kill</tt>, semnalele POSIX.1b și <tt>SIGCHLD</tt> completează <tt>si_pid</tt> și <tt>si_uid</tt>, iar <tt>SIGILL</tt>, <tt>SIGFPE</tt>, <tt>SIGSEGV</tt> și <tt>SIGBUS</tt> completează <tt>si_addr</tt> cu adresa care a provocat eroarea.

Semnalarea proceselor
Pentru transmiterea unui semnal, se poate folosi funcția kill sau funcția sigqueue. Funcția kill are dezavantajul că nu garantează recepționarea semnalului de procesul destinație. Dacă este nevoie să se trimitem un semnal unui proces și să se știe sigur că a ajuns se recomandă folosirea funcției sigqueue:

Funcția trimite semnalul <tt>signo</tt> cu valoarea specificată de <tt>value</tt> procesului cu identificatorul <tt>pid</tt>. Dacă semnalul este zero, se fac verificări pentru cazurile de eroare posibile, dar nu se trimite niciun semnal. Semnalul nul poate fi folosit pentru a verifica faptul că pid-ul este valid.

Valoarea ce poate fi trimisa odata cu semnalul este un union:

Un parametru trimis astfel apare în câmpul <tt>si_value</tt> al structurii <tt>siginfo_t</tt> primite de handler-ul de semnal. In mod evident, nu are sens transmiterea de pointeri dintr-un proces in altul.

Condițiile cerute pentru ca un proces să aibă permisiunea de a trimite un semnal altui proces sunt aceleași ca și în cazul lui <tt>kill</tt>. Dacă semnalul specificat este blocat în acel moment, funcția va ieși imediat și dacă flagul SA_SIGINFO este setat și există resurse necesare, semnalul va fi pus în coadă în starea pending (un proces poate avea în coadă de pending maxim SIGQUEUE_MAX semnale). De asemenea, când semnalul este primit, câmpul <tt>si_code</tt> pasat structurii siginfo va fi setat la <tt>SI_QUEUE</tt>, și <tt>si_value</tt> va fi setat la <tt>value</tt>.

Dacă flagul SA_SIGINFO nu este setat, atunci <tt>signo</tt>, dar nu în mod necesar și <tt>value</tt> vor fi trimise cel puțin o dată procesului care trebuie să primească semnalul.

Așteptarea unui semnal
În cazul în care se utilizează semnalele pentru comunicare și/sau sincronizare există adeseori nevoie să se aștepte ca un anumit tip de semnal să parvină procesului în cauză. Un simplu mod de a realiza acest lucru este o buclă a cărei condiție de ieșire ar fi setarea corespunzătoare a unei variabile (variabila trebuie să fie de tipul <tt>sig_atomic_t</tt>). De exemplu:

Principalul dezavantaj al abordării de mai sus (de tip busy-waiting) este timpul de procesor pe care procesul considerat îl pierde în mod inutil. O variantă ar fi folosirea funcției sleep:

O astfel de abordare nu ar mai ocupa timp de procesor inutil, dar timpul de răspuns în cazul sosirii unui semnal este destul de mare. O altă soluție a problemei este funcția pause (care blochează fluxul de execuție până când procesul curent este întrerupt de un semnal). Deși această abordare pare foarte simplă, ea introduce adeseori deadlock-uri care blochează programul nedefinit. Un exemplu în acest sens este pseudosoluția de mai jos la problema așteptării unui semnal:

Bucla este necesară pentru prevenirea situației în care procesul este întrerupt de alte semnale decât cel așteptat. Se poate întâmpla ca semnalul să ajungă după testarea variabilei și inainte de apelul funcției pause. În acest caz procesul se blochează și dacă nu apare un alt semnal care să cauzeze ieșirea din pause, el va rămâne blocat nedefinit.

Soluția cea mai bună pentru a aștepta un semnal se poate realiza prin utilizarea funcției sigsuspend:

Funcția înlocuiește masca de semnale blocate a procesului cu <tt>set</tt> și apoi suspendă procesul până când este primit un semnal care nu este blocat de noua mască. La ieșire, funcția restaurează vechea mască de semnale.

În secvența de mai jos, funcția sigsuspend este folosită pentru a întrerupe procesul curent până la recepționarea semnalului <tt>SIGUSR1</tt>. Semnalele <tt>SIGKILL</tt> și <tt>SIGSTOP</tt>, deși prezente în masca de semnale, nu vor fi blocate:

Considerente privind utilizarea unui handler de semnal
Un obiect de tip semnal este atașat unui obiect de tip proces. Dacă procesul nu rulează în acel moment sistemul de operare poate atașa unui proces numai un singur semnal care va rămâne în starea pending. Dacă procesul rula în acel moment semnalul primit este deservit imediat și va întrerupe fluxul normal de execuție, permițându-se primirea fără pierdere a unui semnal de același tip. Evident, același lucru se întâmplă și cu semnalele trimise cu sigqueue, cu deosebirea că numărul de semnale care pot fi pending pentru un proces nu mai este 1, ci SIGQUEUE_MAX.

Pentru că numărul de semnale de un anumit tip care poate fi primit de un proces într-un anumit timp este limitat și pentru a evita pierderea de semnale un handler trebuie să se execute cât mai repede.

Fluxul de execuție al unui proces este văzut de către sistemul de operare ca o înșiruire de instrucțiuni pe care platforma le suportă. De aceea unele operații din limbajele de programare de nivel înalt nu sunt atomice și indivizibile, fiind nevoie de mai multe instrucțiuni în cod mașină pentru a se efectua respectiva operație. Un exemplu simplu este atribuirea între variabile Majoritatea platformelor actuale nu permit instrucțiuni în care ambii operanzi să fie aleși din memorie. Deci o implentare standard pentru această operație ar fi încărcarea valorii lui b într-un registru, după care ar urma încărcarea la adresa lui a a valorii salvate în registru : De aceea, este nevoie de atenție suplimentară atunci când un semnal folosește variabile care nu sunt locale funcției, deoarece semnalele pot întrerupe fluxul de execuție în orice punct al său, lăsând astfel unele variabile într-o stare inconsistentă. Pentru a fi siguri că o variabilă nu are valori inconsistente se recomandă folosirea tipului sig_atomic_t pentru variabilele din fluxul de execuție care interacționează cu handlerele de semnale. Acest tip este unul din tipurile întregi disponibile, dar care anume este poate varia de la o platformă la alta. Așadar operațiile ce se pot efectua cu acest tip, sunt aceleași cu cele ale unui întreg.

Timere în Linux
În Linux, folosirea timer-elor este legată de folosirea semnalelor. Acest lucru se întâmplă întrucât cea mai mare parte a funcțiilor de tip timer folosesc semnale.

Un timer este, de obicei, un întreg a cărui valoare este decrementată pe parcursul trecerii timpului. În momentul în care întregul ajunge la 0, timer-ul expiră. În Linux, rezultatul expirării timer-ului, este, în general, transmiterea unui semnal. Definirea unui "timer handler" (rutină apelată în momentul expirării timer-ului) este, astfel, echivalentă cu definirea unui handler pentru semnalul asociat.

Înregistrarea unui timer în Linux înseamnă specificarea unui interval după care un timer expiră și configurarea handler-ului care va rula. Fiind vorba de semnale, configurarea handler-ului se realizează prin intermediul funcției sigaction. Specificarea unui interval de timeout se realiză folosind funcția alarm sau setitimer.

Apelul alarm este mai simplu, dar oferă granularitate la nivel de secundă. La expirarea timeout-ului, se transmite semnalul SIGALRM.

Apelul setitimer este mai complex, dar oferă granularitate la nivel de microsecundă și permite control mai bun asupra modului de actualizare a întregului. Prin intermediul primului argument se poate măsura timpul real al sistemului, timpul de rulare a procesului sau timpul de rulare a procesului în user-space și kernel-space. În funcție de primul argument se vor livra semnalele SIGALRM, SIGVTALRM respectiv SIGPROF.

Mai jos este prezentată o secvență de cod de folosire a funcțiilor alarm și setitimer

Atenție: Secvența de mai sus are rol academic. Nu este recomandată combinarea apelurilor alarm și setitimer. Standardul POSIX nu specifică modul de interacțiune între cele două funcții iar efectele nu sunt deterministe.

Una din formele de utilizare a timerelor este implementarea funcțiilor de așteptare de tipul sleep sau nanosleep. Avantajul folosirii funcției sleep este simplitatea. Dezavantajele sunt rezoluția scăzută (secunde) și posibila interacțiune cu semnale (în special <tt>SIGALRM</tt>). nanosleep are un apel mai complex, dar oferă rezoluție până la ordinul nanosecundelor și este "signal-safe" - nu interacționează cu semnale.

Semnale și timere sub Windows
În Windows există mai multe mecanisme de notificare a proceselor: evenimente, APC-uri, evenimente de consolă, timere. Evenimentele sunt folosite pentru sincronizarea între thread-uri/procese. APC-urile sunt mecanisme de execuție asincronă în contextul unui proces/thread. Pot fi folosite pentru rularea unei secvențe de cod în combinație cu un timer.

În secțiunile de mai jos vor fi prezentate funcțiile de interacțiune cu evenimentele venite de la tastatură și funcțiile de lucru cu timere.

Evenimente de la consolă
API-ul Win32 permite configurarea de funcții care să fie apelate în momentul apăsării unei anumite combinații de taste. Aceste funcții sunt similare cu handler-ele de semnale <tt>SIGINT</tt> sau <tt>SIGQUIT</tt> în Linux. În afara combinațiilor de taste un proces poate primi și semnale de forma <tt>CTRL_CLOSE_EVENT</tt>, <tt>CTRL_LOGOFF_EVENT</tt> sau <tt>CTRL_SHUTDOWN_EVENT</tt>.

Tipul de date PHANDLER_ROUTINE definește rutina apelată în momentul recepționării unui mesaj. Parametrul funcției descrie tipul semnalului primit. Rutina este similară handler-ului de semnal din Linux.

Înregistrarea unei rutine de tratare a semnalului se realizează cu ajutorul apelului SetConsoleCtrlHandler. Spre deosebire de apelul sigaction, funcția înregistrată va fi rulată în momentul generării tuturor semnalelor precizate mai sus.

Exceptând modul "clasic" de transmitere a semnalelor prin intermediul tastaturii, se pot genera semnale folosind apelul GenerateConsoleCtrlEvent.

Mai jos este o secvență uzuală de înregistrare a unui handler pentru tratarea semnalelor de tastatură:

<!--

Apeluri asincrone
Un apel de funcție este asincron dacă se execută în paralel cu apelantul astfel încât acesta poate continua procesarea instrucțiunilor sale fără a mai aștepta ca funcția sa revină, urmând ca sincronizarea cu fluxul de execuție să se facă la un moment ulterior de timp.

Asynchronous Procedure Call
Obiectele sunt unul dintre cele mai importante concepte în Windows 2000. Ele oferă o interfață uniformă și consistentă la toate resursele sistemului și structurile de date (în fapt structurile de date se identifică cel mai adesea cu alte tipuri de resurse cum ar fi procesele, threadurile, semafoarele, etc).

Obiectele de pe nivelul kernelului se împart în două mari categorii :


 * obiecte cu rol de control cum ar fi procesele, threadurile, etc
 * obiecte cu rol de expeditor cum ar fi semafoarele, mutexurile, etc

Apelurile de proceduri asincrone (APC) sunt obiecte cu rol de control definite în kernel care reprezintă o procedură ce este apelată asincron. APC-urile sunt dependente de contextul threadului în care se află; mai exact, ele sunt puse într-o coadă specifică fiecărui thread. Există trei tipuri de APC-uri în kernel:


 * 1) User APC - utilizate de anumite servicii de sistem asincrone pentru a permite aplicațiilor din user space sau subsisteme protejate să sincronizeze execuția threadului cu încheierea unei operații sau a unui eveniment cum ar fi expirarea unui timer. Ele sunt invalidate, adică sunt puse în coada threadului din user mode, dar nu sunt executate, decât la momente bine definite în program. Mai exact, ele pot fi executate doar când o aplicație sau subsistem protejate au apelat un serviciu de așteptare.
 * 2) Kernel Apc - sunt foarte asemănătoare cu user APC-urile cu excepția faptului că ele sunt executabile, adică intră direct în execuție cu excepția cazului când firul de executie rula deja un kernel APC. Ele pot fi preemptate de APC-urile special din kernel mode.
 * 3) APC-urile speciale din kernel mode - ele nu pot fi blocate decât dacă sistemul rulează la un nivel crescut de IRQ. APC-urile de acest tip sunt executate pe nivelul APC_LEVEL_IRQL . Sunt utilizate pentru a forța un thread să execute o funcție în contextul său.

Echivalentul semnalelor ar fi ultimele două tipuri de APC-uri, dar nu pot fi utilizate decât în kernel mode. În continuare ne vom ocupa de APC-urile din user mode.

O aplicație poate adăuga la coada de APC-uri apelând funcția: Parametri

Tipul PAPCFUNC este un pointer la o funcție de tipul specificat mai jos. CALLBACK este un alias pentru __stdcall (directivă pentru compilator care precizează că argumentele vor fi preluate de la dreapta la stânga și stiva va fi curățată de apelat și nu de apelant). Parametri
 * pfnAPC - [in] Pointer la funcția APC ce va fi apelată când firul de execuție respectiv efectuează o operație de așteptare alertabilă.
 * dwParam - [in] Date pasate funcției utilizând parametrul dwData funcției QueueUserAPC.
 * hThread - [in] Handle(Identificator) pentru thread. Identificatorul trebuie să aibă dreptul de acces THREAD_SET_CONTEXT.
 * dwData - [in] Valoarea pasată funcției spre care pointează pfnAPC.

Pentru ca APC-urile sa poată rula un thread trebuie să fie într-o stare alertabilă. Atunci când intră într-o astfel de stare, firul de execuție va fi suspendat și se va continua execuția cu APC-uri până când coada de APC-uri specifică lui e goală.

Un thread poate intra într-o stare alertabilă prin apelarea uneia din cele patru funcții de așteptare alertabile :


 * WaitForSingleObjectEx
 * WaitForMultipleObjectsEx
 * SignalObjectAndWait
 * MsgWaitForMultipleObjectsEx

fie apelând funcția SleepEx cu prototipul : Parametri


 * dwMilliseconds - [in] intervalul minim de timp pentru care execuția va fi întreruptă, în milisecunde. Valoarea zero impune ca firul să-și cedeze cuanta de timp rămasă oricărui alt thread cu prioritate egală care este în starea ready (gata să ruleze) . Dacă nu există un astfel de thread funcția revine imediat și firul își continuă execuția. Valoarea INFINITE indică faptul că funcția va aștepta indefinit un eveniment care să o scoată din această stare.
 * bAlertable - [in] dacă acest parametru are valoarea FALSE, funcția nu revine decât atunci când perioada de time-out a expirat. Thread-ul nu este într-o stare alertabilă deci APC-urile nu pot interveni în fluxul de execuție al programului. Dacă parametrul are valoarea TRUE, atunci funcția poate reveni dacă intervalul de timp specificat a expirat, dacă intervine o funcție callback de încheiere I/O și firul care a apelat funcția este același care a apelat o funcție extinsă de I/O ( ReadFileEx WriteFileEx), sau un APC a fost pus în coada firului.

Un exemplu de folosire a APC-urilor îl reprezintă funcțiile de citire/scriere asincronă ( ReadFileEx WriteFileEx). În acest caz trebuie să ne asigurăm că operația de I/O inițiată a fost completată intrând într-o stare alertabilă pentru a permite rutinei să se execute. Un alt exemplu îl constituie obiectele de tip ''waitable timer. ''

Un alt caz este SetWaitableTimer : În acest caz rutina APC care este pusă în coadă este de tipul TIMERAPCROUTINE : Parametri :


 * lpArgToCompletionRoutine - [in] valoarea pasată funcției folosind parametrul lpArgToCompletionRoutine al funcției SetWaitableTimer.
 * dwTimerLowValue - [in] partea nesemnificativă a timpului UTC la care a fost semnalizat timerul. Corespunde câmpului dwLowDateTime al structurii FILETIME.
 * dwTimerHighValue - [in] partea semnificativă a timpului UTC la care a fost semnalizat timerul. Corespunde câmpului dwHighDateTime al structurii FILETIME.

Structura FILETIME este o valoare pe 64 de biți reprezentând numărul de intervale de 100 nanoseconde de la 1 Ianuarie 1601 (UTC). -->

<!--

Event Objects
Obiectele de tip eveniment (event) din sistemele de operarare Microsoft Windows sunt generate în mod explicit de către utilizator. Ele sunt de asemenea sincrone cu fluxul de execuție. Sunt utilizate pentru a aștepta apariția unui eveniment specificat de utilizator (de exemplu, așteptarea unui fir de execuție). Evenimentul considerat de către utilizator este un concept abstract, adică nu are sens decât pentru utilizator și nu se materializează în program.

Prin eveniment se va înțelege alternativ fie conceptul de eveniment, fie o instanță a acestui tip de obiecte. Operațiile definite pentru un eveniment sunt:


 * crearea unui eveniment
 * așteptarea unui eveniment
 * semnalarea apariției acelui tip de eveniment

Evident, aceste operații pot fi efectuate în măsura în care drepturile de acces asupra evenimentului o permit.O instanță a unui eveniment are asociată două stări : signaled (evenimentul s-a produs) și nonsignaled (evenimentul nu s-a produs încă).

După modul în care se comportă un eveniment din punct de vedere al comportamentului la semnalarea lui event-urile sunt de două tipuri :


 * evenimente care se resetează manual - când un astfel de eveniment este semnalat el va rămâne în această stare până când va fi resetat manual.Toate firele de execuție care așteptau ca acest eveniment să aibă loc folosind una funcțiile de așteptare pot să-și continue execuția.
 * evenimente care se resetează automat - când un astfel de eveniment este semnalat, el va rămâne în această stare până când un thread și numai unul este eliberat și simultan sistemul de operare resetează starea evenimentului ca fiind nesemnalat. Dacă niciun thread nu aștepta producerea evenimentului starea obiectului rămâne semnalată. Dacă așteptau mai multe unul singur este deblocat. Nu se garantează eliberararea threadurilor în ordinea în care acestea au intrat în starea de așteptare. Kernel APC-urile pot schimba această ordine.

Crearea unui eveniment
Crearea unui eveniment se face cu funcția CreateEvent : Parametri


 * lpEventAttributes - [in] pointer la o structură de tip SECURITY_ATTRIBUTES care determină dacă identificatorul pe care îl returnează funcția poate fi moștenit de procesele copil. Dacă pointerul este nul, identificatorul nu poate fi moștenit. Câmpul lpSecurityDescriptor specifică un descriptor de securitate pentru noul eveniment. Dacă lpEventAttributes este NULL, evenimentul primește un descriptor de securitate implicit (aceleași drepturi ca și creatorul).
 * bManualReset - [in] Acest parametru specifică tipul evenimentului : cu resetare manuală sau automată.
 * bInitialState - [in] Dacă argumentul are valoarea TRUE, starea inițială a obiectului este signaled; altfel, este nonsignaled.
 * lpName - [in] pointer la un șir de caractere care se termină cu NULL care va specifica numele obiectului. Numele este limitat la MAX_PATH caractere. Compararea de nume este senzitivă la litere mari. Dacă lpName se potrivește cu numele vreunui eveniment, această funcție cere dreptul de acces EVENT_ALL_ACCESS. În acest caz, câmpurile bManualReset și bInitialState sunt ignorate deoarece au fost deja setate de către procesul care a creat respectivul eveniment. Dacă parametrul lpEventAttributes e diferit de NULL, el determină dacă identificatorul poate fi moștenit, însă membrul lpSecurityDescriptor este ignorat. Dacă lpName este NULL, obiectul de tip eveniment este creat fără nume. Dacă lpName se potrivește cu numele altui tip de obiect cu nume (semafor, mutex, waitable timer, job, sau file-mapping object), funcția eșuează și GetLastError returnează ERROR_INVALID_HANDLE. Acest lucru se întâmplă deoarece aceste obiecte împart același spațiu de nume.

Așteptarea unui eveniment
Pentru a aștepta apariția unui eveniment(i.e., ca starea obiectului de tip eveniment să devină signaled) se pot folosi funcțiile de așteptare standard

Semnalarea apariției unui eveniment
Atunci când apare evenimentul cu care am asociat un obiect de tip eveniment putem semnaliza acest lucru (i.e., să setăm starea obiectului de tip eveniment ca signaled) folosind una din funcțiile : Parametri

Parametri
 * hEvent - [in] identificatorul unui obiect de tip eveniment. El trebuie să aibă dreptul de acces EVENT_MODIFY_STATE.


 * hEvent - [in] identificatorul unui obiect de tip eveniment. El trebuie să aibă dreptul de acces EVENT_MODIFY_STATE.

Funcția PulseEvent setează starea obiectului ca fiind signaled apoi o resetează la nonsignaled după ce eliberează un număr de threaduri potrivit cu tipul de eveniment (dacă este cu resetare manuală, vor fi eliberate toate care așteaptă - cu anumite excepție, altfel cel mult unul va fi eliberat).

Un fir de execuție care așteaptă după un obiect de sincronizare poate fi scos temporar din starea de așteptare de către un kernel APC și apoi readus în această stare după ce APC-ul este executat. Dacă apelul funcției PulseEvent se face chiar în timp ce threadul revine în starea de așteptare, el nu va fi eliberat din această stare deoarece această funcție eliberează doar firele care sunt în starea de așteptare la momentul apelării ei. De aceea, ea nu este sigură și nu ar trebuie folosită în aplicații.

Invalidarea unui eveniment
Invalidarea unui eveniment este de obicei necesară doar când obiectul de tip eveniment se resetează manual. Parametri


 * hEvent - [in] identificatorul unui obiect de tip eveniment. El trebuie să aibă dreptul de acces EVENT_MODIFY_STATE.

Drepturi de acces la un obiect de tip eveniment
Pentru a putea manipula un obiect de tip eveniment avem nevoie de un identificator cu drepturi de acces specifice fiecărei operații. Pentru a obține un identificator se pot folosi funcțiile CreateEvent (care poate fi utilizată pentru crearea unui nou eveniment sau deschiderea unuia deja existent) și OpenEvent (folosită numai pentru deschiderea unui obiect cu nume)''. Funcția OpenEvent ''are semnătura : Parametri


 * dwDesiredAccess - [in] tipul de acces dorit asupra obiectului de tip timer. Funcția eșuează dacă descriptorul de securitate al obiectului specificat nu permite accesul cerut de procesul apelant.
 * bInheritHandle - [in] specifică dacă identificatorul obiectului poate fi moștenit sau nu de către procesele copil.
 * lpEventName - [in] pointer la un șir de caractere terminat cu NULL. Şirul are maxim MAX_PATH caractere. Compararea între nume este senzitivă la literele mari.

Drepturile de acces posibile sunt :

-->
 * EVENT_ALL_ACCESS - sunt acordate toate drepturile posibile asupra acelui obiect.
 * EVENT_MODIFY_STATE - drepturi de modificare a stării obiectului de tip eveniment

Waitable Timer Objects
Un obiect de tipul waitable timer este un obiect de sincronizare a cărui starea este configurată ca semnalizată (signaled) atunci când timpul specificat se scurge. Există două tipuri de obiecte waitable timer ce pot fi create:


 * timer cu resetare manuală - un timer a cărui stare rămâne semnalizată până când un nou apel al funcției SetWaitableTimer setează un nou timp de așteptare;
 * timer cu resetare automată - un timer a cărui stare rămâne signaled până când un thread efectuează o operație de așteptare pe acel obiect.

Oricare dintre cele două tipuri de timere poate fi configurat ca un timer periodic. Un timer periodic este reactivat de fiecare dată când perioada de timp specificată expiră, până când timerul este setat din nou sau anulat. Operațiile care se pot efectua cu un timer sunt:
 * crearea unui timer: se realizează prin intermediul funcției CreateWaitableTimer
 * setarea unui timer: cu ajutorul funcției SetWaitableTimer
 * anularea: cu ajutorul funcției CancelWaitableTimer
 * așteptarea: folosind WaitForSingleObject

În secvența de cod de mai jos, se folosește un timer pentru afișarea unui mesaj după 5 secunde:

<!--

Crearea unui obiect de tip timer
Crearea unui timer se face cu funcția CreateTimer : Parametri


 * lpTimerAttributes - [in] pointer la o structură de tip SECURITY_ATTRIBUTES care determină dacă identificatorul pe care îl returnează funcția poate fi moștenit de procesele copil. Dacă pointerul este nul, identificatorul nu poate fi moștenit. Câmpul lpSecurityDescriptor specifică un descriptor de securitate pentru noul timer. Dacă lpTimerAttributes este NULL, obiectul primește un descriptor de securitate implicit (aceleași drepturi ca și creatorul).
 * bManualReset - [in] Acest parametru specifică tipul evenimentului : cu resetare manuală sau automată.
 * lpTimerName - [in] pointer la un șir de caractere care se termină cu NULL care va specifica numele obiectului. Numele este limitat la MAX_PATH caractere. Compararea de nume este senzitivă la litere mari. Dacă lpName se potrivește cu numele vreunui eveniment, această funcție cere dreptul de acces EVENT_ALL_ACCESS. În acest caz, câmpurile bManualReset și bInitialState sunt ignorate deoarece au fost deja setate de către procesul care a creat respectivul eveniment. Dacă parametrul lpEventAttributes e diferit de NULL, el determină dacă identificatorul poate fi moștenit, însă membrul lpSecurityDescriptor este ignorat. Dacă lpName este NULL, obiectul de tip eveniment este creat fără nume. Dacă lpName se potrivește cu numele altui tip de obiect cu nume (semafor, mutex, event, job, sau file-mapping object), funcția eșuează și GetLastError returnează ERROR_INVALID_HANDLE. Acest lucru se întâmplă deoarece aceste obiecte împart același spațiu de nume.

Setarea unui timer
Armarea unui timer se face folosind funcția Parametri

Structura FILETIME este o valoare pe 64 de biți reprezentând numărul de intervale de 100 nanoseconde de la 1 Ianuarie 1601 (UTC). Parametri : Structura FILETIME este o valoare pe 64 de biți reprezentând numărul de intervale de 100 nanoseconde de la 1 Ianuarie 1601 (UTC).
 * hTimer - [in] identificator pentru obiectul de tip timer. El trebuie să aibă dreptul de acces TIMER_MODIFY_STATE 
 * pDueTime - [in] timpul după care starea timerului trece în starea signaled, specificat în unități de 100 nanosecunde. Se va utiliza formatul descris de structura FILETIME. Valorile pozitive indică timpul absolut. Folosirea timpului bazat pe UTC deoarece sistemul de operare îl va considera ca atare. Valorile negative indică un moment de timp relativ (la cel actual). Finețea timpului depinde de capabilitățile hardware.
 * lPeriod - [in] perioada cu care se repetă timerul, specificată în milisecunde. Dacă valoarea este zero, timerul nu este periodic și devine signaled doar o singură dată. Dacă valoarea este mai mare decât zero timerul este periodic. Un timer periodic se reactivează automat de fiecare dată când timpul specificat de perioada expiră, până când timerul este anulat folosind funcția CancelWaitableTimer sau resetat folosind SetWaitableTimer. Nu sunt permise valori negative pentru perioadă. Folosirea lor face ca funcția să eșueze.
 * pfnCompletionRoutine - [in] pointer la o funcție care urmează să se execute de fiecare dată când timerul devine signaled.Funcția trebuie definită în aplicația care o utilizează și trebuie să fie de tipul PTIMERAPCROUTINE :
 * lpArgToCompletionRoutine - [in] valoarea pasată funcției folosind parametrul lpArgToCompletionRoutine al funcției SetWaitableTimer.
 * dwTimerLowValue - [in] partea nesemnificativă a timpului UTC la care a fost semnalizat timerul. Corespunde câmpului dwLowDateTime al structurii FILETIME.
 * dwTimerHighValue - [in] partea semnificativă a timpului UTC la care a fost semnalizat timerul. Corespunde câmpului dwHighDateTime al structurii FILETIME.
 * lpArgToCompletionRoutine - [in] pointer la o structură care este pasată funcției.
 * fResume - [in] dacă parametrul are valoarea TRUE, restaurează un sistem care era în modul de operare economic când starea timerului devine signaled. Dacă sistemul nu suportă o restaurare, apelul iese cu succes, dar GetLastError returneză ERROR_NOT_SUPPORTED.

În mod normal, un timer abia creat este inactiv. El poate fi activat folosind ''SetWaitableTimer. ''Dacă această funcție este apelată când timerul era deja activ, el va oprit și pornit din nou.

Anularea unui timer
Operația de anulare are sens numai asupra unui timer activ (Dacă timerul este inactiv anularea nu are niciun efect). Ea poate realizată folosind funcția : unde hTimer este un identificator pentru obiectul de tip timer pentru care se cere anularea.

Funcția CancelWaitableTimer nu schimbă starea timerului (signaled sau nonsignaled).Ea oprește timerul înainte de a fi setat în starea signaled și anulează eventualele APC-uri. De accea, threadurile care efectuau o operație de așteptare pe un obiect de tip timer rămân în această stare până când fie operația eșuează în urma unui timeout, fie până când timerul este reactivat și starea lui setată ca fiind signaled. Dacă timerul era deja în starea signaled, el va rămâne în această stare.

Așteptarea după un timer
Pentru a aștepta ca un timer să devină signaled se pot folosi funcțiile de așteptare standard. -->

Quiz
Pentru autoevaluare răspundeți la întrebările din acest quiz.

Prezentare
Pentru a urmări mai ușor noțiunile expuse la începutul laboratorului folosiți această prezentare. odp|pdf

Exerciții de laborator
Folosiți arhiva de sarcini a laboratorului.

Linux

 * Atenție: La folosirea sigaction, veți inițializa, în general, câmpul <tt>sa_flags</tt> al structurii <tt>struct sigaction</tt> la 0. Inițializarea la <tt>SA_RESETHAND</tt> restaurează comportamentul implicit (ignorare, terminarea procesului etc.)


 * 1) (2 puncte) Intrați în directorul <tt>1-nohup</tt>.
 * 2) * Realizați un program denumit <tt>mynohup</tt> care simulează comanda nohup.
 * 3) * Programul primește ca prim parametru numele unei comenzi de executat.
 * 4) * Restul parametrilor reprezintă argumentele cu care trebuie invocată comanda respectivă; lista de argumente poate fi nulă.
 * 5) * Programul executat de <tt>mynohup</tt> trebuie să nu fie înștiințat de închiderea terminalului la care era conectat.
 * 6) * Analizați conținutul fișierului <tt>mynohup.c</tt>.
 * Hint:
 * 1) ** Pentru testare procesul trebuie rulat în background <tt>./mynohup command args &</tt>
 * 2) ** Va trebui să ignorați semnalul <tt>SIGHUP</tt> livrat de shell procesului în momentul încheierii sesiunii curente.
 * 3) * Dacă fișierul standard de ieșire era legat la un terminal acesta trebuie redirectat în fișierul <tt>NOHUP_OUT_FILE</tt>.
 * Hint:
 * 1) ** Folosiți apelul isatty.
 * 2) * Pentru testare rulați <tt>./mynohup sleep 100 &</tt>.
 * 3) ** După rulare închideți sesiunea de shell curentă.
 * 4) ** Dintr-o altă consolă rulați <tt>ps -ef | grep sleep</tt>. Cine este noul părinte al procesului?
 * 5) ** Consultați secțiunea Tratarea semnalelor și secțiunile Înlocuirea imaginii unui proces și Redirectări în Linux din laboratoarele precedente.
 * 6) (1.5 puncte) Intrați în directorul <tt>2-zombie</tt>.
 * 7) * Urmăriți conținutul fișierelor <tt>mkzombie.c</tt> și <tt>nozombie.c</tt>.
 * 8) * Realizați două programe denumite <tt>mkzombie</tt> și <tt>nozombie</tt>.
 * 9) * Fiecare program va crea câte un proces copil nou care se va termina imediat după rulare apelând exit.
 * 10) * <tt>mkzombie</tt> va aștepta <tt>TIMEOUT</tt> secunde și va ieși.
 * 11) ** Din altă consolă rulați <tt>ps -eF | grep zombie</tt>.
 * 12) ** Observați faptul că procesul copil, deși nu mai rulează, apare în lista de procese ca <tt>&lt;defunct&gt;</tt> și are un pid (unic în sistem la acel moment).
 * 13) ** Observați că după moartea procesului părinte dispare și procesul zombie.
 * 14) * <tt>nozombie</tt> va aștepta <tt>TIMEOUT</tt> secunde și va ieși.
 * 15) ** Implementați, fără a folosi funcțiile de așteptare de tipul wait, <tt>nozombie</tt> astfel încât procesul său copil să nu treacă în starea de zombie.
 * Hints:
 * 1) *** Folosiți semnalul <tt>SIGCHLD</tt>. Informații găsiți în sigaction(2) și wait(2).
 * Hints:
 * 1) ** Consultați secțiunea Tratarea semnalelor și Crearea unui proces.
 * 2) (1.5 puncte) Intrați în directorul <tt>3-askexit</tt>.
 * 3) * Urmăriți conținutul fișierului <tt>askexit.c</tt>.
 * 4) * Realizați un program denumit <tt>askexit</tt> care face busy waiting afișând la consolă numere consecutive.
 * 5) * Programul va intercepta semnalele generate de <tt>CTRL+\</tt>, <tt>CTRL+C</tt> și <tt>SIGUSR1</tt> (folosiți comanda <tt>kill</tt>).
 * 6) * Pentru fiecare semnal primit va întreba utilizatorul dacă dorește să încheie execuția sau nu.
 * Hints:
 * 1) ** Deși funcțiile <tt>printf</tt>, <tt>scanf</tt>, etc. nu trebuie folosite în handlere de semnale, le puteți folosi în cadrul acestui exercițiu pentru a nu complica inutil problema.
 * 2) ** Consultați secțiunea Tratarea semnalelor.
 * 3) (1.5 puncte) Intrați în directorul <tt>4-timer</tt>.
 * 4) * Urmăriți conținutul fișierului <tt>mytimer.c</tt>.
 * 5) * Folosiți setitimer pentru a afișa timpul curent la fiecare <tt>TIMEOUT</tt> secunde.
 * Hints:
 * 1) ** Completați funcțiile din fișierul sursă.
 * 2) ** Folosiți ctime și time pentru afișarea timpului curent.
 * 3) *** ctime adaugă un caracter new-line (<tt>\n</tt>) la sfârșitul șirului întors.
 * 4) ** Folosiți sigsupend pentru a aștepta semnale.
 * 5) *** Obțineți, folosind sigprocmask, masca procesului curent și transmiteți-o ca argument către sigsupend.
 * 6) ** Urmăriți secțiunile Timere în Linux și Așteptarea unui semnal

Windows
Folosiți header-ul <tt>../util/util.h</tt> și funcția de tratare a unei erori (<tt>doError</tt>).


 * 1) (1 puncte) Intrați în directorul <tt>1-ignore</tt>.
 * 2) * Urmăriți conținutul fișierului <tt>myignore.c</tt>.
 * 3) * Configurați procesul curent să ignore închiderea consolei (semnalul <tt>CTRL_CLOSE_EVENT</tt>).
 * 4) * Rulați programul și închideți consola. Ce mesaj primiți?
 * Hints:
 * 1) ** Consultați secțiunea Evenimente de la consolă.
 * 2) (1.5 puncte) Intrați în directorul <tt>2-askexit</tt>.
 * 3) * Urmăriți conținutul fișierului <tt>askexit.c</tt>.
 * 4) * Realizați un program denumit care face face sleep la nivel de secundă și afișează pe ecran numere în ordine crescătoare.
 * 5) * Programul va intercepta semnalele generate de CTRL+C, CTRL+Break.
 * 6) * Pentru fiecare semnal primit va întreba utilizatorul dacă dorește să încheie execuția sau nu.
 * Hints:
 * 1) ** Consultați secțiunea Evenimente de la consolă.
 * 2) (2 puncte) Intrați în directorul <tt>3-timer</tt>.
 * 3) * Urmăriți conținutul fișierului <tt>mytimer.c</tt>.
 * 4) * Realizați un program care afișează data curentă la fiecare <tt>TIMEOUT</tt> secunde.
 * Hints:
 * 1) ** Folosiți componenta <tt>QuadPart</tt> a tipului <tt>LARGE_INTEGER</tt> pentru specificarea timeoutului. Timeoutul este negativ și expiră la atingerea valorii 0.
 * 2) ** Al treilea argument al SetWaitableTimerObject este timpul (în milisecunde) după care se va livra primul semnal de timer.
 * 3) * Folosiți un handler APC pentru tratarea timer-ului și afișarea mesajului:
 * Hints:
 * 1) ** Folosiți exemplul de utilizare a timer-elor cu APC-uri din documentația MSDN.
 * 2) ** Ignorați warning-urile de compilare.
 * Hints:
 * 1) ** Completați funcțiile din fișierul sursă.
 * 2) ** Folosiți ctime și time pentru afișarea timpului curent.
 * 3) *** ctime adaugă un caracter new-line (<tt>\n</tt>) la sfârșitul șirului întors.
 * 4) ** Folosiți SleepEx și argumentele <tt>INFINITE</tt> pentru a aștepta nedefinit și <tt>TRUE</tt> pentru a forța intrarea procesului într-o stare alertabilă (care să declanșeze rularea APC-ului).
 * 5) ** Urmăriți secțiunea Waitable Timer Objects.

Soluții

 * Soluții exerciții laborator 6

Note

 * 1) pagina gnu despre semnale aici
 * 2) mai multe informații despre threaduri și semnale aici