Sunteți pe pagina 1din 15

PROCESE

Un concept cheie n orice sistem de operare este procesul. Un proces este un program n execuie(instana execuiei unui program de pe disc). Procesele sunt unitatea primitiv prin care sistemul de operare aloc resurse utilizatorilor. Orice proces are un spaiu de adrese i unul sau mai multe fire de execuie (thread-uri). Putem avea mai multe procese ce execut acelai program, dar oricare dou procese sunt complet independente. Procesele sunt organizate n Unix ntr-o structur arborescent asemenea organizrii sistemului de fiiere. Toate procesele active sunt de fapt descendeni direci sau indireci ai unui singur proces, lansat la pornirea sistemului prin comanda /etc/init. Comanda pstree permite vizualizarea arborelui curent de procese.

Identificarea proceselor
Fiecare proces are asociat un identificator unic numit identicator de proces (PID). Un proces poate s determine propriul PID folosind apelul sistem getpid(). PID-ul unui proces nu poate fi schimbat, dar poate fi refolosit cnd procesul nu mai exist . Procesele sunt organizate n Unix ntr-o structur arborescent asemenea organizrii sistemului de fiiere. Orice proces nou n Unix este creat de un proces anterior existent, dnd na tere unei relaii printe-fiu. Excepie face procesul init, care este creat si utilizat chiar de nucleu, toate celelalte procese din sistem fiind descendeni direci sau indireci. Comanda pstree permite vizualizarea arborelui curent de procese. Un proces poate sa determine PID-ul printelui prin apelul getppid(). Sistemul UNIX ine evidena proceselor ntr-o structur de date intern numit tabel de procese. Lista proceselor din tabela de procese poate fi obinut prin comanda ps. Un proces poate fi asociat unui terminal, care este numit terminalul de control asociat procesului. Acesta este motenit de la procesul printe la creare. Un proces care nu este asociat cu un terminal de control este numit daemon. Spoolerul de imprimant este un exemplu de astfel de proces. Un proces daemon este identificat n rezultatul afirii comenzii ps prin simbolul ? plasat n coloana TTY. Process Control Block structur n sistemul de operare n care se regsesc informaii necesare pentru rularea programului, cte una pentru fiecare proces existent n sistem: spaiile de adrese i regitrii generali PC (contor program) i SP (indicator stiv) tabelele de fiiere deschise (descriptori de fiiere) lista de semnale (blocate, ignorate sau care ateapt s fie trimise procesului) handler-ele pentru semnale informaiile referitoare la sistemele de fiiere (directorul rdcina, directorul curent)

n general un proces ruleaz ntr-un mediu specificat printr-un set de variabile de sistem. O variabil de sistem este o pereche NUME=valoare. Un proces poate s verifice sau s seteze valoarea unei variabile de sistem printr-o serie de apeluri de bibliotec. n momentul lansrii n execuie a unui program, n sistemul de operare se va crea un proces pentru alocarea resurselor necesare rulrii programului respectiv. Fiecare sistem de operare pune la dispoziie apeluri de sistem pentru: crearea terminarea ateptarea terminrii unui proces duplicarea descriptorilor de resurse ntre procese ori nchiderea acestor descriptori. fork() i exec() pentru crearea unui proces i respectiv modificarea imaginii unui proces wait() i waitpid() pentru ateptarea terminrii unui proces exit() pentru terminarea unui proces. dup() i dup2() pentru copierea descriptorilor de fiier getenv(), setenv(), unsetenv() pentru citirea, modificarea ori tergerea unei variabile de sistem precum i un pointer la tabela de variabile de sistem - environ.

Apelurile de sistem puse la dispoziie de Linux pentru gestionarea proceselor sunt:

Crearea unui proces


n UNIX singura modalitate de creare a unui proces este prin apelul de sistem fork:
pid_t fork(void);

La fiecare execuie a acestui apel, se obin dou procese concurente, identice la nceput, dar cu nume diferite. Apelul sistem fork realizeaz o copie a procesului iniial, ca atare imaginea proceselor n memorie este identic. Noul proces va avea propria lui zon de date, propria lui stiv, propriul lui cod executabil, toate fiind copiate de la printe. Aadar, efectul este crearea unui nou proces - procesul copil, copie a celui care a apelat fork - procesul printe. Copilul primete un nou PID de la sistemul de operare.
pid_t getpid(void)

- returneaza PID-ul procesului curent - returneaza PID-ul parintelui procesului curent - returneaza identificatorul utilizatorului care a lansat procesul curent - returneaza identificatorul grupului utilizatorului care a lansat procesul curent

pid_t getppid(void) uid_t getuid(void) gid_t getgid(void)

n noul proces (fiu) toate vechile variabile i pstreaz valorile, toi descriptorii de fi ier sunt aceiai, se motenete acelai UID real i GUID real, acelai ID de grup de procese, aceleai variabile de context. Din momentul revenirii din apelul fork, procesele tat i fiu se execut independent, concurnd unul cu cellalt pentru obinerea resurselor. Procesul fiu i ncepe execuia din locul unde rmsese procesul tat. Nu se poate preciza care dintre procese va porni primul. Este posibil ns separarea execuiei n cele dou procese prin testarea valorii ntoarse de apelul fork. Deoarece codul printelui i codul fiului sunt identice si pentru ca aceste procese vor rula n continuare n paralel, trebuie fcut clar distincia, n interiorul programului, ntre aciunile ce vor fi executate de fiu i

cele ale printelui. Cu alte cuvinte, este nevoie de o metod care s indice care este poriunea de cod a printelui i care a fiului. Apelul de sistem fork este unul mai special prin faptul c, n caz de reuit, se ntoarce de 2 ori, cte o dat n fiecare proces. Fork() ntoarce valoarea -1 n caz de eroare, PID-ul noului proces n procesul printe i valoarea 0 n procesul copil. Pentru aflarea PID-ului procesului curent ori al procesului printe se va apela una din funciile menionate anterior. Secvena clasic de creare a unui proces este prezentat n continuare:
#include <sys/types.h> #include <unistd.h> ... switch (pid = fork()) { case -1: /* fork failed */ printf("fork failed\n"); exit(-1); case 0: /* child starts executing here */ ... default: /* parent starts executing here */ printf("created process with pid %d\n", pid); ... }

Ateptarea terminrii unui proces


Rulai urmtorul exemplu. Ce observai? Cum poate fi remediat execuia programului?
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { int pid; printf("\nbefore if -> PID=%d PPID=%d\n",getpid(),getppid()); if ((pid = fork()) < 0) { printf("EROARE!!!"); } else if (pid == 0) { printf("if -> fiu? ->PID=%d PPID=%d\n",getpid(),getppid()); } else { printf("if -> parinte? ->PID=%d PPID=%d\n",getpid(),getppid()); } printf("after if -> ? ->PID=%d PPID=%d\n",getpid(),getppid()); return 0; }

Procesul printe poate s atepte terminarea (normala sau cu eroare) procesului copil folosind apelurile sistem wait sau waitpid (wait3, wait4).
pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);

Apelul wait() este folosit pentru asteptarea terminarii copilului si preluarea valorii returnate de acesta. Parametrul status este folosit pentru evaluarea valorii returnate, folosind cateva macro-uri definite special. Apelul waitpid() este asemanator cu wait(), dar ateapt terminarea unui anumit proces dat, in vreme ce wait() ateapt terminarea oricrui proces copil. Familia de apeluri wait suspend execuia procesului apelant pn cnd procesul (procesele) specificate n argumente fie s-au terminat fie au fost oprite (SIGSTOP).

Valorile uzuale ale argumentului pid sunt identificatorul unui proces copil (spre exemplu, returnat de fork) sau -1, n cazul n care se dorete ateptarea oricrui proces copil. Funcia va ntoarce PID-ul procesului a crui stare e raportat; informaiile de stare sunt depuse ca int la adresa indicata prin argumentul status. Options ofer posibilitatea procesului apelant de a se ntoarce din apelul waitpid, fr a rmne blocat n ateptarea unui proces copil (WNOHANG, WUNTRACED,WCONTINUED studiai man page) Exista o variant simplificat care ateapt orice proces copil s se termine:
pid_t wait(int *status);

care este echivalent cu

waitpid(-1, &status, 0);

Pentru a folosi wait sau waitpid trebuie incluse header-ele sys/types.h i sys/wait.h. Un proces ce apeleaz wait sau waitpid poate: a) s se blocheze (daca toi copiii si sunt n execuie) b) s primeasc starea de terminare a copilului (daca unul dintre copii s-a terminat) c) s primeasc eroare (daca nu are procese copil). Diferenele intre cele doua funcii constau in: a) wait blocheaz procesul apelant pn la terminarea execuiei unui copil, n timp ce waitpid are o opiune, precizat prin argumentul options, care evit acest lucru (WNOHANG). b) waitpid nu ateapt terminarea primului fiu, ci poate specifica prin argumentul pid procesul fiu ateptat. Pentru a analiza starea n care s-a terminat un proces copil (normal sau eroare) exist trei macrouri excluse mutual, toate prefixate de WIF i definite n fiierul sys/wait.h. Pe lng acestea, exista alte macrouri pentru determinarea codului de exit, numr semnal, etc (studiai man page). Aceste macrouri se aplica parametrului pstatus i sunt ilustrate mai jos: a) WIFEXITED (pstatus) TRUE daca informaia de stare, pstatus, provine de la un proces terminat normal(apel exit sau return) - n acest caz, WEXITSTATUS(pstatus) extrage octetul mai puin semnificativ pentru a determina codul de exit al procesului terminat. Completai exemplul anterior cu secvena de cod de mai jos:
pid_copil=wait(&status); if(WIFEXITED(status)) printf("%d terminat normal cu codul %d\n",pid_copil,WEXITSTATUS(status)); //adugai n procesul printe

b) WIFSIGNALED (pstatus) TRUE daca informaia de stare, pstatus, provine de la un proces terminat anormal (semnal netratat) - n acest caz WTERMSIG (pstatus) extrage numrul semnalului trimis procesului terminat.
while(1){} >ps ef >kill SIGUSR1 pid_copil if(WIFSIGNALED(status)) printf("%d terminat cu semnalul %d\n",pidex,WTERMSIG(status)); //n procesul copil //n terminal //n printe

c) WIFSTOPPED (pstatus) TRUE daca informaia de stare, pstatus, provine de la un proces temporar oprit - n acest caz WSTOPSIG (pstatus) extrage numrul semnalului care a oprit procesul.
while(1){} //n procesul copil ------------------------------------sleep(2); kill(pid,SIGTSTP); sleep(2); pidex=waitpid(-1,&status,WUNTRACED); if(WIFSTOPPED(status)) printf("%d oprit temporar cu semnalul %d\n",pidex,WSTOPSIG(status)); //n printe

Terminarea unui proces


Pentru terminarea procesului curent, Linux pune la dispoziie apelul de sistem exit. Dintr-un program C exist trei moduri de invocare a acestui apel de sistem: Apelul _exit (POSIX), i apelurile _Exit i exit din biblioteca standard C.
#include <unistd.h> void _exit (int status); #include <stdlib.h> void _Exit (int status); void exit (int status);

Procesul apelant se va termina imediat. Toi descriptorii de fiier ai procesului sunt nchii, copiii procesului sunt "nfiai" de init, iar printelui procesului i va fi trimis un semnal SIGCHLD. Procesului printe i va fi ntoars valoarea status ca rezultat al unei funcii de ateptare (wait sau waitpid). Pentru terminarea unui alt proces din sistem, se va trimite un semnal c tre procesul respectiv prin intermediul apelului de sistem kill. Mai multe detalii despre kill i semnale n laboratorul de semnale. FORK - waiting
int main() { int pid,pidex; int status; printf("\nbefore if -> PID=%d PPID=%d\n",getpid(),getppid()); if ((pid = fork()) < 0) { printf("EROARE!!!"); } else if (pid == 0) { printf("if -> fiu? ->PID=%d PPID=%d\n",getpid(),getppid()); while(1){} } else { printf("if -> parinte? ->PID=%d PPID=%d\n",getpid(),getppid()); //sleep(2); //kill(pid,SIGTERM); //sleep(2); pidex=waitpid(-1,&status,WUNTRACED); printf("--------\n"); if(WIFEXITED(status)) printf("%d terminat normal cu codul %d\n",pidex,WEXITSTATUS(status)); if(WIFSIGNALED(status)) printf("%d terminat cu semnalul %d\n",pidex,WTERMSIG(status)); if(WIFSTOPPED(status)) printf("%d oprit temporar cu semnalul %d\n",pidex,WSTOPSIG(status)); } printf("after if -> ? ->PID=%d PPID=%d\n",getpid(),getppid()); return 0; }

nlocuirea imaginii unui proces


Raiunea a dou procese identice are sens dac se poate modifica segmentul de date i cel de cod al procesului rezultat aa nct s se poat ncrca un nou program. Pentru acest lucru exist apelul exec() (mpreuna cu familia de astfel de apeluri execl, execlp, execv si execvp). Partea de sistem a procesului nu se modifica n nici un fel prin apelul exec. Practic, procesul fiu executa cu totul altceva dect parintele sau. Dupa un apel exec reusit nu se mai revine n vechiul cod. Fisierele deschise ale tatalui se regasesc deschise si la fiu dupa apelul exec si ca indicatorul de citire/scriere al fisierelor deschise ramne nemodificat, ceea ce poate cauza neplaceri n cazul n care tatal si fiul vor sa scrie n acelasi loc. Un apel exec nereusit returneaza valoarea -1, dar cum alta valoare nu se returneaza ea nu trebuie testata. Diferitele variante de exec() dau utilizatorului mai mult flexibilitate la transmiterea parametrilor. Sintaxele lor sunt:
(1) execl ( const char *path, const char *arg0, ..., NULL); (2) execv ( const char *path, char *argv[]); (3) execlp( const cahr *filename, const char *arg0, ..., NULL); (4) execvp( const cahr *filename, char *argv[]);

Familia de funcii exec va executa un nou program, nlocuind imaginea procesului curent, cu cea dintrun fiier (executabil). Spaiul de adrese al procesului va fi nlocuit cu unul nou, creat special pentru execuia fiierului. De asemenea vor fi reiniializai regitrii IP (EIP/RIP - contorul program) i SP (ESP/RSP indicatorul stiv) i regitrii generali.
int execl(const char *path, const char *arg, ...);

Presupunem c vrem s apelm comanda ls -la:

execl("ls", "ls", "-la", NULL);

Se observ c primul argument este nsui numele programului, iar ultimul este NULL. execl nu caut programul dat ca parametru n PATH, astfel c acesta trebuie nsoit de calea complet. Versiunea execlp cauta programul i n PATH. Folosirea oricrei funcii din familia exec necesit includerea header-ului unistd.h.

Rularea unui program executabil


Modul cel mai simplu prin care se poate crea un nou proces este prin folosirea funciei de bibliotec system:
int system(const char *command);

Apelul acestei funcii are ca efect execuia ca o comand shell a comenzii reprezentate prin irul de caractere command. S lum ca exemplu urmtorul program C: Exemplu 1 - sys1.c
#include <stdlib.h> int main(int argc, char **argv) { system("ls -la $HOME"); }

Exemplu 2 - sys2.c
#include <stdlib.h> int main(int argc, char **argv) { system("cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la"); }

care este echivalent cu


$ sh -c "ls -la $HOME"

program C care este echivalent cu


$ sh -c "cd /etc/rc.d/rc$RUNLEVEL.d/; ls -la"

Implementarea system: se creeaz un nou proces cu fork; procesul copil execut prin intermediul exec programul sh cu argumentele -c "comanda"(shell-ul creeaz la rndul su un proces nou pentru execuia comenzii), timp n care printele ateapt terminarea procesului copil.

my_system Un exemplu de folosire a primitivelor exec, fork, exit i wait (sau de rulare a unui program) l reprezint chiar reimplementarea apelului de bibliotec system.
int my_system(const char *command) { int pid, status; switch ((pid=fork())) { case -1: //error forking. return -1; case 0: { const char *argv[] = {"/bin/bash", "-c", command, NULL}; execv("/bin/bash", (char *const *)argv); /* exec se poate ntoarce doar cu cod de eroare (de ex. cnd nu se gasete fiierul de executat - n cazul nostru /bin/bash. n caz de eroare, terminm procesul copil */ exit(-1); } } //doar procesul printe ajunge aici, i doar dac fork() s-a terminat cu succes waitpid(pid, &status, 0); // obinem codul de eroare cu care s-a terminat copilul if (WIFEXITED(status)) printf("Child %d terminated normally, with code %d\n", pid, WEXITSTATUS(status)); return status; } int main() { my_system("ls"); return 0; }

Motenirea resurselor
Apelul sistem fork realizeaz o copie a procesului iniial, ca atare imaginea proceselor n memorie este identic. Noul proces va avea propria lui zon de date, propria lui stiv, propriul lui cod executabil, toate fiind copiate de la printe. Orice modificare fcut, prin urmare, asupra unei variabile din procesul fiu, va rmne invizibil procesului printe i invers. Motenirea(duplicarea) segmentului de cod
int main() { int c=2,status,pid=1; while(c){ if(pid==1) pid=fork(); if(pid>0){ wait(&status); printf("parent %d done\n",getpid()); _exit(0); } printf("child %d running - c=%d\n",getpid(),c); c--; } printf("child %d done\n",getpid()); return 0; }

Motenirea(duplicarea) segmentului de date

if(pid>0){ wait(&status); printf("parent %d done - c=%d\n",getpid(),c); _exit(0); }

Exemplu motenire/ateptare
int main() { int c=2,status; while(c){ fork(); printf("proces PID=%d PPID=%d cu c=%d\n",getpid(),getppid(),c); c--; } while(wait(&status)>0){} printf("proces %d FINAL\n",getpid()); return 0; }

O greeal frecvent este ntlnit n cazul folosirii unei secvene repetitive pentru crearea unui numr fix de procese copil. Fr un control bun al execuiei secvenei se pot crea mult mai multe procese. n exemplul urmtor se dorete crearea a 3 procese copil.
int main() { int i;//int pid=1; for(i=0;i<3;i++){ //if (pid>0) pid=fork(); } printf("proces %d cu parinte %d\n",getpid(),getppid()); while(1){} return 0; }

COW Copy-On-Write n general, la apelul fork(), toate resursele printelui sunt duplicate i atribuite i copilului. Aceast abordare este ineficient n 2 situaii: dac procesul copil ar apela imediat ce a fost creat exec(), ntreg programul ar fi rescris i, prin urmare, toat zona de date i cod va fi fost duplicat inutil; dac ambele procese ar folosi aceleai date i acelai cod, fr s aduc modifice, ar fi inutil existena a 2 seturi resurse i ar fi suficient unul singur.

n Linux, apelul fork() este implementat mpreun cu tehnica COW care permite amnarea sau chiar prevenirea duplicrii ineficiente a resurselor. Astfel, la crearea unui nou proces, att copilul ct i printele partajeaz resursele care sunt marcate ntr-o aa manier nct, duplicarea are loc abia n momentul n care unul din cele 2 procese trebuie s modifice o zona de memorie. n cazul modificrilor aduse zonei de cod(apelul exec() ), resursele nu mai sunt duplicate i se aloc altele pentru noul program ncrcat.

Motenirea descriptorilor de fiier dupa operaii fork/exec


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. main.c a.out
int main() { int pid,fd,status; char *buf[3]={"before fork\n","parent\n","child"}; fd=open("a.txt",O_WRONLY|O_TRUNC|O_CREAT,0644); write(fd,buf[0],12); pid=fork(); if (pid > 0) { write(fd,buf[1],7); wait(&status); } else { write(fd,buf[2],6); _exit(0); } return 0; }

main.c a.out
int main() { int pid,fd,status; char *buf[3]={"before fork\n","parent\n","child\n"}; fd=open("a.txt",O_WRONLY|O_TRUNC|O_CREAT,0644); dup2(fd,STDOUT_FILENO); printf("%s",buf[0]); fflush(stdout); pid=fork(); if (pid > 0) { wait(&status); printf("%s",buf[1]); fflush(stdout); } else { //execlp("ls","-a",NULL); printf("%s",buf[2]); fflush(stdout); _exit(0); } return 0; }

Dup un apel exec descriptorii de fiier sunt pstrai de asemenea, mai puin aceia dintre ei care au setat flagul O_CLOEXEC Pentru a seta flagul O_CLOEXEC, fie se deschide fisierul cu flagul setat fie, daca fisierul este deja deschis, se folosete funcia fcntl cu un apel de tipul:
fcntl(file_descriptor, F_SETFD, FD_CLOEXEC);

Pentru a putea folosi funcia fcntl trebuie incluse header-ele unistd.h i fcntl.h.

main.c a.out
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //#ifndef O_CLOEXEC //# define O_CLOEXEC 02000000 //#endif int main() { int pid,fd,status; char *buf[3]={"before fork\n","parent\n","child\n"}; fd=open("a.txt",O_WRONLY|O_APPEND); // |O_CLOEXEC); //fcntl(fd,F_SETFD,FD_CLOEXEC); write(fd,buf[0],12); pid=fork(); if (pid > 0) { wait(&status); write(fd,buf[1],7); } else { execl("./progr","progr",buf[2],NULL); _exit(1); } return 0; }

progr.c progr
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main(int argc,char *argv[]) { int fd=3; write(fd,argv[1],6); return 0; }

Variabile de mediu
extern char **environ;

Un vector de pointeri la iruri de caractere, ce conin variabilele de mediu i valorile lor. Vectorul e terminat cu NULL. irurile de caractere sunt de forma "VARIABILA=VALOARE". getenv ntoarce valoarea variabilei de mediu denumite name, sau NULL daca nu exist o variabil de mediu denumita astfel:
char* getenv(const char *name);

setenv adauga n mediu variabila cu numele name (daca nu exista deja) i i seteaza valoarea la value. Daca variabila exista i replace e 0, actiunea de setare a valorii variabilei e ignorata; daca replace e diferit de 0, valoarea variabilei devine value:
int setenv(const char *name, const char *value, int replace);

unsetenv sterge variabila denumita name din mediu:


int unsetenv(const char *name);

Exemplu
int main() { char * pPath; pPath = getenv ("PATH"); if (pPath!=NULL) printf ("The current path is: %s",pPath); return 0; }

Forking
Funciile de bibliotec fork(), vfork() i _clone() sunt implementate n Linux prin apelul de sistem clone(), care, la rndul su apeleaz do_fork(). user-space fork() vfork() _clone() kernel-space clone() do_fork() copy_process()

Copy_process() : apeleaz dup_task_struct(), care creeaz o nou stiv precum i dou structuri una pentru proces (task_struct) i alta pentru firul de execuie aferent (thread_info) cu valori identice cu ale procesului printe; n acest moment cele 2 procese sunt identice; verific disponibilitatea alocrii unei identiti unice procesului creat; apeleaz copy_flags() pentru actualizarea flag-urilor; se seteaz PF_FORKNOEXEC; apeleaz alloc_pid() pentru a atribui un PID nou procesului creat; n funcie de parametrii transmii la apelul clone(), se vor copia sau partaja resurse precum fiiere deschise, informaii despre sistemul de fiiere, rutine de tratare a semnalelor, spaiul de adrese; returneaz un pointer ctre noul proces creat.

vfork() Apelul vfork() are acelai efect cu meniunea c resursele procesului printe nu sunt duplicate. Procesul copil se execut n spaiul de adrese al printelui, care devine astfel blocat pn cnd copilul i ncheie execuia sau invoc apelul exec(). Avnd astfel garania c procesul copil i ncepe primul execuia are loc o utilizare foarte eficient a resurselor n sensul c, n cazul execuiei apelului exec() pentru ncrcarea unui nou program n cadrul procesului copil, exec() va fi chiar prima instruciune executat dup ntoarcerea din fork()/vfork() i, astfel, nu se vor mai duplica resurse inutil. Comportamentul este nedefinit dac procesul creat nu termin execuia cu apelul _exit(). Rulai exemplul de mai jos, adugnd/eliminnd secvenele comentate.
int main() { int pid,var=5; printf("parinte? ->PID=%d PPID=%d-------var=%d\n",getpid(),getppid(),var); pid=vfork(); //pid=fork(); if (pid > 0) { //sleep(3); printf("if -> parinte? ->PID=%d PPID=%d------- var =%d\n",getpid(),getppid(),var); } else { printf("if -> fiu? ->PID=%d PPID=%d------ var =%d\n",getpid(),getppid(),var); var ++; printf("if -> fiu? ->PID=%d PPID=%d------ var =%d\n",getpid(),getppid(),var); execl("/bin/ls", "ls", "-la", NULL); //_exit(0); }

return 0; }

Exiting
Un proces i poate termina execuia: involuntar cnd primete un semnal sau o excepie pe care nu o poate trata; voluntar la apelul exit().

Indiferent de situaie, apelul de sistem implicat este do_fork() care: - marcheaz task_struct ca aparinnd unui proces n curs de ieire; - anuleaz toate timerele active; - elibereaz zona de memorie; dac nu este partajat cu un alt proces, kernelul o elibereaz complet; - scoate procesul din cozile de ateptare ale semafoarelor; - elibereaz structurile pentru lucrul cu fiiere (descriptori); - seteaz, n cadrul task_struct, valoarea de ieire dat de funcia exit() sau alt mecanism de terminare; - transmite un semnal de ieire printelui; dac acesta nu rspunde i transfer copiii ctre init i seteaz starea de ieire n task_struct la valoarea EXIT_ZOMBIE; - anuleaz intrrile din coada de ateptare a programatorului de procese (nu mai trebuie programat pentru execuie). n acest moment, toate obiectele asociate cu procesul sunt eliberate, acesta nemaiputnd fi rulat (este n starea zombie). Singurele resurse ocupate sunt structurile thread_info i task_struct, singurul scop al existenei acestora fiind acela de a oferi informaii pentru procesul printe. Structurile sunt eliberate dup ce printele preia informaiile de ieire, moment la care este eliberat i PID-ul procesului terminat. Zombie
int main() { int pid; printf("parinte? ->PID=%d PPID=%d\n",getpid(),getppid()); pid=fork(); if (pid > 0) { while(1){} } else { printf("if -> fiu? ->PID=%d PPID=%d\n",getpid(),getppid()); _exit(0); } return 0; }

> top pPID_COPIL

Depanarea unui proces


Pe majoritatea sistemelor de operare pe care a fost portat, gdb nu poate detecta cnd un proces realizeaz o operaie fork(). Atunci cnd programul este pornit, depanarea are loc exclusiv n procesul initial, procesele copii nefiind ataate debugger-ului. n acest caz, singura soluie este introducerea unor ntrzieri n execuia procesului nou creat (de exemplu, prin apelul de sistem sleep()), care sa ofere programatorului suficient timp pentru a ataa manual gdb-ul la respectivul proces, presupunnd ca i-a aflat PID-ul n prealabil. Pentru a ataa debugger-ul la un proces deja existent, se folosete comanda attach, n felul urmator:
(gdb) attach PID

Aceasta metoda este destul de incomoda i poate cauza chiar o funcionare anormal a aplicaiei depanate, n cazul n care necesitile de sincronizare ntre procese sunt stricte (de exemplu operaii cu timeout). Din fericire, pe un numr limitat de sisteme, printre care i Linux, gdb permite depanarea comod a programelor care creeaz mai multe procese prin fork() i vfork(). Pentru ca gdb s urmreasc activitatea proceselor create ulterior, se poate folosi comanda set follow-fork-mode, n felul urmator:
(gdb) set follow-fork-mode mode

unde mode poate lua valoarea parent, caz n care debugger-ul continua depanarea procesului parinte, sau valoarea child, i atunci noul proces creat va fi depanat n continuare. Se poate observa c n aceast maniera debugger-ul este ataat la un moment dat doar la un singur proces, neputnd urmri mai multe simultan. Cu toate acestea, gdb poate ine evidena tuturor proceselor create de ctre programul depanat, dei n continuare numai un singur proces poate fi rulat prin debugger la un moment dat. Comanda set detach-on-fork realizeaz acest lucru:
(gdb) set detach-on-fork mode

unde mode poate fi on, atunci cnd gdb se va ataa unui singur proces la un moment dat (comportament implicit), sau off, caz n care gdb se ataeaz la toate procesele create n timpul execuiei, i le suspend pe acelea care nu sunt urmrite, n funcie de valoarea setrii follow-fork-mode. Comanda info forks afieaza informaii legate de toate procesele aflate sub controlul gdb la un moment dat:
(gdb) info forks

De asemenea, comanda fork poate fi utilizat pentru a seta unul din procesele din list drept cel activ (care este urmrit de debugger).
(gdb) fork fork-id

unde fork-id este identificatorul procesului, aa cum apare n lista afiata de comanda info forks. Atunci cnd un anumit proces nu mai trebuie urmrit, el poate fi nlturat din list folosind comenzile detach fork i delete fork:
(gdb) detach fork fork-id (gdb) delete fork fork-id

Diferena dintre cele dou comenzi este c detach fork las procesul s ruleze independent, n continuare, n timp ce delete fork l ncheie. Pentru a ilustra aceste comenzi ntr-un exemplu concret, sa considerm programul urmtor: Exemplu fork-debug
// forktest.c 1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/wait.h> 4 #include <unistd.h> 5 6 7 int main(int argc, char **argv) { 8 pid_t childPID = fork(); 9 10 if (childPID < 0) { 11 // An error occured 12 fprintf(stderr, "Could not fork!\n"); 13 return -1; 14 } else if (childPID == 0) { 15 16 // We are in the child process 17 printf("The child process is executing...\n"); 18 sleep(2); 19 20 } else { 21 22 // We are in the parent process 23 if (wait(NULL) < 0) { 24 fprintf(stderr, "Could not wait for child!\n"); 25 return -1; 26 } 27 printf("Everything is done!\n"); 28 29 } 30 31 return 0; 32 }

Dac vom rula programul cu parametrii implicii de depanare, vom constata ca gdb va urmri exclusiv execuia procesului printe:
$ gcc -O0 -g3 -o forktest forktest.c $ gdb ./forktest [...] (gdb) run Starting program: /home/student/forktest The child process is executing... Everything is done! Program exited normally.

Plasm cte un breakpoint n codul asociat procesului printe, respectiv procesului copil, pentru a evidenia mai bine acest comportament:
(gdb) break 17 Breakpoint 1 at 0x8048497: file forktest.c, line 17. (gdb) break 27 Breakpoint 2 at 0x80484f0: file forktest.c, line 27. (gdb) run Starting program: /home/student/forktest The child process is executing...

Breakpoint 2, main () at forktest.c:27 27 printf("Everything is done!\n"); (gdb) continue Continuing. Everything is done! Program exited normally.

Setm debugger-ul s urmreasc procesele copil, i observm c de data aceasta cellalt breakpoint este atins:
(gdb) set follow-fork-mode child (gdb) run Starting program: /home/student/forktest [Switching to process 6217] Breakpoint 1, main () at forktest.c:17 17 printf("The child process is executing...\n"); (gdb) continue Continuing. The child process is executing... Program exited normally. Everything is done!

Observai ca ultimele dou mesaje au fost inversate, fa de cazul precedent: debugger-ul ncheie procesul copil, apoi procesul printe afieaz mesajul de final (Everything is done!).

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