Sunteți pe pagina 1din 16

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 natere 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.

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


-

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.

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

pid_t getppid(void)
uid_t getuid(void)

- returneaza identificatorul utilizatorului care a lansat procesul curent

gid_t getgid(void)

procesul curent

- returneaza PID-ul parintelui procesului curent


- returneaza identificatorul grupului utilizatorului care a lansat

n noul proces (fiu) toate vechile variabile i pstreaz valorile, toi descriptorii de
fiier 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));
procesul printe

//adugai

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){}
//n procesul copil
>ps ef
>kill SIGUSR1 pid_copil
//n terminal
if(WIFSIGNALED(status))
printf("%d terminat cu semnalul %d\n",pidex,WTERMSIG(status));
//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 ctre
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
transmiterea parametrilor. Sintaxele lor sunt:

mai

mult

flexibilitate

la

(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 dintr-un 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");
}

care este echivalent cu


$ sh -c "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");
}

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
#include
#include
#include
#include
#include

<stdio.h>
<unistd.h>
<sys/wait.h>
<sys/types.h>
<sys/stat.h>
<fcntl.h>

progr.c progr

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc,char *argv[])


{
int fd=3;

//#ifndef O_CLOEXEC
//# define O_CLOEXEC 02000000
//#endif

write(fd,argv[1],6);
return 0;
}

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;
}

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()
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.

pentru

actualizarea

flag-urilor;

se

seteaz

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 debuggerului. 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 time-out).
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