|
5. Procese
1. Concepte de baza
Orice sistem de calcul modern este capabil sa execute mai multe programe
in acelasi timp. Cu toate acestea, in cele mai multe cazuri, unitatea centrala
de prelucrare (CPU) nu poate executa la un moment dat decat un singur program.
De aceea, sarcina de a rula mai multe programe in acelasi timp revine sistemului
de operare, care trebuie sa introduca un model prin intermediul caruia
executia programelor, privita din perspectiva utilizatorului, sa se desfasoare
in paralel. Se realizeaza, de fapt, un pseudoparalelism, prin care
procesorul este alocat pe rand programelor care trebuie rulate, cate o
cuanta de timp pentru fiecare, astfel incat din exterior ele par ca ruleaza
efectiv in acelasi timp.
Cel mai raspandit model care introduce paralelismul in executia programelor
este modelul bazat pe procese. Acest model este cel adoptat de sistemul
de operare Unix si va face obiectul acestei lucrari.
Un proces este un program secvential in executie, impreuna
cu zona sa de date, stiva si numaratorul de instructiuni (program counter).
Trebuie facuta inca de la inceput distinctia dintre proces si program.
Un program este, in fond, un sir de instructiuni care trebuie executate
de catre calculator, in vreme ce un proces este o abstractizare a programului,
specifica sistemelor de operare. Se poate spune ca un proces executa un
program si ca sistemul de operare lucreaza cu procese, iar nu cu programe.
Procesul include in plus fata de program informatiile de stare legate de
executia programului respectiv (stiva, valorile registrilor CPU etc.).
De asemenea, este important de subliniat faptul ca un program (ca aplicatie
software) poate fi format din mai multe procese care sa ruleze sau
nu in paralel.
Orice proces este executat secvential, iar mai multe procese pot sa
ruleze in paralel (intre ele). De cele mai multe ori, executia in paralel
se realizeaza alocand pe rand procesorul cate unui proces. Desi la un moment
dat se executa un singur proces, in decurs de o secunda, de exemplu,
pot fi executate portiuni din mai multe procese. Din aceasta schema rezulta
ca un proces se poate gasi, la un moment dat, in una din urmatoarele trei
stari [Tanenbaum]:
-
In executie
-
Pregatit pentru executie
-
Blocat
Procesul se gaseste in executie atunci cand procesorul ii executa
instructiunile. Pregatit de executie este un proces care, desi ar
fi gata sa isi continue executia, este lasat in asteptare din cauza ca
un alt proces este in executie la momentul respectiv. De asemenea, un proces
poate fi blocat din doua motive: el isi suspenda executia in mod voit sau procesul efectueaza o operatie in afara procesorului,
mare consumatoare de timp (cum e cazul operatiilor de intrare-iesire -
acestea sunt mai lente si intre timp procesorul ar putea executa parti
din alte procese).
2. Utilizarea proceselor in UNIX
2.1 Apelul sistem fork()
Din perspectiva programatorului, sistemul de operare UNIX pune la dispozitie
un mecanism elegant si simplu pentru crearea si utilizarea proceselor.
Orice proces trebuie creat de catre un alt proces.Procesul creator este
numit proces parinte, iar procesul creat proces fiu. Exista
o singura exceptie de la aceasta regula, si anume procesul init,
care este procesul initial, creat la pornirea sistemului de operare si
care este responsabil pentru crearea urmatoarelor procese. Interpretorul
de comenzi, de exemplu, ruleaza si el in interiorul unui proces.
Fiecare proces are un identificator numeric, numit identificator
de proces (process identifier - PID). Acest identificator este folosit
atunci cand se face referire la procesul respectiv, din interiorul programelor
sau prin intermediul interpretorului de comenzi.
Un proces trebuie creat folosind apelul sistem
pid_t fork()
Prin aceasta functie sistem, procesul apelant (parintele) creeaza un nou
proces (fiul) care va fi o copie fidela a parintelui. Noul proces
va avea propria lui zona de date, propria lui stiva, propriul lui cod executabil,
toate fiind copiate de la parinte in cele mai mici detalii. Rezulta ca
variabilele fiului vor avea valorile variabilelor parintelui in momentul
apelului functie fork( ), iar executia fiului va continua cu instructiunile
care urmeaza imediat acestui apel, codul fiului fiind identic cu cel al
parintelui. Cu toate acestea, in sistem vor exista din acest moment doua
procese independente, (desi identice), cu zone de date si stiva distincte.
Orice modificare facuta, prin urmare, asupra unei variabile din procesul
fiu, va ramane invizibila procesului parinte si invers.
Procesul fiu va mosteni de la parinte toti descriptorii de fisier deschisi
de catre acesta, asa ca orice prelucrari ulterioare in fisiere vor fi efectuate
in punctul in care le-a lasat parintele.
Deoarece codul parintelui si codul fiului sunt identice si pentru ca
aceste procese vor rula in continuare in paralel, trebuie facuta clar distinctia,
in interiorul programului, intre actiunile ce vor fi executate de fiu si
cele ale parintelui. Cu alte cuvinte, este nevoie de o metoda care sa indice
care este portiunea de cod a parintelui si care a fiului.
Acest lucru se poate face simplu, folosind valoarea returnata de functia
fork( ). Ea returneaza:
-
-1, daca operatia nu s-a putut efectua (eroare)
-
0, in codul fiului
-
pid, in codul parintelui, unde pid este identificatorul de
proces al fiului nou-creat.
Prin urmare, o posibila schema de apelare a functiei fork( ) ar
fi:
...
if( ( pid=fork() ) < 0)
{
perror("Eroare");
exit(1);
}
if(pid==0)
{
/* codul fiului */
...
exit(0)
}
/* codul parintelui */
...
wait(&status)
2.2 Functiile wait() si
waitpid()
pid_t wait(int *status)
pid_t waitpid(pid_t pid, int *status, int flags)
Functia wait( ) este folosita pentru asteptarea terminarii fiului
si preluarea valorii returnate de acesta. Parametrul status este
folosit pentru evaluarea valorii returnate, folosind cateva macro-uri definite
special (vezi paginile de manual corespunzatoare functiilor wait( )
si waitpid( ) ). Functia waitpid( ) este asemanatoare cu
wait( ), dar asteapta terminarea unui anumit proces dat, in vreme
ce wait( ) asteapta terminarea oricarui fiu al procesului curent.
Este obligatoriu ca starea proceselor sa fie preluata dupa terminarea acestora,
astfel ca functiile din aceasta categorie nu sunt optionale.
2.3 Functiile de tipul exec()
Functia fork( ) creeaza un proces identic cu procesul parinte. Pentru
a crea un nou proces care sa ruleze un program diferit de cel al parintelui,
aceasta functie se va folosi impreuna cu unul din apelurile sistem de tipul
exec( ): execl( ), execlp( ), execv( ), execvp( ), execle( ),
execve( ).
Toate aceste functii primesc ca parametru un nume de fisier care reprezinta
un program executabil si realizeaza lansarea in executie a programului.
Programul va fi lansat atfel incat se va suprascrie codul, datele si
stiva procesului care apeleaza exec( ), astfel incat, imediat dupa acest
apel programul initial nu va mai exista in memorie. Procesul va ramane,
insa, identificat prin acelasi numar (PID) si va mosteni toate eventualele
redirectari facute in prealabil asupra descriptorilor de fisiere (de exemplu
intrarea si iesirea standard). De asemenea, el va pastra relatia parinte-fiu
cu procesul care a apelat fork( ).
Singura situatie in care procesul apelant revine din apelul functiei
exec( ) este acela in care operatia nu a putut fi efectuata, caz
in care functia returneaza un cod de eroare (-1).
In consecinta, lansarea intr-un proces separat a unui program de pe
disc se face apeland fork( ) pentru crearea noului proces, dupa
care in portiunea de cod executata de fiu se va apela una din functiile
exec( ).
Observatie: consultati paginile de manual
corespunzatoare acestor functii.
2.4 Functiile system() si
vfork()
int system(const char *cmd)
Lanseaza in executie un program de pe disc, folosind in acest scop un apel
fork( ), urmat de exec( ), impreuna cu waitpid( )
in parinte.
pid_t vfork()
Creeaza un nou proces, la fel ca fork( ), dar nu copiaza in intregime
spatiul de adrese al parintelui in fiu. Este folosit in conjunctie cu exec(
), si are avantajul ca nu se mai consuma timpul necesar operatiilor
de copiere care oricum ar fi inutile daca imediat dupa aceea se apeleaza
exec( ) (oricum, procesul fiu va fi supascris cu programul luat
de pe disc).
2.5 Alte functii pentru lucrul cu procese
pid_t getpid() - returneaza PID-ul procesului
curent
pid_t getppid() - returneaza PID-ul parintelui procesului
curent
uid_t getuid() - returneaza identificatorul utilizatorului
care a lansat procesul curent
gid_t getgid() - returneaza identificatorul grupului
utilizatorului care a lansat procesul curent
2.6 Gestionarea proceselor din linia de comanda
Sistemul de operare UNIX are cateva comenzi foarte utile care se refera
la procese:
-
ps - afiseaza informatii despre procesele care ruleaza in mod curent
pe sistem
-
kill -semnal proces - trimite un semnal unui proces. De exemplu,
comanda kill -9 123 va termina
procesul cu numarul 123
-
killall -semnal nume - trimite semnal catre toate procesele
cu numele nume
Exista si alte comenzi utile; pentru folosirea lor, este recomandat sa
se consulte paginile de manual UNIX.
1. Explicati efectul urmatoarei secvente de cod:
int i;
for (i = 1; i <= 10; i++)
fork();
2. Realizati un program C pentru UNIX care creaza 20 de procese (inclusiv
parintele). Fiecare proces afiseaza pe ecran cate 10 linii continand tipul
sau (parinte, fiu1, fiu2, ... , fiu19) si PID-ul propriu. Dupa aceea, procesele
fiu se vor termina returnand valori diferite, iar parintele va afisa valorile
returnate de catre fii.
Bibliografie:
[Tanenbaum] Andrew S. Tanenbaum: Modern Operating Systems,
Prentice Hall, 1992, pag. 27-31, 279-284
Autor: Dan Cosma
|
|