Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sde:laboratoare:06_ro_python [2020/03/25 12:16]
ioana_maria.culic
— (current)
Line 1: Line 1:
-====== Laborator 6 - IPC - Comunicare Inter-Procese ====== 
-===== Materiale ajutătoare ===== 
  
-    * [[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab04-refcard.pdf| lab05-refcard.pdf]] 
- 
-===== Link-uri către secțiuni utile ===== 
-  * [[#Pipe-uri anonime în Linux|Pipe-uri anonime în Linux]] 
-  * [[#Pipe-uri cu nume în Linux|Pipe-uri cu nume în Linux]] 
-  * [[#​Generarea semnalelor|Generarea semnalelor]] 
-  * [[#​Transmiterea și primirea semnalelor| Transmiterea și primirea semnalelor]] 
-  * [[#Tipuri standard de semnale | Tipuri standard de semnale]] 
-  * [[#Mesaje pentru descrierea semnalelor | Mesaje pentru descrierea semnalelor]] 
-  * [[#Măști de semnale. Blocarea semnalelor | Măști de semnale. Blocarea semnalelor]] 
-  * [[#Tratarea semnalelor | Tratarea semnalelor]] 
-  * [[#​Semnalarea proceselor | Semnalarea proceselor]] 
-  * [[#​Așteptarea unui semnal | Așteptarea unui semnal]] 
-  * [[#Timere în Linux| Timere în Linux]] 
- 
-===== Pipe-uri in Linux ===== 
- 
-==== Pipe-uri anonime în Linux ==== 
- 
-Pipe-ul este un mecanism de comunicare unidirecțională între două procese. În majoritatea implementărilor de UNIX, un pipe apare ca o zonă de memorie de o anumită dimensiune în spațiul nucleului. Procesele care comunică printr-un pipe anonim trebuie să aibă un grad de rudenie; de obicei, un proces care creează un pipe va apela după aceea ''​fork'',​ iar pipe-ul se va folosi pentru comunicarea între părinte și fiu. În orice caz, procesele care comunică prin pipe-uri anonime nu pot fi create de utilizatori diferiți ai sistemului. 
- 
- 
-Apelul de sistem pentru creare este [[http://​linux.die.net/​man/​2/​pipe|pipe]]. In python putem folosi doua functii pentru a crea un pipe: [[https://​docs.python.org/​3/​library/​os.html#​os.pipe|os.pipe()]] si [[https://​docs.python.org/​3/​library/​os.html#​os.pipe2|os.pipe2(flags)]] ​ 
- 
-{{ so:​laboratoare:​pipe.png?​370| Exemplu de utilizare - procesul părinte transmite date procesului copil prin pipe}} 
-<code python> 
-import os 
-(r, w) = os.pipe() 
-</​code>​ 
- 
- 
-Tuplul ''​(r,​ w)''​ conține după execuția funcției 2 descriptori de fișier: 
-  *''​r'',​ deschis pentru citire; ​ 
-  *''​w'',​ deschis pentru scriere; 
- 
-/* Se consideră că ieșirea lui ''​w''​ este intrare pentru ''​r''​. */ 
- 
-O alta varianta de a scrie este 
-<code python> 
-import os 
-p = os.pipe() 
-# use p[0] and p[1] 
-</​code>​ 
- 
-Functia ''​os.pipe2(flags)''​primeste ca parametru optiunile spcifice pipe-ului. 
- 
-//​Mnemotehnică//:​ ''​STDIN_FILENO''​ este 0 (citire), ''​STDOUT_FILENO''​ este 1 (scriere). 
- 
-**Observații**:​ 
-  *citirea/​scrierea din/în pipe-uri este atomică dacă nu se citesc/​scriu mai mult de ''​PIPE_BUF''​((limită globală setată implicit pe Linux la 4096 bytes)) octeți. ​ 
-  *citirea/​scrierea din/în pipe-uri se realizează cu ajutorul funcțiilor ''​os.read()''/''​os.write()''​. ​ 
- 
-Majoritatea aplicațiilor care folosesc pipe-uri închid în fiecare dintre procese capătul de pipe **neutilizat** în comunicarea unidirecțională. Dacă unul dintre descriptori este închis se aplică regulile: 
-  *o citire dintr-un pipe pentru care descriptorul de **scriere** a fost închis, după ce toate datele au fost citite, va returna ''​0'',​ ceea ce indică sfârșitul fișierului. Descriptorul de scriere poate fi duplicat astfel încât mai multe procese să poată scrie în pipe. De regulă, în cazul pipe-urilor anonime există doar două procese, unul care scrie și altul care citește, pe când în cazul fișierelor pipe cu nume (FIFO) pot exista mai multe procese care scriu date. 
-  *o scriere într-un pipe pentru care descriptorul de **citire** a fost închis cauzează generarea semnalului ''​SIGPIPE''​. Dacă semnalul este captat și se revine din rutina de tratare, funcția de sistem ''​write''​ returnează eroare și variabila ''​errno''​ are valoarea ''​EPIPE''​. ​ 
- 
-<note important>​ Cea mai frecventă **greșeală**,​ relativ la lucrul cu pipe-urile, provine din neglijarea faptului că nu se trimite ''​EOF''​ prin pipe (citirea din pipe nu se termină) decât dacă sunt închise **TOATE** capetele de scriere din **TOATE** procesele care au deschis descriptorul de scriere în pipe (în cazul unui ''​fork'',​ nu uitați să închideți capetele pipe-ului în procesul părinte). </​note>​ 
- 
-Alte funcții utile: [[https://​docs.python.org/​3/​library/​os.html#​os.popen|os.popen]],​ [[https://​docs.python.org/​3/​library/​os.html#​os.pclose|os.pclose]]. 
- 
-==== Pipe-uri cu nume în Linux ==== 
- 
-Elimină necesitatea ca procesele care comunică să fie înrudite. Astfel, fiecare proces își poate deschide pentru citire sau scriere fișierul pipe cu nume (FIFO), un tip de fișier special, care păstrează în spate caracteristicile unui pipe. Comunicația se face într-un sens sau în ambele sensuri. Fișierele de tip FIFO pot fi identificate prin litera ''​p''​ în primul câmp al drepturilor de acces (''​ls -l''​). 
- 
-Apelul de bibliotecă pentru crearea pipe-urilor de tip FIFO este [[https://​docs.python.org/​3/​library/​os.html#​os.mkfifo|os.mkfifo]]:​ 
- 
-<code python> 
-import os 
-os.mkfifo(path,​ mode); 
-</​code>​ 
- 
-După ce pipe-ul FIFO a fost creat, acestuia i se pot aplica toate funcțiile pentru operații obișnuite pentru lucrul cu fișiere: ''​open'',​ ''​close'',​ ''​read'',​ ''​write''​. 
- 
-Modul de comportare al unui pipe FIFO după deschidere este afectat de flagul ''​O_NONBLOCK'':​ 
-<spoiler Detalii despre flagul O_NONBLOCK>​ 
-  *dacă ''​O_NONBLOCK''​ nu este specificat (cazul normal), atunci un ''​open''​ pentru citire se va bloca până când un alt proces deschide același FIFO pentru scriere. Analog, dacă deschiderea este pentru scriere, se poate produce blocare până când un alt proces efectuează deschiderea pentru citire. 
-  *dacă se specifică ''​O_NONBLOCK'',​ atunci deschiderea pentru citire revine imediat, dar o deschidere pentru scriere poate returna eroare cu ''​errno''​ având valoarea ''​ENXIO'',​ dacă nu există un alt proces care a deschis același FIFO pentru citire. ​ 
-</​spoiler>​ 
- 
- 
-Atunci când se închide ultimul descriptor de fișier al capătului de scriere pentru un FIFO, se generează un „sfârșit de fișier” – ''​EOF''​ – pentru procesul care citește din FIFO. 
- 
- 
-===== Semnale în Linux===== 
-În lumea reală, un proces poate cunoaște o multitudine de situații neprevăzute,​ care-i afectează cursul normal de execuție. Dacă procesul nu le poate trata, ele sunt pasate, 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. 
-<​note>​ Un semnal este o întrerupere software, în fluxul normal de execuție a unui proces. </​note>​ 
-Semnalele sunt un concept specific sistemelor de operare UNIX. Sistemul de operare le folosește pentru a semnala procesului apariția unor situații excepționale oferindu-i procesului posibilitatea de a reacț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. 
- 
-  * Dacă un proces dorește să ''​ignore''​ un semnal, sistemul de operare nu va mai trimite acel semnal procesului. 
- 
-  * Dacă un proces specifică faptul că dorește să ''​blocheze''​ un semnal, sistemul de operare nu va mai trimite semnalele de acel tip spre procesul în cauză, dar 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 de acel tip, dacă există vreun semnal în așteptare, acesta va fi trimis. 
-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 a semnalului nu este decis la primirea semnalului de către proces, ci se alege, în mod automat, din tabelă. ​ 
-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 nevalide sau nepermise, împărțire la zero etc. 
- 
-  * Un eveniment care nu este sincron, e numit ''​asincron''​. 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 acesta raportează diferite erori; 
- 
-  * fie de un ''​proces''​ - care-și poate trimite și singur semnale (semnalul va trece tot prin sistemul de operare). 
- 
-<note important>​ 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. </​note>​ 
- 
-Î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). 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, sistemul de operare verifică dacă stiva este plină. În acest caz, cererea eșuează, altfel semnalul este pus în stivă și operația se termină cu succes. Modul clasic de a trimite semnale este analog cu acesta (stiva are dimensiunea 1) cu excepția faptului că nu se oferă informații despre ajungerea la destinație a unui semnal. ​ 
- 
-Noțiunea de semnal este folosită pentru a indica alternativ fie un anumit tip de semnal, fie efectiv obiectele de acest tip.  
- 
-===== Generarea semnalelor===== 
-În general, evenimentele care generează semnale se încadrează în trei categorii majore: 
- 
-  * O ''​eroare''​ indică faptul că un program a făcut o operație nepermisă ș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 eroare. În general, erorile asociate cu anumite biblioteci sunt raportate prin întoarcerea unei valori speciale. 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 accesarea nevalidă a memoriei. 
- 
-  * Un ''​eveniment extern''​ este, în general, legat de I/O și de alte procese. Exemple: apariția de noi date de intrare, expirarea unui timer, terminarea execuției unui proces copil. 
- 
-  * O ''​cerere explicită''​ indică utilizarea unui apel de sistem, 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. De asemenea, semnalele pot fi generate în mod sincron și prin anumite cereri explicite trimise de un proces lui însuși. 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. Evenimentele externe generează semnale în mod asincron, la fel ca și cererile explicite trimise de 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 destinație. Însă, dacă acel tip de semnal este, în momentul de față, blocat, el ar putea rămâne în starea de așteptare nedefinit, până când semnalele de acel tip sunt deblocate. O dată deblocat acel tip de semnal, el va fi trimis imediat. ​ 
-Când semnalul a fost primit, fie imediat, fie cu întârziere,​ 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 
-  * specifice o funcție de tip **handler** 
-  * accepte **acțiunea implicită** pentru acel tip de semnal. 
-Programul își specifică alegerea utilizând funcția [[https://​docs.python.org/​3/​library/​signal.html#​signal.signal|signal.signal()]]. În timp ce handler-ul rulează, acel tip de semnal este în mod normal **blocat** (deblocarea se va face printr-o cerere explicită în handler-ul care tratează semnalul). 
-<spoiler Exemplu de folosire ''​signal''>​ 
-În codul de mai jos ne propunem să capturăm semnalele ''​SIGINT''​ și ''​SIGUSR1''​ și să facem o acțiune în cazul în care le recepționăm. ''​SIGINT''​ e recepționat atât folosind comanda ''​kill -SIGINT <​program>'',​ cât și prin trimiterea combinației de taste ''​CTRL+c''​ programului. 
-<code python> 
-import os 
-import signal 
- 
-  
-pid_t child1, child2; 
-int child1_pid; 
-  
-  
-def signal_handler(signum,​ frame): 
-    if signum == signal.SIGINT:​ 
-        print("​CTRL+C received in {} Exiting"​).format(os.getpid()) 
-        exit(0); 
-    elif signum == signal.SIGUSR1:​ 
-        print("​SIGUSR1 received. Continuing execution"​) 
-  
- 
-print("​Process {} started"​).format(os.getpid()) 
-  
-/* Semnale ca SIGKILL sau SIGSTOP nu pot fi prinse */ 
-try: 
-    signal.signal(SIGKILL,​ signal_handler) == SIG_ERR: 
-    printf("​\nYou shall not catch SIGKILL\n"​);​ 
-  
-    if(signal(SIGINT,​ signal_handler) == SIG_ERR) { 
-        printf("​Unable to catch SIGINT"​);​ 
-        exit(EXIT_FAILURE);​ 
-    } 
-  
-    if(signal(SIGUSR1,​ signal_handler) == SIG_ERR) { 
-        printf("​Unable to catch SIGUSR1"​);​ 
-        exit(EXIT_FAILURE);​ 
-    } 
-  
-  
-    printf("​Press CTRL+C to stop us\n"​);​ 
-  
-    while(1) { 
-        sleep(1); 
-    } 
-  
-    return 0; 
-} 
-</​code>​ 
-<note tip> Observați că semnalul ''​SIGKILL''​ nu poate fi handle-uit (''​kill -9 <​program>''​ sau ''​kill -SIGKILL <​program>''​). 
-</​note> ​ 
-</​spoiler>​ 
-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 niciodată, 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 semnale, care reprezintă evenimente fără consecințe majore, acțiunea implicită este să nu se facă nimic. ​ 
- 
-Când un semnal forțează terminarea unui proces, părintele procesului poate determina cauza terminării 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 a fost cauzată de un semnal, precum și tipul semnalului. Dacă un program pe care îl rulați din linia de comandă este terminat de un semnal, shell-ul afișează, 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 reprezintă o eroare de program, printr-o cerere explicită, și acesta termină procesul, fișierul este generat ca și cum semnalul ar fi fost generat de o eroare. ​ 
-<note important>​În cazul în care un semnal este trimis procesului, în timp ce acesta execută un apel de sistem **blocant**,​ procesul va suspenda apelul, va executa handler-ul de tratare a semnalului definit folosind ''​signal''​ și apoi fie operația va eșua (cu ''​errno''​ setat pe ''​EINTR''​),​ fie se va reporni operația. Sistemele System V se comportă ca în primul caz, cele BSD ca în cel de-al doilea. De la glibc v2 încoace, comportamentul este același ca și pe BSD, totul depinzând de definiția macrou-ului _BSD_SOURCE. Comportamentul poate fi controlat de către programator folosind ''​sigaction''​ cu flag-ul ''​SA_RESTART''​. </​note>​ 
-===== Tipuri standard de semnale===== 
-Această secțiune prezintă numele pentru diferite tipuri standard de semnale și descrie ce fel de evenimente indică. 
-<​note>​ Fiecare nume de semnal este o **macrodefiniție** care reprezintă,​ de fapt, un număr întreg pozitiv (numărul pentru acel tip de semnal). </​note>​ 
-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 este din cauza faptului că un număr pentru un tip de semnal poate **varia** de la un sistem la altul, dar numele lor sunt standard. Pentru lista completă de semnale suportate de un sistem se poate rula în linia de comandă: 
-<code bash> 
-$ 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 
-</​code>​ 
-Numele de semnale sunt definite în header-ul ''​signal.h''​ din Unix. În general, semnalele au roluri predefinite,​ dar acestea pot fi suprascrise de programator. 
-Cele mai cunoscute sunt următoarele semnale: 
-  * ''​SIGINT''​ - transmis la apăsarea combinației ''​CTRL+C'';​ 
-  * ''​SIGQUIT ''​- transmis la apăsarea combinației de taste ''​CTRL+\'';​ 
-  * ''​SIGSEGV''​ - transmis în momentul accesării unei locații nevalide de memorie, etc; 
-  * ''​SIGKILL''​ - **nu** poate fi ignorat sau suprascris. Transmiterea acestui semnal are ca efect terminarea procesului, indiferent de context. 
-===== Mesaje pentru descrierea semnalelor===== 
-Cel mai bun mod de a afișa un mesaj de descriere a unui semnal este utilizarea funcțiilor [[https://​docs.python.org/​3/​library/​signal.html#​signal.strsignal|strsignal]]. Aceasta funcție foloseste un număr de semnal pentru a specifica tipul de semnal care trebuie descris. Mai jos este prezentat un exemplu de folosire a ei: 
-<code python msg_signal.py>​ 
-import os 
-import signal 
- 
-sig = signal.strsignal(signal.SIGKILL) 
-print ("​signal {} is {}"​).format(signal.SIGKILL,​ sig) 
-</​code>​ 
-Pentru compilare și rulare secvența este: 
-<code bash> 
-so@spook$ python3 msg_signal.py ​ 
-signal 9 is Killed 
- 
-</​code>​ 
-<​note>​Aveti nevoie de Python 3.8 sau mai nou pentru a folosi acesta functie</​note>​ 
-===== Măști de semnale. Blocarea semnalelor===== 
-Pentru a putea efectua operații de blocare/​deblocare semnale avem nevoie să știm, la fiecare pas din fluxul de execuție, starea fiecărui semnal. Sistemul de operare are, de asemenea, nevoie de același lucru pentru a putea lua 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. 
-<​note>​ O **mască de semnale** are fiecare bit asociat unui tip de semnal. </​note>​ 
-Masca de biți este folosită de mai multe funcții, printre care și funcția [[https://​docs.python.org/​3/​library/​signal.html#​signal.pthread_sigmask|signal.pthread_sigmask]],​ folosită pentru schimbarea măștii de semnale a procesului curent. 
-<code python> 
-signal.pthread_sigmask(how,​ maks) 
-</​code>​ 
-Masca de semnale este un set de numere de semnal. Pentru a afisa masca curenta putem folosi functia astfel: ''​signal.pthread_sigmask(signal.SIG_BLOCK,​ [])''​. 
- 
-===== Semnalarea proceselor===== 
-Pentru transmiterea unui semnal, se poate folosi funcția [[https://​docs.python.org/​3/​library/​os.html#​os.kill|os.kill]]:​ 
-<code python> 
-import os 
-os.kill(pid,​ signo) 
-</​code>​ 
-Funcția trimite semnalul ''​signo''​ procesului cu identificatorul ''​pid''​. 
- 
-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 ''​kill''​. 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ă maxim ''​SIGQUEUE_MAX''​ semnale). ​ 
- 
-===== Așteptarea unui semnal===== 
-<spoiler Abordări pentru așteptarea unui semnal > 
-În cazul în care se utilizează semnalele pentru comunicare și/sau sincronizare,​ există, deseori, nevoie să se aștepte ca un anumit tip de semnal să-i sosească procesului în cauză. Un mod simplu de a realiza acest lucru este o buclă, a cărei condiție de ieșire ar fi setarea corespunzătoare a unei variabile. De exemplu: 
-<code python>​while (!signal_has_arrived) 
-</​code>​ 
-Principalul dezavantaj al abordării de mai sus (de tip //''​busy-waiting''//​) este timpul pe procesor pe care procesul considerat îl pierde în mod inutil. O variantă ar fi folosirea funcției [[https://​docs.python.org/​3/​library/​time.html#​time.sleep|time.sleep]]:​ 
-<code python> 
-while (!signal_has_arrived):​ 
-    time.sleep(1) 
-</​code>​ 
- 
-===== Exerciții ===== 
- 
-Pentru rezolvarea laboratorului,​ va rugam sa clonati [[https://​www.github.com/​upb-fils/​sde|repository-ul]]. daca il aveti deja, va rugam sa rulati ''​git pull''​. ​ 
- 
-==== Exercițiul 1 - pipe (2p) ==== 
- 
-Intrați în directorul ''​1-pipe'',​ aveti doua programe: ''​pipe.py''​ si ''​reader.py''​. 
- 
-=== 1a - Pipe si fork (1p) === 
-In fisierul ''​pipe.py''​ creati un pipe dupa care realizati un fork. In procesul parinte, inchideti capatul de citire al pipe-ului 
-si scrieti in pipe datele din //buffer//. 
- 
-In procesul copil, inchideti capatul de scriere al pipe-ului, cititi in buffer datele primite din pipe si afisati-le pe ecran. 
- 
-Urmariti liniile TODO 1. 
- 
-=== 1b - Pipe si exec === 
-In fisierul ''​reader.py'',​ cititi de la tastatura un text stocat in variabila //buffer// si afisati variabila. 
- 
-In fisierul ''​pipe.py'',​ modificati procesul copil astfel incat dupa inchiderea capatului de scriere in pipe, sa  
-redirecteze //stdin// catre capatul de citire al pipe-ului si sa execute (folosind una din functiile //exec//) 
-programul ''​reader.py''​. 
- 
-Urmariti luniile TODO 2. 
- 
-==== Exercițiul 2 - hitme (2p) ==== 
- 
-Intrați în directorul ''​2-hitme/''​ și analizați conținutul fișierului ''​hitme.py''​. Rulați programul. 
- 
-Folosiți comanda ''​kill -l''​ pentru a lista toate semnalele disponibile. Ce valoare are semnalul ''​SIGKILL''?​ 
-Într-o altă consolă trimiteți programului ''​hitme''​ semnale cu valori cuprinse între 20 și 25 astfel:<​code bash> 
- 
-Programul va afisa PID-ul sau, acesta va fi $PID. 
- 
-kill -20 $PID 
-kill -21 $PID 
-kill -22 $PID 
-kill -23 $PID 
-kill -24 $PID 
-kill -25 $PID 
-</​code>​ 
- 
- 
-==== Exercițiul 3 - Normal signals vs Real-Time signals (1p) ==== 
- 
-Intrați în directorul ''​3-signals''​ și urmăriți conținutul fișierului ''​signals.py''​. Programul numără de câte ori se apelează handlerul de semnal în cazul trimiterii semnalelor ''​SIGINT''​ și ''​SIGRTMIN''​ (34) 
- 
-Porniți într-o consolă programul ''​signals.py'':<​code bash>​python3 signals.py</​code>​ 
- 
-Pentru cazul semnalelor ''​normale'',​ într-o altă consolă rulați scriptul ''​send_normal.sh'':<​code bash>​./​send_normal.sh</​code>​ 
-Pentru semnalele ''​real-time'',​ într-o altă consolă rulați scriptul ''​send_rt.sh'':<​code bash>​./​send_rt.sh</​code>​ 
-Pentru a închide executabilul ''​signals''​ este trimis semnalul ''​SIGQUIT''​. De unde apare diferența? 
-Citiți din pagina de manual ''​man 7 signal''​ secțiunea "​Real-time signals"​ și revedeți secțiunea [[#Tipuri standard de semnale| Tipuri standard de semnale]]. 
- 
-<note tip>​Diferența între numărul semnalelor primite se datorează faptului că semnalele cu indecșii între ''​SIGRTMIN''​ și ''​SIGRTMAX''​ sunt semnale real time, prin urmare se garantează că ele ajung la destinație. Vezi [[http://​www.linuxprogrammingblog.com/​all-about-linux-signals?​page=9 | link]]. </​note>​ 
- 
-==== Exercițiul 4 - askexit (2p) ==== 
- 
-Intrați în directorul ''​4-askexit''​ și urmăriți codul sursă. Programul face busy waiting (while), afișând la consolă numere consecutive. 
- 
-Trebuie să completați programul pentru a intercepta semnalele generate de ''​CTRL+\'',​ ''​CTRL+C''​ și ''​SIGUSR1''​ (folosiți comanda ''​kill''​). Handler-ul asociat cu fiecare din semnale va fi ''​ask_handler''​. Pentru fiecare semnal primit, utilizatorul va fi întrebat dacă dorește să încheie execuția sau nu. 
- 
-Testați funcționalitatea programului. 
- 
-==== Exercițiul 5 - noint (1p) ==== 
- 
-Intrați în directorul ''​5-noint''​ și realizați un program, denumit ''​noint.py''​. Programul primește, 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 ''​noint.py''​ trebuie să nu fie înștiințat de primirea semnalului SIGINT (CTRL+C). Va trebui să ignorați semnalul ''​SIGINT'',​ livrat de shell procesului. ​ 
-   * Revedeți secțiunea despre [[#Tratarea semnalelor]].  ​ 
- 
-Pentru testare, rulați <code bash>​python3 noint.py sleep 120 &</​code>​ 
-   * Consultați secțiunea [[#Tratarea semnalelor]] și secțiunile [[sde:​laboratoare:​04_ro#​Înlocuirea imaginii unui proces in linux]] și [[sde:​laboratoare:​03_ro#​Redirectări]] din laboratoarele precedente. 
- 
-==== Exercițiul 6 - nohup (1p) ==== 
- 
-Intrați în directorul ''​6-nohup''​ și realizați un program, denumit ''​nohup.py'',​ care simulează comanda [[http://​linux.die.net/​man/​1/​nohup|nohup]]. Programul primește, 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 ''​nohup.py''​ trebuie să nu fie înștiințat de închiderea terminalului la care era conectat. Va trebui să ignorați semnalul ''​SIGHUP'',​ livrat de shell procesului, în momentul încheierii sesiunii curente. ​ 
-   * Revedeți secțiunea despre [[#Tratarea semnalelor]].  ​ 
- 
-Dacă fișierul standard de ieșire era legat la un terminal acesta trebuie redirectat într-un fișier ''​nohup.out''​. 
-   * Folosiți apelul [[http://​www.kernel.org/​doc/​man-pages/​online/​pages/​man3/​isatty.3.html|isatty]]. ​ 
- 
-Pentru testare, rulați <code bash>​python3 nohup.py sleep 120 &</​code>​ 
-După rulare închideți sesiunea de shell curentă: fie trimițând un semnal SIGHUP, fie folosind iconița '​X'​ din partea dreaptă a ferestrei. 
- 
-Dintr-o altă consolă rulați respectiv <code bash>ps -ef | grep sleep </​code>​ 
-Cine este noul părinte al procesului? 
-   * Consultați secțiunea [[#Tratarea semnalelor]] și secțiunile [[sde:​laboratoare:​04_ro#​Înlocuirea imaginii unui proces in linux]] și [[sde:​laboratoare:​03_ro#​Redirectări]] din laboratoarele precedente. 
-Folosirea comenzii ''​exit'',​ fie combinația de taste ''​Ctrl-d''​ nu va trimite un semnal SIGHUP procesului de sleep; puteți testa utilizând ''​sleep 120 &'',​ inchideți shell-ul curent utilizând una din cele 2 metode, iar după verificați că procesul înca rulează. 
-==== Exercițiul 7 - zombie (1p) ==== 
- 
-Intrați în directorul ''​7-zombie''​ și urmăriți conținutul fișierelor ''​zombie.py''​ și ''​nozombie.py''​. Fiecare program va crea câte un proces copil nou, care doar va apela ''​exit''​. 
- 
-Implementați ''​zombie.py''​ fără a aștepta copilul creat să se termine. Procesul părinte va aștepta ''​TIMEOUT''​ secunde și va ieși (urmăriți //​TODO//​-urile). 
- 
-Din altă consolă rulați:<​code bash> 
-ps -eF | grep python3 
-</​code>​ 
- 
-Observați faptul că procesul copil, deși nu mai rulează, apare în lista de procese ca ''<​defunct>''​ și are un pid (unic în sistem la acel moment). De asemenea, observați că, după moartea procesului părinte, dispare și procesul zombie. ​ 
- 
-Implementați ''​nozombie''​ fără a folosi funcțiile de așteptare de tipul ''​wait'',​ astfel încât procesul copil să nu treacă în starea de zombie. ''​nozombie''​ va aștepta ''​TIMEOUT''​ secunde și va ieși. Folosiți semnalul ''​SIGCHLD''​ (informații găsiți în [[https://​docs.python.org/​3/​library/​signal.html#​signal.signal|signal()]] și [[https://​docs.python.org/​3/​library/​os.html#​os.waitpid|waitpid()]]). Consultați,​ de asemenea, secțiunile [[#Tratarea semnalelor]] și [[sde:​laboratoare:​04_ro#​Crearea unui proces in linux]]. ​ 
-<note tip> 
-Dacă părintele ignoră în mod explicit semnalul SIGCHLD prin setarea handler-ului la SIG_IGN (în loc să ignore semnalul în mod implicit) informația despre exit status al copiilor va fi aruncată iar copiii nu vor deveni procese zombie. 
-</​note>​ 
- 
-<​hidden>​ 
-===== Solutii ===== 
-[[https://​github.com/​UPB-FILS/​sde/​tree/​master/​tp05|Solutii]] 
-</​hidden>​ 
sde/laboratoare/06_ro_python.1585131389.txt.gz · Last modified: 2020/03/25 12:16 by ioana_maria.culic
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0