Biologie | Chimie | Didactica | Fizica | Geografie | Informatica | |
Istorie | Literatura | Matematica | Psihologie |
Pentru controlul proceselor exista cateva apeluri sistem, care vor fi explicate in continuare. In urmatoarele randuri se prezinta succint apelurile sistem implicate in controlul proceselor, urmand ca ele sa fie detaliate pe parcurs. Apelul sistem fork creaza un nou proxes, apelul exit termina executia unui proces si apelul wait permite procesului parinte sa-si sincronizeze executia cu terminarea unui proces fiu. Semnalele informeaza procesele despre evenimente asincrone. Apelul sistem exec permite proceselor lansarea in executie a unui nou program, suprascriindu-se spatiul de adrese al procesului cu imaginea executabila a unui fisier. Apelul sistem brk permite unui proces sa aloce mai dinamic mai multa memorie, similar, sistemul permite si cresterea dinamica a stivei utilizator.
Unicul mod in care se pot crea procese noi in sistemul de operare UNIX este prin apelul sistem fork. Procesul care a apelat fork este numit proces parinte, iar procesul nou creat este numit proces fiu. Sintaxa pentru apelul sistem fork este :
pid = fork();
La intoarcerea din apelul sistem fork, cele doua procese au o copie identica a contextului lor de nivel utilizator, cu exceptia valorii de intoarcere pid. In procesul parinte, pid va avea valoarea corespunzatoare identificatorului procesului fiu, in procesul fiu, pid va avea valoarea 0.
Nucleul face urmatoarea secventa de actiuni pentru fork :
Aloca o intrare in tabela de procese noului proces.
Aloca un identificator unic pentru noul proces.
Face o copie logica a contextului procesului parinte. Deoarece unele portinui ale unui proces, ca si regiunile de text, pot fi partajate intre procese, nucleul cateodata poate sa incrementeze contorul de referinta la o regiune in loc sa il copieze intr-un nou loc in memoria fizica.
Incrementeaza contorii din tabelele de fisiere si inoduri, pentru fisierele asociate procesului.
Intoarce identificatorul procesului fiu, procesului parinte, si valoarea 0, procesului fiu.
In urmatoarea figura se ilustreaza intr-un mod grafic modul de creare a unui proces fiu.
Fig. 7.4-
Exemplu :
Urmatorul program este un exemplu in care se ilustreaza modul de partajare a accesului la fisiere dupa apelul sistem fork. Programul se apeleaza cu doi parametri, numele unui fisier existent si numele unui nou fisier care va fi creat. Procesul deschide fisierul existent, creaza noul fisier si creaza un proces fiu prin apelul lui fork. Intern, nucleul efecueaza copierea contextului procesului parinte pentru procesul fiu. Fiecare proces se executa in spatii de adresare diferite si fiecare poate accesa copiile private ale variabilelor globale fdrd, fdwt si c si copiile private ale variabilelor de pe stiva argc si argv, dar niciuna nu poate accesa variabilele altui proces. Deoarece nucleul a copiat zona u area a procesului original, procesului fiu, in timpul apelului fork, procesul fiu mosteneste accesul la fisierele parintelui (doar la acelea care erau deschise in momentul apelului fork), folosind aceeasi descriptori de fisiere.
Procesele parinte si fiu apeleaza functia rdwrt, independent, si executa un ciclu, citind un bzte din fisierul sursa si scriind-ul in fisierul destinatie. Functia rdwrt se termina cand apelul sistem read intalneste sfarsitul de fisier. Nucleul a incrementat contorul din tabela de fisiere a fisierelor sursa si destinatie si descriptorii de fisiere din ambele procese se refera la aceleasi intrari in tabela de fisiere. Adica descriptorii fdrd pentru ambele procese se refera la intrarea din tabela de fisiere pentru fisierul sursa, iar descriptorii fdwr pentru ambele procese se refera la intrarea din tabela de fisiere pentru fisierul destinatie. Astfel, cele doua procese nu vor citi sau scrie niciodata la aceleasi valori de deplasament in cadrul fisierelor, deoarece nucleul incrementeaza deplasamentul dupa fiecare citire respectiv scriere. Desi procesele aparent copiaza fisierul sursa cu viteza dubla, deoarece isi impart lucrul, continutul fisierului destinatie va depinde de ordinea in care nucleul planifica spre executie procesele. Daca planifica procesele astfel incat ei alterneaza executia apelurilor sistem, sau chiar daca alterneaza executia perechilor de apeluri sistem read-write, continutul fisierului destinatie ar fi identic cu cel al fisierului sursa. Dar se poate considera si urmatorul scenariu, unde procesele sunt pe cale sa citeasca urmatoarea secventa de doua caractere 'ab' din fisierul sursa. Presupunem ca procesul parinte citeste caracterul 'a' si nucleul face o schimbare de context pentru a executa procesul fiu, inainte ca procesul parinte sa poata scrie ce a citit. Daca procesul fiu citeste caracterul 'b' si apuca sa il si scrie in fisierul destinatie inainte ca procesul parinte sa fi fost replanificat, atunci fisierul destinatie nu va contine secventa 'ab' ci secventa 'ba'. Nucleul nu garanteaza rata relativa de executie a proceselor.
#include <fcntl.h>
int fdrd, fdwt;
char c;
main(int argc, char *argv[])
rdwrt()
Semnalele informeaza procesele despre aparitia unor evenimente asincrone. Procesele pot sa-si transmita unele altora semnale cu ajutorul apelului sistem kill, sau nucleul poate transmite semnale intern.
Semnalele in general pot fi clasificate astfel :
Semnale care au de a face cu terminarea unui proces, trimise cand un proces se termina sau cand un proces executa apelul sistem signal cu parametrul death of child (distrugerea unui proces fiu);
Semnale care au de a face cu exceptiile produse de procese, cum ar fi accesarea de catre un proces a unei adrese din afara spatiului sau virtual de adresare, cand incearca sa scrie intr-o zona marcata ca read-only, sau cand executa o instructiune privilegiata sau pentru diferite erori hard;
Semnale ce au de a face cu situatii nerecuperabile din timpul unui apel sistem, cum ar fi consumarea tuturor resurselor sistemului in timpul unui apel exec, dupa ce spatiul de adresare original a fost eliberat;
Semnale cauzate de o eroare neasteptata in timpul unui apel sistem, cum ar fi apelul la o functie sistem inexistenta, scrierea intr-o conducta care nu are un proces care sa citeasca din ea sau utilizarea unor valori de referinta ilegale pentru apelul sistem lseek;
Semnale care provin de la un proces in mod utilizator, cum ar fi cand un proces ar dori sa receptioneze un semnal de alarma, dupa o anumita perioada sau cand procesele trimit semnale arbitrare unele altora cu ajutorul apelului sistem kill;
Semnale care tin de interactionarea cu un terminal, cum ar fi intreruperea unui terminal de catre un utilizator sau cand un utilizator apasa tasta 'break' sau 'delete';
Semnale pentru urmarirea executiei unui proces.
Nucleul testeaza aparitia semnalelor cand un proces este pe cale sa revina din mod kernel in mod utilizator si cand intra sau paraseste starea de asleep (adormit). Nucleul trateaza semnalele doar cand un proces revine din mod kernel in mod utilizator. Astfel, un semnal nu are un efect instantaneu asupra unui proces care ruleaza in mod kernel. Daca un proces ruleaza in mod utilizator si nucleul trateaza o intrerupere care determina transmiterea unui semnal procesului, nucleul va recunoaste si va trata semnalul cand se intoarce din tratarea intreruperii. Deci, un proces nu va executa niciodata in mod utilizator inainte de a trata semnalele ramase.
Nucleul trateaza semnalele in contextul procesului care le-a receptionat, deci un proces trebuie sa se execute pentru a putea trata semnale. Exista trei cazuri pentru tratarea semnalelor: procesul se termina la receptionarea unui semnal, ignora semnalul sau executa o functie particulara (utilizator) la receptionarea unui semnal. Actiunea implicita este apelul lui exit in mod kernel, dar un proces poate specifica executia unor actiuni speciale la receptionarea unor anumite semnale, cu ajutorul apelului sistem signal.
Sintaxa pentru apelul sistem signal este :
oldfunction = signal(signum, function);
unde signum este numarul semnalului pentru care procesul specifica o anumita actiune, function este adresa functiei utilizator pe care procesul vrea sa il execute la receptionarea unui astfel de semnal, iar valoarea de intoarcere oldfunction era valoarea lui function din cel mai recent apel al lui signal pentru semnalul signum. Procesul in locul adresei functiei poate transmite valorile 1 sau 0. Daca parametrul este 1, atunci procesul va ignora aparitiile ulterioare ale semnalului, iar daca este 0, procesul va iesi prin nucleu la aparitia acelui semnal (comportarea implicita).
Cand un proces primeste un semnal pe care a decis sa il ignore (printr-un apel anterior la signal cu parametrul, corespunzator adresei, 1), el isi cpntinua executia ca si cand nu s-ar fi intamplat nimic. Deoarece nucleul nu reseteaza campul din zona u area care indica faptul ca semnalul este ignorat, procesul va ignora semnalul ori de cate ori va mai aparea. Daca un proces primeste un semnal pe care a decis sa il trateze, el va executa functia specificata imediat cand se reintoarce in mod utilizator, dupa ce nucleul face urmatorii pasi :
Nucleul acceseaza contextul utilizator salvat, pentru gasirea valorii registrilor program counter si stack pointer, pe care le-a salvat pentru intoarcerea la procesul utilizator.
Sterge campul care indica rutina de tratare a semnalului din zona u area, setandu-l pe valoarea implicita.
Nucleul creaza pe stiva utilizator un pseudo-apel la functia specificata, prin punerea in stiva a registrilor corespunzatori, acest pseudo-apel va fi simulat in locul unde a fost facut apelul sistem sau a aparut intreruperea.
Nucleul schimba contextul salvat astfel : el reseteaza valoarea program counter-ului astfel incat sa indice adresa functiei specificate si seteaza valoarea stack pointer-ului astfel incat sa reflecte cresterea corespunzatoare a stivei utilizator.
Deci dupa intoarcerea din mod kernel in mod utilizator, procesul va executa functia de tratare a semnalului; iar la intoarcerea din acea functie se va intoarce in locul in care a fost executat apelul sistem sau s-a receptionat intreruperea, simuland intoarcerea dintr-o intrerupere.
Deoarece nucleul reseteaza campul care indica rutina de tratare a unui semnal s-ar putea ajunge la situatii nedeterminate. Deoarece procesele ruleaza in mod utilizator, nucleul ar putea face o schimbare de context, aparand posibilitatea ca procesul sa primeasca un semnal inainte de a putea sa refaca legatura spre functia de tratare a semnalului.
Exemplu :
Urmatorul program ilustreaza aceasta stare. Procesul apeleaza signal pentru a specifica executia functiei sigcatcher la aparitia semnalului SIGINT. El creaza un proces fiu, dupa care executa apelul sistem nice pentru a-si micsora prioritatea, dupa care intra intr-un ciclu infinit. Procesul fiu isi suspenda executia pentru 5 secunde, pentru a da timp procesului parinte pentru asi micsora prioritatea, dupa care procesul fiu intra intr-un ciclu, in care trimite un semnal de intrerupere procesului parinte, prin apelul sistem kill. Daca kill se intoarce cu un cod de eroare, probabil din cauza ca procesul parinte nu mai exista, procesul fiu se termina. Idea este ca procesul parinte ar trebui sa apeleze de fiecare data functia de tratare a semnalului, care sa tipareasca un mesaj si sa apeleze din nou signal pentru a capta urmatoarea aparitie a unui semnal de intrerupere (SIGINT), astfel procesul parinte ar executa la infinit ciclul.
Dar este posibila aparitia urmatoarei secvente de evenimente :
Procesul fiu trimite un semnal de intrerupere procseului parinte.
Procesul parinte prinde semnalul si apeleaza functia de tratare, dar nucleul intrerupe procesul si face o schimbare de context, inainte ca acesta sa fi executat din nou apelul sistem signal.
Procesul fiu se executa din nou si trimite un nou semnal de intrerupere procesului parinte.
Procesul parinte primeste cel de-al doilea semnal de intrerupere, dar el nu a reusit sa specifice rutina de tratare pentru acesta, astfel incat se va apela la tratarea impicita, adica la reluarea executiei procesul parinte se va termina.
#include <signal.h>
sigcatcher()
main()
/* lower priority, greater chance of exhibiting race */
nice(10);
for(;;)
;
Desi procesele de pe un sistem UNIX sunt identificate printr-un identificator unic, sistemul cateodata trebuie sa identifice procesele prin 'grup'. De exemplu, procesele cu un proces stramos comun, care este un shell sunt in general legate unele de altele, de aceea toate aceste procese vor primi semnale cand un utilizator apasa tasta 'delete' sau 'break' sau cand terminalul nu mai este accesibil. Nucleul foloseste identificatorul de grup al proceselor pentru a identifica grupurile de procese care trebuie sa primeasca un semnal comun, pentru anumite evenimente. Procesele din acelasi grup de procese au aceiasi identificatori de grup.
Apelul sistem setpgrp initializeaza identificatorul de grup al unui proces si il seteaza egal cu valoarea identificatorului de proces al procesului care l-a apelat.
Sintaxa apelului este :
grp = setpgrp();
unde grp este noul identificator de grup al procesului. Un proces fiu retine identificatorul de grup al procesului parinte prin fork.
Procesele folosesc apelul sistem kill pentru a trimite semnale. Sintaxa apelului este :
kill(pid, signum);
unde pid identifica setul de procese care vor primi semnalul, iar signum este numarul semnalului care se doreste a fi transmis. Urmatoarea lista arata corespondenta dintre valorile lui pid si setul de procese :
Daca pid este un intreg pozitiv, nucleul trimite semnalul la procesul cu identificatorul pid.
Daca pid este 0, nucleul trimite semnalul la toate procesele din grupul procesului emitator.
Daca pid este -1, nucleul trimite semnalul tuturor proceselor al caror identificator real de utilizator corespunde cu identificatorul efectiv de utilizator al procesului emitator. Daca procesul emitator are identificatorul efectiv de utilizator al superutilizatorului, atunci nucleul trimite semnalul tuturor proceselor cu exceptia procesului 0 si 1.
Daca pid este un intreg negativ, dar nu -1, nucleul trimite semnalul tuturor proceselor din grupul de procese al carui identificator de grup este egal cu valoarea absoluta a pid.
In toate cazurile, daca procesul emitator nu are identificatorul efectiv de utilizator al superutilizatorului, sau identificatorul sau real sau efectiv nu se potrivesc cu identificatorul real sau efectiv al procesului destinatie, apelul kill esueaza.
Exemplu :
Urmatorul program exemplifica notiunea de grup de procese, impreuna cu folosirea apelurilor de sistem mentionate.
#include <signal.h>
main()
}
kill(0, SIGINT);
In precedentul program, procesul reseteaza identificatorul sau de grup si creaza 10 procese fii. Cand sunt create, fiecare proces fiu va avea acelasi identificator de grup ca si procesul parinte, dar procesele create in timpul iteratiilor impare isi reseteaza si elel identificatorul de grup. Apelurile sistem getpid si getpgrp intorc identificatorul de proces, respectiv identificatorul de grup al procesului aflat in executie, iar apelul sistem pause suspenda executia procesului pana cand receptioneaza un semnal. In final, parintele executa apelul sistem kill si trimite un semnal de intrerupere tuturor proceselor din grupul sau. Nucleul trimite semnalul la cele 5 procese 'pare', care nu si-au resetat identificatorul de grup, dar celelalte 5 procese 'impare' vor continua sa ruleze.
Procesele de pe un sistem UNIX se termina prin executia apelului sistem exit. Un proces pe cale de terminare intra in starea de Zombie, eliberand resursele detinute, in afara intrarii din tabela de procese. Sintaxa pentru apel este :
exit(status);
unde valoarea lui status este intoarsa procesului parinte pentru examinare. Procesele pot apela exit explicit sau implicit la sfarsitul programului, deoarece rutina de pornire linkeditata cu toate programele C apeleaza exit cand programul revine din functia main, punctul de intrare al tuturor programelor. Deasemenea si nucleul poate apela exit, intern, pentru un proces, ca raspuns la un semnal care nu a fost captat. Daca este asa atunci valoarea lui status indica numarul semnalului.
Un proces poate sa-si sincronizeze executia cu terminarea unui proces fiu prin executarea apelului sistem wait. Sintaxa apelului sistem este :
pid = wait(stat_addr);
unde pid este identificatorul de proces al unui fiu aflat in starea Zombie, iar stat_addr este adresa unui intreg (ret_code), care va contine codul de iesire pentru respectivul proces fiu.
Exemplu :
Pentru exemplificare consideram urmatorul program. Consideram doua cazuri, primul, apelul programului fara parametri si al doilea caz, apelul programului cu cel putin un parametru. In primul caz procesul parinte creaza 15 procese fii care eventual se termina cu valoarea de iesire i, valoarea variabilei contor cand procesul fiu a fost creat. Nucleul, executand wait pentru procesul parinte, gaseste un proces fiu in starea Zombie si ii intoarce identificatorul de proces, este nedeterminat al carui proces fiu va fi intors. Codul din libraria C, pentru apelul sistem exit plaseaza codul de iesire in bitii 8 - 15 ai lui ret_code si intoarce identificatorul de proces.
In al doilea caz, procesul parinte apeleaza signal pentru a ignora semnalele datorate 'mortii' proceselor fiu. Presupunand ca procesul parinte adoarme inainte ca vreun proces fiu sa se termine, cand un proces fiu se termina, el trimite un semnal de 'deces' procesului parinte, procesul parinte se trezeste si va fi planificat pentru executie. Cand procesul parinte ajunge sa se execute, el gaseste semnalul in asteptare, dar deoarece el ignora semnalele de 'deces ale fiilor', nucleul indeparteaza intrarea corespunzatoare procesului fiu in starea Zombie din tabela de procese si continua executia lui wait ca si cum nu s-ar fi receptionat nici un semnal. Nucleul executa aceasta procedura de fiecare data cand parintele primeste un semnal de 'deces al unui fiu', pana cand procesul parinte nu va mai avea fii. Atunci apelul sistem wait va intoarce -1, semnaland eroare, deoarece nu exista nici un proces fiu dupa care sa astepte.
Diferenta dintre cele doua cazuri este ca in primul caz procesul parinte asteapta dupa terminarea oricarui proces fiu, in timp ce in al doilea caz el asteapta pentru terminarea tuturor proceselor fiu.
#include <signal.h>
main(int argc, char *argv[])
ret_val = wait(&ret_code);
printf('wait ret_val %x ret_code %xn', ret_val, ret_code);
Apelul sistem exec incarca un alt program, suprascriind spatiul de memorie al unui proces cu o copie a fisierului executabil. Sintaxa acestui apel sistem este :
execve(filename, argv, envp);
unde filename este numele fisierului executabil care va fi incarcat, argv este un pointer spre un tablou de pointeri spre sirurile de caractere care vor fi parametri programului executabil, iar envp este un pointer spre un tablou de pointeri spre siruri de caractere care constituie ambianta programului care va fi executat. Exista mai multe functii de librarie, care apeleaza la exec, cum ar fi execl, execv, execle si altele.
Cand un program foloseste parametrii din linia de comanda, ca in :
main(argc, argv)
tabloul argv este o copie a parametrului argv comunicat lui exec. Sirurile de caractere din ambianta au urmatoarea forma : 'name=value' si pot contine informatii utile. Procesele pot accesa ambianta lor prin variabila globala environ, initializat de rutina C de start.
Exemplu :
Urmatorul fragment de program ilusteaza folosirea acestui apel sistem :
main()
Copyright © 2024 - Toate drepturile rezervate