Sunteți pe pagina 1din 47

8.

CONTROLUL PROCESELOR
Ultimul capitol a definit contextul unui proces i a explicat algoritmii care l
manipuleaz; acest capitol va descrie utilizarea i implementarea apelurilor sistem
care controleaz contextul unui proces. Apelul sistem fork creaz un nou proces,
apelul exit termin executia unui proces i apelul wait permite unui proces printe si sincronizeze execuia cu terminarea (exit) unuia (sau toi) dintre procesele fii.
Semnalele informeaz procesele despre apariia evenimentelor asincrone. Deoarece
nucleul (kernel) sincronizeaz execuia apelurilor sistem wait i exit prin intermediul
semnalelor, capitolul prezint semnalele naintea apelurilor sistem wait i exit. Apelul
sistem exec permite unui proces s lanseze n execuie un "nou" program,
suprapunnd peste spaiul propriu de adrese imaginea unui fiier executabil. Apelul
sistem brk permite unui proces s aloce mai mult memorie n mod dinamic; similar,
sistemul permite stivei utilizator s creasc n mod dinamic prin alocarea unui spaiu
suplimentar cnd este necesar, folosind aceleai mecanisme ca i pentru brk. Spre
final, capitolul prezint pe scurt construcia principalelor bucle ale shell-lui i init-lui.
Figura 8.1 arat relaiile existente ntre apelurile sistem decrise n acest capitol
i algoritmii de gestiune ai memoriei descrii n ultimul capitol. Dei aproape toate
apelurile sistem utilizeaz sleep i wakeup, acest lucru nu este artat n figur. n plus,
exec interacioneaz cu algoritmii sistemului de fiiere descrii n capitolele 4 i 5.

Apelurile sistem care refer

Apelurile sistem care refer

managementul memoriei

sincronizarea

fork

exec

dupreg

detachre

attachre

allocreg

brk

growreg

exit

wai

signa

kill

setpgrp

Altele

setuid

detachre
g

attachre
g
growreg
loadreg
mapreg
Fig. 8.1. Apelurile sistem ale proceselor i relaiile cu ali algoritmi

8.1. Crearea proceselor

181

Singurul mod prin care un utilizator poate crea un nou proces n UNIX este
folosirea apelului sistem fork. Procesul care apeleaz fork este numit proces printe
(parent), iar noul proces creat este numit proces fiu (child). Sintaxa pentru apelul
sistem fork este:
pid=fork();
La revenirea din apelul sistem fork, dou procese au copii identice ale contextului la
nivel utilizator, excepie fcnd valoarea de retur a pid-ului. n procesul printe, pid-ul
are valoarea identificatorului procesului fiu; n procesul fiu, pid-ul are valoarea zero.
Procesul 0, creat intern de ctre kernel cnd sistemul este iniializat este singurul
proces care nu este creat prin intermediul apelului sitem fork.
Nucleul execut urmtoarea secven de operaii la apelul sistem fork:
1.

Aloc o intrare n tabela proceselor penrtru noul proces.

2.

Atribuie un identificator unic procesului fiu.

3.
Face o copie logic a contextului procesului printe. Deoarece n mod sigur
poriuni ale procesului, cum ar fi zona de cod, pot fi mprite ntre procese, kernel
poate uneori incrementa numrul de referiri al unei regiuni n schimul copierii regiunii
la o nou locaie fizic n memorie.
4.

Incrementez contorii tabelei de inoduri i fiiere asociate procesului.

5.
ntoarce n procesul printe numrul identificatorului atribuit procesului fiu i
valoarea zero n procesul fiu.

Implementarea apelului sistem fork nu este trivial deoarece procesul fiu pare a-i
incepe secvena de execuie dintr-un punct aflat n aer. Algoritmul pentru fork difer
puin de la sistemul cu paginare la cerere la sistemul cu swapping; discuia care
urmeaz se bazeaz pe sistemul traditional de swapping dar va sublinia locurile n care
apar schimbri pentru sistemul cu paginare la cerere. De asemenea se presupune c
sistemul are suficient memorie pentru a pstra procesul fiu. Capitolul 9 va considera
cazul n care nu exist suficient memorie pentru procesul fiu i descrie implementarea
apelului sistem fork ntr-un sistem bazat pe paginare la cerere.
algoritm fork
intrri: niciuna
ieiri: la procesul printe, identificatorul fiului (PID)
la procesul fiu, 0
{
verific resursele disponibile ale nucleului;
obine o intrare liber n tabela proceselor, un unic numr pentru
PID;
verific ca utilizatorul s nu ruleze prea multe procese;
marcheaz starea procesului ca "fiind creat";
copiaz datele din intrarea tabela procese corespunztoare procesului
printe n

noua intrare a fiului;


182

incrementeaz contorul inodului directorului curent i a rdcinii


schimbate (dac este necesar);
face o copie a contextului printelui (u area, text, date, stiv) n
memorie;
depune contextul dummy al nivelului sistem n contextul nivelului
sistem al fiului;
contextul dummy conine date care permit procesului fiu s se
recunoasc singur i s nceap s ruleze de aici atunci cnd este programat;
if (procesul n execuie este procesul printe)
{
schimb starea procesului n "gata de rulare";
ntoarce (idetificatorul fiului); /* dinspre sistem ctre utilizator */
else /* procesul care se execut este procesul fiu */
{
iniializeaz cmpurile de timp ale u area;
return (0);
}
}
Figura 8.2. Algoritmul pentru Fork
Figura 8.2 prezint algoritmul pentru fork. Nucleul se asigur mai nti dac are
resurse disponibile pentru a termina cu succes apelul sistem fork. ntr-un sistem bazat
pe swapping, acesta are nevoie de spaiu n memorie sau pe disk pentru a pstra
procesul fiu; ntr-un sistem bazat pe paginare la cerere, acesta trebuie s aloce
memorie pentru tabelele auxiliare cum ar fi tabelele de pagini. Dac nu sunt resurse
disponibile, apelul sistem fork d gre. Nucleul gsete o intrare n tabela proceselor
pentru a ncepe construcia contextului procesului fiu i se asigur c utilizatorul care a
apelat fork nu are deja prea multe procese n curs de execuie. De asemenea
selecteaz un unic identificator pentru noul proces, acesta trebuind s fie mai mare
dect cel mai recent atribuit. Dac un alt proces deine deja acel numr identificator,
nucleul ncearc s atribuie urmtorul numr identificator mai mare. Cnd numerele
identificator ajung la valoarea maxim, atribuirea acestora ncepe din nou de la zero.
Deoarece cele mai multe procese se execut pentru scurt timp, cele mai multe numere
identificator nu sunt utilizate atunci cnd atribuirea identificatorilor se reia.
Sistemul limiteaz numrul proceselor care se pot executa simultan de ctre un
utilizator astfel c acesta nu poate ocupa multe intrri din tabela proceselor, lucru care
ar duce la mpiedicarea altor utilizatori de a crea procese noi. De asemenea utilizatorii
obinuii nu pot crea un proces care ar duce la ocuparea ultimei intrri din tabela
proceselor, altfel sistemul s-ar putea bloca. Deci, dac nici un proces nu se va termina
n mod natural (exit), nu se va putea crea un nou proces deoarece nu exist nici o
intrare liber n tabela proceselor. Pe de alt parte, un administrator de reea poate
executa un numr nelimitat de procese, numrul acestora fiind limtat doar de mrimea
tabelei de procese i, trebuie menionat faptul c acesta poate ocupa ultima intrare
liber din tabela proceselor. Administratorul de reea are, astfel, posibilitatea de a
183

lansa un proces care s foreze celelalte procese s se termine (vezi Seciunea 8.2.3
pentru apelul sistem kill).
Mai departe nucleul iniializeaz intrarea din tabela proceselor pentru procesul fiul
creat prin copierea diferitelor cmpuri din intrarea procesului printe. De exemplu,
procesul fiu motenete numerele identificatorilor utilizator efectiv i real ai procesului
printe i valoarea nice a acestuia, utilizat pentru calcularea prioritii de planificare.
Nucleul depune identificatorul procesului printe n intrarea fiului, pune fiul n structura
arborescent a proceselor i iniializeaz diferii parametri de planificare, cum ar fi
valoarea prioritii iniiale, folosirea iniial a C.P.U. i alte cmpuri de timp. Starea
iniial a procesului este "n curs de creare" (revezi Figura 6.1).
Nucleul ajusteaz acum contoarele de referin pentru fiierele cu care procesul fiu
este automat asociat. Procesul fiu se afl n directorul curent al procesului printe.
Numrul proceselor care au acces la director va fi incrementat cu 1 i, n consecin,
nucleul incrementeaz contorul de referin al inodului. Apoi, dac procesul printe
sau unul din strmoi executase apelul sistem chroot pentru a schimba rdcina,
procesul fiu motenete schimbarea rdcinii i incrementeaz contorul de referin al
inodului respectiv. n final, nucleul caut n tabela cu descriptori fiier ai utilizatorului
(UFDT) specific procesului printe, gsete intrrile pentru fiierele deschise,
cunoscute de ctre proces i incrementeaz contoarele de referine al tabelei globale
de fiire (FT) asociate fiierelor deschise. Procesul fiu nu numai c motenete
drepturile de acces la fiierele deschise, dar i mparte accesul la fiiere cu procesul
printe deoarece ambele procese manipuleaz aceleai intrri din tabela fiierelor.
Efectul apelului sistem fork este identic cu cel al dup-ului vis-a-vis de fiierele
deschise: o nou intrare n UFDT va adresa o intrare din FT pentru un fiier deschis.
Totui, pentru dup, intrrile din UFDT sunt asociate unui singur proces, pe cnd pentru
fork, ele sunt n procese diferite.
Nucleul este acum gata pentru a crea contextul nivelului utilizator al procesului fiu.
Nucleul aloc memorie pentru u. area, regiunile i tabelele de pagini (PT) auxiliare ale
procesului fiu, duplic fiecare regiune din procesul printe folosind algoritmul dupreg i
ataeaz fiecare regiune la procesul fiu folosind algoritmul attachreg. n sistemul bazat
pe swapping, acesta copiaz coninutul regiunilor care nu sunt folosite n comun ntr-o
nou zon (area) a memoriei principale. n seciunea 6.2.4 s-a artat c u. area
conine un pointer la intrarea asociat din PT. Exceptnd acest cmp, coninutul u.
area a fiului este iniial identic cu cel al u. area a printelui, dar ele pot s difere dup
terminarea apelului sistem fork. De exemplu procesul printe poate deschide un nou
fiier dup terminarea apelului sistem fork, dar procesul fiu nu poate avea automat
acces la el.
Pn n prezent, nucleul a creat poriunea static a contextului procesului fiu; acum
acesta creaz poriunea dinamic a acestuia. Nucleul copiaz nivelul 1 al contextului
printelui, coninnd contextul regitrilor salvai i structura stivei nucleului ale
apelului sistem fork. Dac implementarea este una n care stiva nucleului este parte a
u. area, nucleul creaz automat stiva nucleu a fiului cnd acesta creaz u. area a
fiului. Altfel, procesul printe trebui s copieze propria stiv nucleu ntr-o zona privat
a memoriei asociate cu procesul fiu. n ambele cazuri, stivele nucleu pentru procesele
fiu i printe sunt identice. Nucleul creaz apoi un context pe nivel dummy (2) pentru
procesul fiu, coninnd contextul regitrilor salvai pentru nivelul context (1). Acesta
seteaz contorul de program (PC) i ali regitri n contextul regitrilor salvai astfel
nct s poat reface contextul fiului, dei acesta nu a fost executat nainte i de aceea
procesul fiu se poate recunoate singur ca fiind un proces fiu cnd acesta ruleaz. De
exemplu, dac codul nucleul testeaz valoarea registrului 0 pentru a decide dac
procesul este printe sau fiu, acesta scrie valoarea corespunztoare n contextul
regitrilor salvai corespunztor fiului n nivelul 1. Mecanismul este similar cu cel
discutat pentru o comutare de context discutat n capitolul anterior.

184

Cnd contextul fiului este gata, printele termin partea sa a apelului fork prin
schimbarea strii fiului n "gata de rulare (n memorie)" i prin ntoarcerea spre
utilizator a identificatorului de proces al fiului. Nucleul programeaz mai trziu
procesul fiu pentru execuie cu ajutorul algoritmului normal de planificare i procesul
fiu i termin astfel partea sa de fork. Contextul procesului fiu a fost completat de
ctre procesul tat; n nucleu, procesul fiu pare a fi trezit dup ateptarea unei
resurse. Procesul fiu execut poriunea de cod din apelului sistem fork, n acord cu
contorul programului, pe care nucleul l-a refcut din contextul regitrilor salvai aflat n
nivelul context 2 i ntoarce un 0 din apelul sistem.
Figura 8.3 d o imagine logic a proceselor printe i fiu i a relaiilor lor cu alte
structuri de date ale nucleului, imediat dup terminarea apelului sistem fork.
Concluzionnd, ambele procese mpart fiiere pe care printele le-a deschis n timpul
apelului sistem fork i contorul de referine al FT pentru aceste fiiere crete cu 1.
Similar, procesul fiu are acelai director curent i aceeai rdcin ca a printelui i
contoarele de referine ale inodurilor aparinnd acestor directoare cresc cu valoarea
1. Procesele au copii identice ale regiunilor de text, date i stiv utilizator; tipul
regiunii i implementarea sistemului stabilete dac procesele pot mpri o copie fizic
a regiunii de text.
Considerm programul din figura 8.4, un exemplu de partajare a accesului la fiiere pe
timpul executrii apelului sistem fork. Un utilizator poate apela programul cu doi
parametri, numele unui fiier existent i numele unui nou fiier ce va fi creat. Procesul
deshide fiierul existent, creaz noul fiier i presupunnd c nu apar erori, execut
fork i creaz un proces fiu.

Intern, nucleul face o copie a contextului printelui pentru procesul fiu iar procesul
printe i procesul fiu se execut n spaii de adrese diferite. Fiecare proces poate
accesa copiile private ale variabilelor globale fdrd, fdwt i c i copiile private ale
variabilelor de stiv argc i argv, dar nici un proces nu poate accesa variabilele celuilalt
proces. Cu toate acestea, nucleul copiaz u. area a procesului original n procesul fiu n
timpul apelului sistem fork i fiul astfel motenete dreptul de acces la fiierele
printelui (acestea sunt fiierele originale pe care printele le-a deschis i creat)
folosind aceiai descriptori de fiiere.
#include <fentl.h>
int fdrd, fdwt;
char c;
main (argc, argv)
int argc;
char *argv[];
{
if (argc!=3)
185

exit (1);
if (fdrd=open(argv[1],

O_RDONLY))== -1)

exit(1);
if (fdwt=creat(argv[2], 0666))== -1)
exit (1);
fork ();
/* ambele procese execut accelai cod */
rdwrt ();
exit (0);
}
rdwrt ()
{
for (;;)
{
if (read (fdrd, &c,1)!=1)
return;
write (fdwt ,&c,1);
}
}
Figura 8.4. Program n care printele i fiul au acces partajat la fiiere
Procesele printe i fiu apeleaz funcia rdwrt independent, apoi execut un ciclu,
citind cte un octet din fiierul surs i scriindu-l apoi n fiierul destinaie. Funcia
rdwrt se ntoarce atunci cnd apelul sistem read ntlnete sfritul fiierului. Nucleul
incrementase contoarele fiierelor surs i destinaie din tabela de fiiere i descriptorii
fiierelor din ambele procese refer aceeai intrare din tabela fiierelor. Aa c,
descriptorii de fiier fdrd pentru ambele procese refer aceeai intrare din tabela
fiierelor pentru fiierul destinaie i descriptorii de fiier fdwr pentru ambele procese
refer aceeai intrare din tabela fiierelor pentru fiierul surs. De aceea, cele dou
procese nu vor citi sau scrie niciodat de le (la) aceleai valori de offset ale fiierului,
deoarece nucleul incrementeaz aceste valori dup fiecare apel de citire sau scriere.
Dei procesele par s copieze fiierul surs de dou ori mai repede, coninutul fiierului
surs depinde de ordinea n care nucleul a planificat procesele. Dac acesta planific
procesele astfel nct ele s alterneze n executarea propriilor apeluri sistem, sau chiar
dac alterneaz execuia perechii read-write dintr-un proces, coninutul fiierului
destinaie va fi identic cu coninutul fiierului surs. Dar s considerm urmtorul
scenariu, n care procesele sunt gata s citeasc o scecven de dou caractere "ab"
din fiierul surs. Presupunem c procesul printre citete caracterul 'a', dup care
nucleul face o comutare de context pentru a executa procesul fiu nainte ca printele
s scrie. Dac procesul fiu citete caracterul 'b' i l scrie n fiierul destinaie nainte ca
printele s fie reprogramat, fiierul destinaie nu va conine irul "ab", ci "ba". Nucleul
nu garanteaz ritmul (rata) de programare a execuiei proceselor.
Acum s considerm programul din figura 8.5, care motenete descriptorii de fiiere 0
i 1 (intrarea i ieirea standard) de la printele su. Execuia fiecrui apel sistem pipe
186

aloc n plus 2 descriptori de fiiere n irurile to_par i to_child. Procesul execut fork
i face o copie a contextului su: fiecare proces poate accesa datele proprii, ca n
exemplul anterior. Procesul printe nchide fiierul su standard de ieire (descriptorul
de fiier 1) i duplic (dup) descriptorul de scriere ntors de pipe pentru to_child.
Deoarece primul slot liber din UFDT-ul printelui este chiar acela eliberat prin apelul
sistem close, nucleul copiaz descriptorul de scriere rezultat n urma executrii
apelului sistem pipe n slotul 1 al UFDT-lui i astfel descriptorul fiierului standard de
ieire devine descriptorul de scriere al pipe-lui pentru to_child. Procesul printe face
operaii similare astfel nct descriptorul intrrii standard s devin descriptorul de
citire al pipe-lui pentru to_par. Analog, procesul fiu i nchide fiierul standard de
ieire (descriptor 0) i duplic descriptorul de citire al pipe-lui pentru to_child. ntruct
primul slot liber n UFDT este slotul fostuului fiier standard de intare, intrarea
standard a fiului devine descriptorul de citire al pipe-lui pentru to_child. Fiul face
operaii similare astfel nct descriptorul ieirii standard s devin descriptorul de
scriere al pipe-lui pentru to_par. Ambele procese nchid descriptorii de fiiere ntori de
apelarea lui pipe ( o bun metod de programare ). Ca rezultat, cnd printele scrie
la ieirea standard, acesta scrie n pipe-ul to_child i trimite date procesului fiu, care
citete pipe-ul ca pe propria intrare standard. Cnd procesul fiu scrie la ieirea
standard, acesta scrie n pipe-ul to_par i trimite datele procesului printe care citete
pipe-ul ca pe propria intrare standard. Procesele astfel schimb mesaje prin
intermediul pipe-urilor.
Rezulatele acestui exemplu sunt aceleai, indiferent de ordinea n care procesele i
execut apelurile sistem respective. Adic nu este nici o diferen, dac printele se
ntoarce din fork naintea fiului sau dup el. Similar, nu este nici o diferen fa de
ordinea relativ n care procesele i esxecut apelurile sistem pn cnd acestea intr
n ciclu: structurile nucleului sunt identice. Dac procesul fiu execut apelul sistem
read nainte ca procesul printe s fac write, procesul fiu va intra n ateptare pn
cnd procesul printe scrie pipe-ul i este trezit. Dac procesul printe scrie pipe-ul
nainte ca procesul fiu s citeasc pipe-ul, printele nu va citi de la intrarea sa
standard pn cnd fiul nu de la citete intrarea sa standard i nu scrie la ieirea sa
standard. Din acest motiv, ordinea de execuie este fixat: fiecare proces termin un
apel sistem read i write i nu poate termina urmtorul apel sistem read pn cnd
cellalt proces nu termin un apel sistem read i write.
#include <string.h>
char string[]=" hello word";
main ()
{
int count,i;
int to_par[2], to_chil[2]; /* pentru pipe-urile printelui i fiului */
char buff[256];
pipe(to_par);
pipe(to_chil);
if(fork() ==0)
{
/* procesul fiu se execut aici */
close(0);

/* nchide vechea intrare standard

*/

187

dup(to_chil[0]);

/* duplic descriptorul de citire al pipe-lui la


/*

intrarea standard */
close(1);

/* nchide vechea intrare standard

*/
dup(to_par[1]);/* duplic descriptorul de scriere al pipe la ieirea
standard */
close(to_par[1]);

/* nchide descriptorii pipe-lui nefolosii */

close(to_chil[0]);
close(to_par 0]);
close(to_chil[1]);
for(;;)
{
if (count=read(0, buf, sizeof(buf)))==0)
exit ();
write(1, buf, count);
}
}
/* procesul printe se execut aici */
close(1);

/* rearanjeaz intrrile i ieirile standard

*/
dup(to_chil [1]);
close(0);
dup(to_par [0]);
close(to_chil [1]);
close(to_par [0]);
close(to_chil [0]);
close(to_par [1]);
for(i=0; i<15; i++)
{
write(1, string, strlen(string));
read(0, buf, sizeof(buf));
}
}
Figura 8.5. Utilizarea Pipe-lui, Dup i Fork

Printele face exit dup cele 15 iteraii aflate n ciclu; apoi fiul citete "sfrit de fiier"
(EOF), deoarece pipe-ul nu are procese care s scrie, i termin. Dac fiul ar fi scris n
pipe dup ce printele a fcut exit, primul ar fi recepionat un semnal care indic
188

scrierea ntr-un pipe fr procese cititoare. Am menionat mai devreme c o metod


bun de programare este aceea de a nchide descriptorii de fiiere inutili. Acest lucru
este adevrat din trei motive. Primul, se conserv descriptorii de fiiere n limitele
impuse de sistem. n al doilea rnd, dac un proces fiu apeleaz execs, descriptorii
fiierelor rmn asignai n noul context, aa cum se va vedea. nchiderea fiierelor
neeseniale nainte de a face un exec permite programelor s ruleze ntr-un mediu
curat, numai cu descriptorii fiierelor standard de intrare, ieire i eroare deschii. n
final, citirea dintr-un pipe ntoarce "sfrit de fiier" doar dac nici un proces nu are
pipe-ul deschis pentru scriere. Dac procesul cititor pstreaz descriptorul de scriere al
pipe-lui deschis, acesta nu va ti niciodat cnd procesele care scriu nchid pipe-ul.
Exemplul de mai sus nu ar lucra corect dac procesul fiu nu i-ar nchide (close)
descriptorii de scriere ai pipe-lui naintea intrrii n ciclu.

8.2. Semnale
Semnalele informeaz procesele despre apariia evenimentelor asincrone. Procesele i
pot trimite semnale prin intermediul apelului sistem kill, sau nucleul poate trimite
intern semnale. Exist 19 semnale n versiunea de UNIX System V (Release 2) care
pot fi clasificate dup cum urmeaz (vezi descrierea semnalelor n [SVID 85]):
1.
Semnale pentru terminarea proceselor, trimise atunci cnd un proces execut
exit sau cnd un proces execut apelul sistem signal cu parametrul "terminare fii";
2.
Semnale pentru de atenionare n cazul excepiilor provocate n procese, de
exemplu cnd acesta acceseaz o adres n afara spaiului su virtual de adrese, cnd
acesta ncearc s scrie n zonele de memorie protejate la scriere (cum ar fi codul
programului), cnd acesta execut o instruciune privilegiat, sau pentru diferite erori
hard;
3.
Semnale pentru situaii fr ieire aprute n timpul apelurilor sistem, cum ar fi
lipsa resurselor n timpul execuiei apelului sistem exec dup ce spaiul iniial de
adrese fusese eliberat (vezi Paragraful 8.5);
4.
Semnale determinate de condiii de eroare neateptate aprute n timpul unui
apel sistem, cum ar fi executarea unui apel sistem inexistent (procesul a furnizat
numrul unui apel sistem care nu corespunde unui apel sistem corect), scrierea unui
pipe care nu are proces cititor, sau folosirea unei valori de "referin" ilegale pentru
apelul sistem lseek. Ar fi mult mai comod s se ntoarc o eroare n apelurile sistem n
schimbul generrii unui semnal, dar folosirea semnalelor pentru a abandona procesele
care nu se comport corespunztor este mult mai practic (folosirea semnalelor n
aceste cazuri suplinete lipsa verificrii erorilor de ctre programator la apelurile
sistem) ;
5.
Semnale provenite de la procesele n modul utilizator, de exemplu cnd un
proces dorete s recepioneze un semnal alarm dup un anumit timp, sau cnd
procesele trimit unul altuia semnale n mod arbitrar, prin intermediul apelului sistem
kill;
6.
Semnale provenite de la interaciunea cu un terminal, atunci cnd un utilizator
nchide un terminal (sau semnalul "purttor" cade din anumite motive), sau cnd un
utilizator apas tastele "break" sau "delete";
8.

Semanale de urmrire a execuiei unui proces;


189

Tratarea semnalelor este diferit dup cum nucleul trimite un semnal unui proces, cum
trateaz procesul un semnal i cum un proces i controleaz reacia la apariia unui
semnal. Pentru a trimte un semnal unui proces, nucleul seteaz un bit n cmpul signal
din intrarea tabelei proceselor, corespunztor tipului semnalului recepionat. Dac
procesul este n ateptare la o prioritate ntreruptibil, nucleul l trezete. Sarcina celui
care trimite (proces sau nucleu) ia sfrit. Un proces poate pstra diferite tipuri de
semnale, dar nu are memorie pentru a pstra numrul de semnale de acelai tip pe
care le recepioneaz. De exemplu, dac un proces recepioneaz un semnal hangup i
un semnal kill, acesta seteaz biii corespunztori n cmpul signal din tabela
proceselor, dar nu poate spune cte instane ale semnalelor au fost recepionate.
Nucleul verific recepionarea unui semnal cnd un proces este gata s se ntoarc din
modul nucleu n modul utilizator i cnd acesta intr sau prsete starea de sleep la o
prioritate sczut programat (vezi figura 8.6). Nucleul trateaz semnalele doar cnd
un proces se ntoarce din modul nucleu n modul utilizator. Astfel, un semnal nu are un
efect imediat ntr-un proces care ruleaz n modul nucleu. Dac un proces ruleaz n
modul utilizator i nucleul trateaz o ntrerupere care presupune trimiterea unui
semnal ctre proces, nucleul va recunoate i trata semnalul cnd se va ntoarce din
ntrerupere. Astfel, un proces niciodat nu se va executa n modul utilizator nainte de
tratarea semnalelor importante.
Figura 8.7 prezint algoritmul pe care nucleul l execut pentru a determina dac un
proces a recepionat un semnal. Cazul semnalelor de tipul "terminare fiu" va fi tratat
mai trziu. Aa cum se va vedea, un proces are posibilitatea de a ignora semnalele
primite prin intermdiul apelului sistem signal. n algoritmul issig, nucleul terge
indicatorii care marcheaz apariia semnalelor pe care procesul vrea s le ignore dar
noteaz existena semnalelor pe care acesta nu le ignor.

algoritmul issig

/* test pentru recepia semnalelor */

intrri: niciuna
ieiri: adevrat, dac procesul recepioneaz semnale pe care nu le
ignor;
fals, n celelalte cazuri
{
while( cmpul semalului recepionat n intrarea din tabela proceselor nu
este 0)
{
gsete numrul semnalului trimis ctre proces;
if( semnalul este de "terminare fiu")
{
if( se ignor semnalul de "terminare fiu")
elibereaz

intrrile

din

tabela

proceselor

corespunztoare fiilor aflai n strile zombie;


else if( intercepteaz semnale de "terminare proces fiu")
return (adevrat);
190

}
else if(dac nu este ignorat semnalul)
return (adevrat);
terge bitul semnalului recepionat din cmpul
semnalului aflat n tabela proceselor;
}
return (fals);
}
Figura 8.8. Algoritm pentru recunoaterea semnalelor

8.2.1.

Tratarea semnalelor

Nucleul trateaz semnalele n contextul procesului care le recepioneaz, deci un


proces trebuie s ruleze pentru a putea trata semnalele. Exist trei cazuri n tratarea
semnalelor: procesul apeleaz exit la recepionarea semnalului, acesta ignor
semnalul, sau execut o funcie particular (definit de utilizator). Aciunea implicit
este de a apela exit n modul nucleu, dar un proces poate specifica o aciune special,
pe care s-o execute la recepionarea unui anumit semnal cu ajutorul apelul sistem
signal.
Sintaxa pentru apelul sistem signal este

oldfunction=signal (signum, function);

unde signum este numrul semnalului pentru care procesul specific aciunea, function
este adresa funciei (utilizatorului) pe care procesul dorete s-o apeleze la recepia
semnalului i ntoarce valoarea oldfunction care a fost valoarea lui function n cel mai
recent apel al lui signal cu parametrul signum. Procesul poate apela signal cu valoarea
1 sau 0 n loc de adresa funciei: procesul va ignora viitoarele apariii ale semnalului
dac valoarea parametrului este 1 (Paragraful 8.4 trateaz cazul special pentru
ignorarea semnalului de "terminare a fiului") i se ntoarce n nucleu dac valoarea lui
este 0 (valoarea implicit). U area conine un ir de cmpuri de tratarea semnalelor,
un cmp pentru fiecare semnal definit n sistem. Nucleul stocheaz adresa funciei
definite de utilizator n cmpul care corespunde numrului semnalului. Modul de
tratare a semnalelor de un anumit tip nu are efect n tratarea semnalelor de alte tipuri.

algoritmul psig

/*

intrri: niciuna
191

ieiri: niciuna
{
obine numrul semnalului setat n intrarea din tabela proceselor;
terge numrul semnalului din intrarea tabelei proceselor;
if( utilizatorul apelase funcia sistemsignal pentru ignorarea acestui
semnal)
return;
if( utilizatorul a specificat funcia care s trateze semnalul)
{
obine adresa virtual utilizator a interceptorului de semnale
pstrat n u area;
terge intrarea din u area care a coninut adresa interceptorului de
semnal;
modific contextul nivelului utilizator:
artificial creaz structura stivei utilizator pentru a mima apelarea
funciei de interceptare a semnalului;
modific contextul nivelului sistem:
scrie adresa interceptorului de semnal n cmpul PC al
contextului regitrilor salvai;
return;
}
if (semnalul este de tipul care face ca sistemul s creeze o imagine
intern unui proces)
{
creaz fiierul numit "core" n directorul curent;
scrie coninutul contextului nivelului utilizator n fiierul "core";
}
apeleaz imediat algoritmul exit;
}
Figura 8.8. Algoritm pentru tratarea semnalelor
Cnd trateaz un semnal (figura 8.8) nucleul determin tipul semnalului i terge bitul
semnalului tratat din intrarea corespunztoare tabelei proceselor, setat cnd procesul
a recepionat semnalul. Dac funcia de tratare a semnalului are valoarea implicit,
nucleul va descrca imaginea din memorie (core) a procesului (vezi exerciiul 8.7)
pentru anumite tipuri de semnale nainte de a face exit. Descrcarea (dump) este o
facilitate pentru programatori, permind acestora s se asigure de motivele
descrcrii i, de aceea, s-i poat depana programele. Nucleul descarc imaginea
procesului din memorie pentru semnalele care sugereaz c ceva este greit n proces,
cum ar fi faptul c procesul execut o instruciune ilegal sau cnd acesta acceseaz o
adres n afara spaiului su virtual de adrese. Dar nucleul nu descarc core-ul pentru
semnale care nu implic o eroare program. De exemplu, recepionarea unui semnal de
192

ntrerupere, trimis cnd un utilizator apas tastele "delete" sau "break" la terminal,
sugereaz c utilizatorul dorete s termine prematur un proces iar recepionarea
semnalului hangup sugereaz c terminalul login nu mai este "conectat". Aceste
semnale nu implic faptul c ceva este greit cu procesul. Semnalul quit, totui,
provoac o descrcare a core-lui chiar dac este iniiat din afar. Uzual trimis prin
apsarea tastei control + bara vertical, semnalul quit permite programatorului s
obin o descrcare a core-lui a procesului n rulare, chiar dac acesta are o bucl
infinit.
Cnd un proces recepioneaz un semnal pe care anterior a decis s-l ignore, acesta
continu ca i cnd semnalul nu ar fi aprut. Deoarece nucleul nu reseteaz cmpul
din u area care arat c semnalul este ignorat, procesul va ignora semnalul i la
apariia ulterioar a acestuia. Dac un proces recepioneaz un semnal pe care acesta
se decisese anterior s-l intercepteze, acesta execut funcia de tratare a semnalului
specificat de utilizator imediat cum se ntoarce n modul utilizator, dup ce nucleul
parcurge urmtorii pai:
1.
Nucleul acceseaz contextul regitrilor salvai al utilizatorului, gsind
numrtorul programului (PC) i pointerul stivei (SP) pe care acesta i salvase pentru
ntoarcerea n procesul utilizator.
2.

Acesta terge cmpul semnalului tratat din u area, setndu-l la starea implicit.

3.
Nucleul creaz un nivel de stivei n stiva utilizator, scriind n aceasta valorile
pentru PC i SP pe care acesta le restabilete din contextul regitrilor salvai al
utilizatorului i aloc spaiu nou, dac este necesar. Stiva utilizator art ca i cum
procesul apelase o funcie la nivel utilizator (interceptorul de semnale) n punctul unde
acesta fcuse apelul sistem sau nucleul l ntrerupsese (nainte de recunoaterea
semnalului).
4.
Nucleul schimb contextul regitrilor salvai al utilizatorului: acesta reseteaz
valorile pentru PC la adresa funciei de interceptare a semnalului i seteaz valoarea
pentru SP pentru a ine evidena creterii stivei utilizatorului.

Dup ntoarcerea din nucleu n modul user, procesul va executa funcia de tratare a
semnalului; cnd acesta termin funcia de tratare a semnalului, se ntoarce n locul
din codul utilizator unde a fost gsit apelul sistem sau s-a produs ntreruperea, imitnd
o ntoarcere din apelul sistem sau ntrerupere.
De exemplu, figura 8.9 conine un program care intercepteaz semnalele de
ntrerupere (SIGINT) i i trimite un semnal de ntrerupere (rezultatul apelului kill);
figura 8.10 conine prile relevante ale unei dezasamblri a modulului ncrcat pe un
VAX 11/780. Cnd sistemul execut procesul, apelul la rutina kill se va face la adresa
ee (n hexazecimal) i rutina execut instruciunea chmk (schimbarea modului n
nucleu) la adresa 10a pentru a face apelul sistem kill. Adresa de retur din apelul
sistem este 10c. n executarea apelului sistem, nucleul trimite un semnal de
ntrerupere ctre proces. Nucleul ia n considerare semnalul de ntrerupere cnd se
ntoarce n modul utilizator, mut adresa 10c din cotextul regitrilor salvai a
utilizatorului i o plaseaz n stiva utilizator. Nucleul ia adresa funciei de interceptare
a semnalului (catcher) 104 i o pune n contextul regitrilor salvai al utilizatorului.
Figura 8.11 arat strile stivei utilizator i contextul regitrilor salvai.
Exist cteva anomalii n algoritmul descris aici pentru tratarea semnalelor. Prima i
cea mai important este c nainte ca un proces s se ntoarc n modul utilizator dup
ce acesta trateaz un semnal, nucleul terge cmpul din u area care conine adresa
funciei de tratare a semnalului utilizator. Dac procesul dorete s trateze semnalul
193

din nou, acesta trebuie s fac apel din nou la funcia sistem signal. Aceasta are din
nefericire ramificaii: o condiie de concuren rezult deoarece a doua instan a
semnalului poate sosi nainte ca procesul s aib ocazia s invoce apelul sistem.
Pentru c procesul este executat n modul utilizator, nucleul ar putea face o comutare
de context, crescnd ansa ca procesul s recepioneze semnalul naintea resetrii
interceptorului de semnal.

#include <signal.h>
main()
{
extern chatcher();
signal(SIGINT, catcher);
kill(0, SIGINT);
}
catcher()
{
}
Figura 8.9. Codul surs al programului care intercepteaz semnale

**** AX DISASSEMBLER ****


_main()
e4:
e6:

pushab

0x18(pc)

ec:

pushl

$0x2

# urmtoarea linie apeleaz signal


ee:

calls

$0x2,0x23(pc)

f5:

pushl

$0x2

f7:

clrl

-(sp)

# urmtoarea linie apeleaz rutina de bibliotec kill


f9:

calls

100:

ret

101:

halt

102:

halt

103:

halt

$0x2,0x8(pc)

_catcher()
104:
194

106:

ret

107:

halt

_kill()
# urmtoarea linie este n nucleu
10a:

chmk

$0x25

10c:

bgequ

0x6 <0x114>

10e:

jmp

0x14(pc)

114:

clrl

r0

116:

ret

Figura 8.10. Dezasamblarea programului care intercepteaz semnale

Programul din figura 8.12 ilustreaz condiia de concuren. Procesul apeleaz signal
pentru a aranja interceptarea semnalului de ntrerupere i execut funcia sigcatcher.
Acesta creaz un proces fiu, invoc apelul sistem nice pentru a-i micora prioritatea
de programare relativ la procesul fiu (vezi cap. 8) i intr ntr-un ciclu infinit. Procesul
fiu i suspend execuia pentru 5 secunde pentru a da procesului printe timp s
execute apelul sistem nice i s-i scad prioritatea. apoi procesul fiu intr ntr-un
ciclu, trimind un semnal de ntrerupere (prin kill) la procesul pinte n timpul fiecrei
iteraii. Dac kill se ntoarce datorit unei erori, probabil datorit procesului printe
care nu mai exist, procesul fiu apeleaz exit. Ideea este c procesul printe ar trebui
s invoce interceptorul de semnale de fiecare dat cnd acesta recepioneaz un
semnal de ntrerupere. Interceptorul de semnale tiprete un mesaj i apeleaz din
nou signal pentru a intercepta urmtoarea apariie a unui semnal de ntrerupere i
printele continu s execute ciclul infinit.

#include <signal.h>
sigcatcher()
{
printf("PID %d se obine \n", getpid());
signal(SIGINT, sigchatcher);
}
main()
{
int ppid;
signal(SIGINT, sigcatcher);
if(fork==0)
{
195

/* tipretePID-ul */

/* d posibilitatea proceselor pentru setri proprii */


sleep(5);

/* funcie care introduce o ntrziere de 5 secunde

*/
ppid=getppid();

/* obine ID-ul printelui */

for(;;)
if(kill(ppid,SIGINT)== -1)
exit();
}
/* prioritate sczut, posibilitate mare de prezentare a cursei */
nice(10);
for(;;)
}
Figura 8.12. Program ce demonstreaz condiiile de concuren n
interceptarea semnalelor

Este posibil s ntlnim, totui, urmtoarea secven de eveniment:


1.

Procesul fiu trimite un semnal de ntrerupere procesului printe.

2.
Procesul printe intercepteaz semnalul i apeleaz interceptorul de semnale
dar nucleul elibereaz (preempt) procesul i comut contextul naintea executrii
apelului sistem signal.
3.
Procesul fiu se execut din nou i trimite un alt semnal de ntrerupere ctre
procesul printe.
4.
Procesul printe recepioneaz al doilea semnal de ntrerupere, dar acesta nu
are fcute setrile pentru a intercepta semnalul. Cnd acesta reia execuia, iese (exit).

Programul a fost scris astfel nct s permit o astfel de comportare deoarece


invocarea apelului sistem nice de ctre procesul printe determin nucleul s
programeze procesul fiu mai frecvent. Totui este neprecizat cnd se va produce acest
efect.
Potrivit lui Ritchie, semnalele au fost proiectate ca evenimente care sunt fatale sau
ignorabile, nu n mod necesar tratate i din acest motiv condiia de concuren nu a
fost fixat n situaiile de eliberare anterioare. Cu toate acestea, pune serioase
probleme programelor care doresc s intercepteze semnale. Problema ar putea fi
rezolvat dac cmpul semnalului nu ar fi ters la recepionarea semnalului. O astfel
de soluie ar putea da natere unei noi probleme: dac semnalele sosesc continuu i
sunt preluate, stiva utilizator ar putea crete peste limita admis datori apelurilor
adunate. Ca alternativ, nucleul ar putea reseta valoarea funciei de tratare a
semnalului astfel nct s ignore semnalele de tipul respectiv pn cnd utilizatorul
precizeaz din nou ce va face cu acestea. O astfel de soluie implic o pierdere de
informaie, deoarece procesul nu are cum s tie cte semnale recepioneaz. Totui,
pierderea de informaie nu este mai mare dect n cazul n care procesul recepioneaz
196

mai multe semnale de un anumit tip nainte ca acesta s le poat trata. n sfrit,
sistemul BSD permite unui proces s blocheze i s deblocheze recepionarea unor
semnale printr-un nou apel sistem; cnd un proces deblocheaz semnalele, nucleul
trimite semnalele care au fost blocate ctre proces. Cnd un proces recepioneaz un
semnal, nucleul blocheaz n mod automat recepionarea altor semnale pn cnd
semnalul a fost tratat complet. Aceast metod este similar modului n care nucleul
reacioneaz la ntreruperile hard: acesta blocheaz transmiterea de noi ntreruperi n
timp ce sunt prelucrate cele anterioare.
O a doua anomalie n tratarea semnalelor se refer la interceptarea semnalelor care
apar n timp ce procesul se afl ntr-un apel sistem, n ateptare (sleep) pe o prioritate
ntreruptibil. Semnalul determin procesul s execute un longjmp din starea de
ateptare, ntoarcerea n modul utilizator i apelarea funciei de tratare a semnalului.
Cnd aceast funcie se termin, procesul pare a se ntoarce dintr-un apel sistem cu o
eroare care indic faptul c apelul sistem a fost ntrerupt. Utilizatorul poate verifica
eroarea ntoars i restarta apelul sistem, dar uneori este mai convenabil dac nucleul
face automat aceast restartare, aa cum este fcut n sistemul BSD.
O a treia anomalie apare n cazul n care un proces ignor un semnal. Dac semnalul
sosete n timp ce procesul se afl n ateptare pe un nivel de prioritate intreruptibil,
procesul se va trezi (wake up) dar nu va executa un longjmp. Aceasta nseamn c
nucleul realizeaz c ignor semnalul doar dup ce l scoate din ateptare i l pune n
execuie. O politic mai bun ar fi ca procesul s fie lsat n ateptare. Cu toate
acestea, nucleul depune adresa funciei semnal n u area i u area nu poate fi accesat
atunci cnd semnalul este trimis procesului. O soluie la aceast problem ar fi
nscrierea adresei funciei semnal n intrarea din tabela proceselor, unde nucleul ar
putea verifica dac procesul trebuie trezit la recepionarea unui semnal. Ca o
alternativ, procesul ar putea imediat s se ntoarc n starea sleep (ateptare)
utiliznd algoritmul sleep, dac i d seama c nu ar fi trebuit scos din ateptare. Cu
toate acestea, procesele utilizator nu realizeaz dac primul proces a fost trezit,
deoarece nucleul nchide intrare n algoritmul sleep ntr-o bucl while (vezi capitolul 2),
punnd procesul napoi n ateptare dac evenimentul sleep nu a aprut de fapt.
n sfrit, nucleul nu trateaz semnalele de "terminare a proceselor fii" (death of child)
la fel ca pe alte semnale. n particular, cnd procesul i d seama c a primit un
semnal de "terminare a proceselor fii" , anuleaz existena semnalului n cmpul de
semnale al tabelei proceselor i n acest caz acioneaz ca i cum nici un semnal nu ar
fi fost trimis. Efectul unui semnal de "terminare a proceselor fii" este trezirea unui
proces n ateptare pe o prioritate ntreruptibil. Dac procesul intercepteaz un
semnal de "terminare proces fiu", se apeleaz funcia de tratare utilizator ca i pentru
alte semnale. Operaiile pe care le face nucleul dac procesul ignor semnalele de
"terminare proces fiu", vor fi discutate n paragraful 8.4. n sfrit, dac un proces
invoc apelului sistem signal cu parametrul "terminare proces fiu", nucleul trimite
procesului apelant un semnal de "terminare proces fiu" dac acesta are procese n
starea zombie. n paragraful 8.4. se discut conveniile pentru apelul signal cu
parametrul "terminare proces fiu".

8.2.2.

Grupe de procese

Dei procesele ntr-un sistem UNIX sunt identificate printr-un numr unic (PID),
sistemul trebuie s identifice uneori procesele dup "grup". De exemplu, procesele cu
un strmo comun care este un login shell sunt n general nrudite i de aceea toate
aceste procese primesc semnale cnd un utilizator apas tastele "Delete" sau "Break"
sau cnd linia ctre terminal este ntrerupt. Nucleul utilizeaz identificatorul de grup
al proceselor pentru a identifica grupurile de procese nrudite care trebuie s
197

primesac un semnal comun pentru evenimente sigure. Acesta salveaz identificatorul


de grup n tabela proceselor; procesele aflate n acelai grup au acelai ID de grup.
Apelul sistem setpgrp iniialiazeaz numrul de grup pentru un proces i-l seteaz ca
fiind egal cu valoarea ID de proces propriu. Sintaxa apelului sistem este:

grp=setpgrp();

unde grp este noul numr al grupului de procese. Un fiu reine numrul de grup de
proces al printelui n timpul apelului sistem fork. Setpgrp are, de asemenea,
importan n setarea terminalului de control al unui proces (vezi paragraful 10.3.5.).

8.2.3.

Trimiterea semnalelor de ctre procese

Procesele trimit semnale folosind apelului sistem kill. Sintaxa pentru aceast funcie
este:
kill (pid, signum)

unde pid identific setul de procese care primesc semnalul i signum este numrul
semnalului trimis. n lista care urmeaz se arat corespondena dintre valorile pidurilor i setul de procese.

Dac pid-ul este un ntreg pozitiv, nucleul trimite un semnal proesului care are
identificatorul de proces pid.

Dac pid-ul este 0, nucleul trimite semnal tuturor proceselor din grupul
procesului care a trimis semnalul.

Dac pid-ul este -1, nucleul trimite semnal tuturor proceselor al cror UID (Uutilizator, ID-identificator) real este egal cu UID-ul efectiv al procesului care a trimis
semnalul (paragraful 8.6. va defini UID-ul real i efectiv). Dac procesul care trimite
semnalul are UID-ul efectiv al superutilizatorului, nucleul trimite semnalul tuturor
proceselor cu excepia proceselor 0 i 1.

Dac pid-ul este un ntreg negativ diferit de -1, nucleul trimite semnalul tuturor
proceselor din grupul de procese avnd valoarea absolut a pid-lui.
n toate cazurile, dac procesul care trimite semnalul nu are UID efectiv al
superutilizatorului, sau UID-ul su efeciv sau real este diferit de UID-ul procesului
destinatar, kill eueaz.
#include <signal.h>
main()
{
register int i;
198

setpgrp();
for(i=0;i<10;i++)
{
if(fork()==0)
{
/* procesul fiu */
if(i&1)
setpgrp();
printf("pid=%d pgrp=%d pgrp=%d\n", getpid(), getpgrp());
pause();

/* se suspend execua apelului sistem */

}
}
kill(0,SIGINT);
}
Figura 8.13. Exemplu de utilizare a lui Setpgrp
n programul din figura 8.13, procesul anuleaz numrul su de grup i creaz 10
procese fii. Cnd sunt creai, fiecare proces fiu are acelai numr de grup cu al
procesului printe, dar procesele create n timpul iteraiilor impare din ciclu reseteaz
numrul de grup. Apelurile sistem getpid i getpgrp ntorc PID-ul i ID-ul grupului
(GID) ale procesului care se execut, iar apelului sistem pause suspend execuia
procesului pn cnd acesta recepioneaz un semnal. La sfrit, procesul printe
execut apelului sistem kill i trimite un semnal de ntrerupere tuturor proceselor din
grupul su de procese. Nucleul trimite semnalul ctre 5 procese "pare" care nu i-au
schimbat propriu grup de procese n timp ce, cele 5 procese "impare" continu ciclul.

8.3. Terminarea proceselor


ntr-un sistem UNIX procesele se termin prin execuia apelului sistem exit. Un proces
care a executat exit intr n starea zombie (vezi figura 6.1), i abandoneaz resursele
i i distruge contextul mai puin intrarea din tabela de procese. Sintaxa pentru apel
este:

exit (status)

unde valoarea status este ntoars printelui pentru examinare. Procesele ar putea
apela exit explicit sau implicit la sfritul programului: rutina de pornire existent n
toate programele C apeleaz exit cnd programul revine din funcia main care este
punctul de intrare al tuturor programelor. Ca o alternativ, nucleul poate apela exit
199

intern pentru un proces la primirea unor semnale neinterceptate, dup cum s-a
discutat anterior. n acest caz, valoarea lui status este numrul semnalului.
Sistemul nu impune limite de timp pentru execuia unui proces i astfel procesele pot
exista pentru o perioad ndelungat. De exemplu, procesele 0 (swapper-ul) i 1 (init)
exist de-a lungul ntregii funcionri a sistemului. Alte exemple sunt procesele getty,
cate urmresc o linie de terminal, ateptnd intrarea unui utilizator i procesele
administrative cu scop dedicat.

algoritm exit
intrri: codul de retur pentru procesul printe
ieiri: niciuna
{
ignor toate semnalele;
if( conductorul grupului de procese este asociat cu terminalul de
control)
{
trimite semnalul de suspendare a tuturor membrilor grului de
procese;
iniializeaz grupul proceselor pentru toi membrii la 9;
}
nchide toate fiierele (versiunea intern a algoritmului close);
elibereaz directorul curent (algoritmul iput);
elibereaz rdcina curent (schimbat) dac exist (alg. iput);
elibereaz regiunile de memorie asociate procesului (alg. freereg);
scrie nregistrarea cu informaii de contabilitate;
trece procesul n starea zombie;
atribuie PID-ului printe al tuturor fiilor ca fiind procesul init (1);
dac vreun proces fiu era n starea zombie trimite semnalul de
"terminare proces fiu" ctre init;
trimite semnal de "terminare proces fiu" procesului printe;
comut contextul;
}

Figura 8.14 Algoritmul pentru exit


Figura 8.14 prezint algoritmul pentru exit. Nucleul dezactiveaz tratarea semnalelor
de ctre proces deoarece nu mai are nici un sens tratarea semnalelor. Dac procesul
care execut exit este un conductor de grup de procese asociat cu un terminal de
control (vezi paragraful 10.3.5), nucleul presupune c utlizatorul nu mai face nimic util
i trimite semnalul de suspendare ctre toate procesele aflate n grupul de procese.
Astfel, dac un utilizator tasteaz "sfrit de fiier" din shell-ul login-ului n timp ce
200

mai exist procese n execuie asociate terminalului, procesul care face exit le va
trimite un semnal de suspendare. Nucleul iniializeaz, de asemenea, numrul de grup
la 0 pentru procesele din grup, deoarece este posibil ca un alt proces s ia
identificatorul procesului care tocmai a executat exit i s fie un conductor de grup.
Procesele care au aparinut vechiului grup nu vor aparine i grupului urmtor. Nucleul
parcurge descriptorii fiierelor deschise, nchizndu-le pe fiecare prin algoritmul intern
close i elibereraz inodurile care au fost folosite pentru directorul curent i rdcina
schimbat (dac exist) prin algoritmul iput.
Nucleul elibereaz apoi toat memoria utilizatorului prin tererea regiunilor
corespunztoare cu algoritmul detachreg i schimb starea procesului n zombie. El
salveaz codul de stare al lui exit i timpii de execuie utilizator i nucleu acumulai de
proces i descendenii lui n tabela de procese. Descrierea lui wait n paragraful 8.4
arat cum un proces ia datele de timp pentru procesele descendente. Nucleul mai
scrie, de asemenea, o nregistrare de contabilitate ntr-un fiier global de cont ce
conine diferite statistici de rulare cum ar fi identificatorul utilizatorului, utilizarea CPUlui i a memoriei precum i cantitatea de operaii de intrare/ieire pentru un proces.
Programele la nivel de utilizator pot citi mai trziu fiierul de contabilitate pentru a
obine diferite date statistice utile pentru urmrirea performanelor i pentru taxarea
clientului. La sfrit, nucleul deconecteaz procesul de la arborele de procese fcnd
ca procesul 1 (init) s adopte toate procesele sale fii. Aceasta presupune c procesul 1
devine printe legal al tuturor proceselor fii n execuie pe care procesul care a
executat exit le-a creat. Dac vreunul din procesele de tip fiu este n starea zombie,
procesul ce execut exit trimite ctre init semnalul de "terminare proces fiu" pentru ca
init s-l tearg din tabela proceselor (vezi 8.9); procesul care execut exit trimite i
printelui su un semnal de terminare proces fiu. ntr-un scenariu tipic, procesul
printe execut un apel sistem wait pentru a se putea sincroniza cu procesul fiu care
face exit. Procesul aflat n starea zombie face o comutare de context astfel c nucleul
poate s programeze un alt proces pentru execuie; nucleul nu va programa pentru
execuie un proces aflat n starea zombie.
n programul din figura 8.15 un proces i creaz un fiu care i tiprete propriul PID
i execut apelul sistem pause suspendndu-i execuia pn cnd primete un
semnal. Procesul printe tiprete PID-ul fiului i execut exit ntorcnd PID-ul fiului
drept cod de stare. Dac apelul exit nu ar fi prezent, rutina de lansare procese ar
apela exit la ntoarcerea programului din funcia main. Procesul fiu creat de printe
continu s existe pn cnd primete un semnal, chiar dac procesul printe s-a
terminat.

main()
{
int child;
if((chil=fork())==0)
{
printf({child PID %d\n", getpid());
pause() /* suspend execuia pn la recepionarea unui semnal*/
}
/* printe */
printf("child PID %d\n", child);
201

exit(child);
}
Figura 8.15. Exemplu de Exit

8.4. Ateptarea (wait)


Un proces i poate sincroniza execuia cu terminarea unui proces fiu prin execuia
apelului sistem wait. Sintaxa pentru acest apel sistem este:

pid=wait (stat_addr);

unde pid este identificatorul de proces al fiului din starea zombie, iar stat_addr este
adresa unui ntreg din spaiul utilizator ce va conine codul de ieire al fiului.
Figura 8.16 prezint algoritmul pentru wait. Nucleul caut un fiu al procesului n starea
zombie i dac nu gsete nici unul ntoarce o eroare. Dac gsete un fiu n starea
zombie, extrage PID-ul i parametrul furnizat apelului sistem exit al fiului i ntoarce
aceste valori la terminarea apelului sistem. Un proces ce execut exit poate astfel
preciza diferite coduri de retur pentru a da sens execuiei apelului exit, dar multe
programe nu le stabilesc n mod explicit n practic. Nucleul adaug timpul acumulat
de execuia procesului fiu n modurile utilizator i nucleu la cmpurile corespunztoare
din u area a procesului printe i n final elibereaz poziia din tabela proceselor ce
fusese ocupat de procesul n stare zombie. Aceast poziie devine disponibil pentru
noi procese.
Dac procesul ce execut wait are procese de tip fiu, dar niciunul dintre acestea nu se
afl n starea zombie, el trece n ateptare la o prioritate ntreruptibil pn la sosirea
unui semnal. Nucleul nu are un apel explicit de reluare a unui proces n ateptare prin
wait: astfel de procese sunt reluate numai la primirea unor semnale. Pentru orice
semnal, cu excepia celui de "terminare proces fiu" procesul va reaciona dup cum sa descris anterior. Cu toate acestea, dac semnalul este de "terminare proces fiu",
procesul ar putea rspunde n mod diferit.

n cazul implicit el va fi reluat din wait iar sleep va invoca algoritmului issig
pentru a cuta semnalele. Issig (figura 8.7) recunoate cazul special al semnalului
"terminare proces fiu" i ntoarce fals. Ca urmare nucleul nu execut un longjmp din
sleep ci se ntoarce n wait. Nucleul va relua bucla wait, gsete un fiu n starea
zombie -cel puin unul exist, elibereaz poziia slotul fiului din tabela proceselor i se
ntoarce din apelului sistem wait.

Dac procesul intercepteaz semnalul "terminare proces fiu", nucleul


apelezeaz din partea procesului rutina utilizator de tratare a semnalului, aa cum face
pentru alte semnale.

Dac procesul ignor semnalele de "terminare proces fiu", nucleul reia bucla
wait, elibereaz sloturile din tabela proceselor corespunztoare proceselor fii aflai n
starea zombie i caut ali fii.

202

algoritm wait
intrri: adresa unei variabile de pstrare a strii procesului ce execut
exit
ieiri: identificatorul fiului, codul de ieire al fiului
{
if(procesul n ateptare nu are procese fii)
return(eroare);
for(;;)

/* cicleaz pn la instruciunea return din interiorul

buclei */
{
if(procesul n ateptare are un fiu n starea zombie)
{
alege un fiu aflat n starea zombie;
adaug timpul de folosire a CPU la printe;
elibereaz intrarea fiului din tabela de procese;
return(ID-ul fiului, codul de ieire al fiului);
}
if(procesul nu are fii)
return eroare;
ateapt cu o prioritate ntreruptibit pe evenimentul :(se iese din
procesul fiu);
}
}
Figura 8.16. Algoritmul pentru wait
De exemplu, un utilizator obine diferite rezultate cnd apeleaz programul din figura
8.17 cu sau fr parametri. S considerm mai nti cazul n care utilizatorul apeleaz
programul cu un parametru (argc este 1, numele programului). Procesul printe
creaz 15 procese fiu, care eventual apeleaz exit returnnd codul i, valoarea
variabilei de ciclu cnd procesele fiu au fost create. Nucleul execut apelul sistem wait
pentru procesul printe, gsete un proces fiu n starea zombie i ntoarce
identificatorul su de proces i codul lui exit. Procesul fiu care este gsit este
nedeterminat. n biblioteca C, procedura pentru pentru apelul sistem exit pstreaz
codul n biii de la 8 la 15 ai variabilei ret_code i ntoarce identificatorul procesului fiu
pentru apelul wait. Astfel variabila ret_code este egal cu 256*i, depinznd de
valoarea lui i pentru procesul fiu i ret_val este egal cu valoarea identificatorului de
proces pentru procesul fiu.
Dac utilizatorul apeleaz programul de mai sus cu parametri (argc>1), procesul
printe apeleaz signal pentru a ignora semnalele de terminare proces fiu. S
presupunem c procesul printe ateapt n wait nainte ca vreun proces s apeleze
exit: cnd procesul fiu apeleaz exit se trimite un semnal de terminare proces fiu la
printe: procesul printe prsete starea de ateptare, pentru c ateptarea sa n
wait este la o prioritate ntreruptibil. Cnd procesul printe eventual ruleaz, acesta
gsete c cel mai important semnal a fost de "terminare proces fiu"; dar pentru c se
203

ignor semnalul de "terminare proces fiu", nucleul terge intrarea procesului aflat n
starea zombie din tabela proceselor i continu execuia lui wait ca i cum nu ar fi
existat nici un semnal.
#include <signal.h>
main(argc,argv)
int argc;
char *argv[];
{
int i, ret_val, ret_code;
if(argc>=1)
signal(SIGCLD, SIG_IGN);

/* ignor terminarea proceselor fii */

for(i=0; i<15; i++)


if(fork()=0)
{
/* proces fiu */
printf("chil proc %x\n", getpid());
exit(i);
}
ret_val=wait(&ret_code);
prinf("wait ret_val %x ret_code %x\n", ret_val, ret_code);
}
Figura 8.18. Exemplu pentru semnalele de wait i ignorare terminare
fiu
Nucleul execut procedura anterioar de fiecare dat cnd printele recepioneaz un
semnal de "terminare proces fiu" pn cnd n final trece prin bucla wait i constat c
printele nu mai are fii. Apelul sistem wait ntoarce atunci -1. Diferena ntre cele dou
apeluri ale programului este c procesul printe ateapt terminarea unui proces fiu n
primul caz i a tuturor proceselor fii n al doilea caz.
Versiunile mai vechi ale sistemului UNIX implementau apelurile exit i wait fr
semnalul de "terminare proces fiu". n loc s trimit semnalul de "terminare proces
fiu", exit trezete procesul printe. Dac procesul printe era n ateptare n apelul
sistem wait,acesta se putea trezi, gsea un proces fiu n starea zombie i apoi se
ntorcea. Dac acesta nu era n ateptare n wait, activarea nu ar fi avut nici un efect;
ar fi gsit un fiu n starea zombie n urmtorul apel wait. n mod similar, procesul init
s-ar putea gsi n ateptare n wait i procesele care ar executa exit l-ar trezi pe
acesta pentru a adopta noi procese aflate n starea zombie.
Problema acestei implementri este imposibilitatea nlturrii proceselor n stare
zombie dac procesul printe nu execut wait. Dac un proces creaz mai mul4i fii
dar nu execut wait, tabela de procese se va ncrca cu fii aflai n starea zombie dup
ce acetia execut exit. De exemplu, s lum programul din figura 8.18. Procesul
citete din fiierul su standard de intrare pn cnd acesta ntlnete sfritul de
204

fiier, crend un proces fiu la fiecare citire. Cu toate acestea, procesul printe nu
ateapt terminarea vreunui proces fiu deoarece dorete expedierea ct mai rapid a
proceselor iar un proces fiu poate exista destul de mult timp (pn s fac exit). Dac
printele face n aa fel nct apelul signal s ignore semnalele de "terminare proces
fiu" , nucleul va elibera intrrile proceselor aflate n starea zombie n mod automat.
Altfel, procesele aflate n starea zombie ar putea umple, eventual la maxim, locurile
permise n tabela de procese.

#include <signal.h>
main(argc,argv)
{
char buf[256];
if(argc!=1)
signal(SIGCLD,SIG_IGN);

/* ignor "terminare fii" */

while(read(0, buf, 256))


if(fork()==0)
{
/* procesul fiu lucreaz cu variabila buf */
exit(0);
}
}
Figura 8.18. Exemplu de prezentare a importanei semnalului
de "terminare proces fiu"

8.5. Apelul altor programe


Apelul sistem exec apeleaz un alt program suprapunnd pe spaiul de memorie al
unui proces o copie a unui fiier executabil. Coninutul contextului la nivel utilizator
care a existat naintea apelului exec nu mai este apoi accesibil cu excepia
parametrilor lui exec pe care nucleul i copiaz din vechiul spaiu de adres n cel nou.
Sintaxa apelului de sistem este:

execve(filename, argv, envp)

unde filename este numele fiierului executabil care a fost apelat, argv este un pointer
ctre un vector de pointeri la iruri care sunt parametrii programului executabil iar
envp este un pointer ctre un vector de pointeri la iruri care reprezint mediul
programului executat. Exist mai multe funcii de bibliotec care apeleaz exec cum ar
fi execl, execv, execle, etc. Toate acestea apeleaz eventual pe execve, din acest
205

motiv acesta este folosit aici pentru a specifica apelul sistem exec. Cnd un program
folosete parametrii n linia de comand, cum ar fi

main(argc,argv)

vectorul argv este o copie a parametrului argv n exec. irurile de caractere n mediu
sunt de forma "nume=valoare" i pot conine informaii utile programelor cum ar fi
directorul propriu al utilizatorului i o cale de directoare pentru a cuta programe
executabile. Procesele i pot accesa mediul prin variabila environ, iniializat de rutina
de pornire a C-ului.
algoritm exec
intrri:

(1) nume de fier


(2) lista de parametri
(3) lista de variabile de mediu

ieiri: nimic
{
obine inodul fiierului (algoritmul namei);
verifi dac fiierul e executabil, dac utilizatorul are drept de
execuie;
citete header-ele de fiier, verific dac sunt module ncrcabile;
copiaz parametrii lui exec din spaiul vechi de adrese n spaiul
sistemului;
for(fiecare regiune specificat n modului ncrcat)
{
aloc regiuni noi (algoritm allocreg);
ataeaz regiunile (algoritmul attachreg);
ncarc regiunea n memorie dac este cazul (algoritmul loadreg);
}
copiaz parametrii lui exec n noua regiune de stiv a utilizatorului;
procesare special pentru programele setuid, opiune de execuie pas
cu pas;
elibereaz inodul fiierului (algoritmul iput);
}
Figura 8.19. Algoritmul pentru exec
Figura 8.19 prezint algoritmul pentru exec. n primul rnd exec acceseaz fiierul prin
algoritmul namei pentru a determina dac este executabil, obinuit (nu e director) i
pentru a determina dac utilizatorul are drept de execuie asupra programului. Nucleul
citete apoi header-ul fiierului pentru a determina mrimea fiierului executabil.

206

Figura 8.20 prezint formatul logic al unui fiier executabil, aa cum exist n sistemul
de fiiere, n mod tipic generat de asamblor sau ncrctor. Acesta conine 4 pri:
1.
Header-ul primar care precizeaz cte seciuni sunt n fiier, adresa de start
pentru execuia procesului i "numrul magic" care d tipul fiierului executabil.
2.
Header-ele de seciune descriu fiecare seciune din fiier, dnd mrimea
seciunii, adresele virtuale pe care ar trebui s le ocupe seciunea atunci cnd ruleaz
n sistem i alte informaii.
3.
Seciunile conin att zon de "date",ct i zon de text, care sunt iniial
ncrcate n spaiul de adrese al procesului.
4.
Diferite seciuni ar putea conine tabele de simboluri i alte date utile n
depanare.
Formatele specifice au evoluat de-a lungul anilor dar toate fiierele executabile au
coninut un header primar cu un "numr magic".
Numrul magic este un ntreg scurt care identific fiierul ca un modul ncrcabil i d
nucleului posibilitatea s disting diferite caracteristici de rulare ale sale. De exemplu,
folosirea unui numr magic pe un PDP 11/70 informeaz nucleul c procesul poate
utiliza pn la 128 ko de memorie n loc de 64 ko; numrul magic joac, de
asemenea, un rol important n sistemele de paginare, dup cum se va vedea n
capitolul 9.
Header-ul primar

Header-ul seciunii 1

Numrul magic
Numrul seciunilor
Valorile iniiale ale regitrilor
Tipul seciunii
Mrimea seciunii
Adresa virtual

Header-ul seciunii 2

Tipul seciunii
Mrimea seciunii
Adresa virtual

Header-ul seciunii n
Seciunea 1

:
:
Tipul seciunii
Mrimea seciunii
Adresa virtual
Date (de exemplu, text)

Seciunea 2

Date

Seciunea n

:
:
Date
Alte informaii

Figura 8.20 Imaginea unui fiier executabil


n acest moment, nucleul a accesat inodul fiierului executabil i a verificat dac-l
poate executa. Nucleul este pe punctul de a elibera resursele de memorie care
formeaz contextul procesului la nivel utilizator. Dar, deoarece parametrii pentru noul
program sunt coninui n spaiul de memorie care trebuie eliberat, nucleul copiaz
parametrii din vechiul spaiu de memorie care trebuie eliberat ntr-un buffer temporar,
pn cnd vor fi ataate regiunile noului spaiu de memorie. Deoarece parametrii lui
exec sunt adrese utilizator ale unor cmpuri ce conin iruri de caractere, nucleul
copiaz adresele cmpurilor de caractere i apoi irurile de caractere n spaiul
nucleului pentru fiecare cmp de caractere. El ar putea alege diferite locuri pentru a
207

pstra irurile de caractere, n funcie de implementare. Cele mai obinuite locuri sunt
stiva nucleului (un vetor local n rutina nucleului), zonele nealocate (cum ar fi paginile)
de memorie care pot fi mprumutate temporar sau memoria secundar, cum ar fi un
dispozitiv de swapping.
Cea mai simpl implementare pentru copierea parametrilor n noul context la nivel de
utilizator este folosirea stivei nucleului. Dar, deoarece configurarea sistemului impune
n mod obinuit o limit de mrime pentru stiva nucleului i deoarece parametrii lui
exec pot avea o lungime neprecizat, planul trebuie s fie combinat cu altul. Din
celelalte variante, implementrile utilizeaz cea mai rapid metod. Dac este uor s
se aloce pagini de memorie, o astfel de metod este preferabil deoarece accesul la
memoria primar este mai rapid dect la cea secundar (cum ar fi un dispozitiv de
swapping).
Dup copierea parametrilor lui exec n locul de pstrare din nucleu, nucleul detaeaz
vechile regiuni ale procesului folosind algoritmul detachreg. Tratamentul special pentru
regiunile de text va fi discutat mai trziu n acest paragraf. n acest moment procesul
nu are context la nivel utilizator aa nct erorile care apar de acum nainte duc la
terminarea sa, cauzat de un semnal. Astfel de erori pot fi: rularea n afara spaiului
coninut n tabela de regiuni a nucleului, ncercarea de a ncrca un program a crui
mrime depete limita sistemului, ncercarea de a ncrca unui program ale crei
adrese de regiuni se suprapun, i altele. Nucleul aloc i ataeaz regiuni pentru text
i date, ncrcnd coninutul fiierului executabil n memoria principal (algoritmii
allocreg, attachreg i loadreg). Regiunea de date a unui proces este iniial mprit n
dou: date iniializate n momentul compilrii i date care nu se iniializeaz n
momentul compilrii ("bss"). Alocarea i ataarea iniial a regiunilor de date se face
pentru datele iniializate. Nucleul mrete atunci regiunea de date folosind
algoritumul growreg pentru datele "bss" i iniializeaz valoarea memoriei cu 0. n
final, aloc o regiune pentru stiva procesului, o ataeaz procesului i aloc memorie
pentru pstrarea parametrilor funciei exec. Dac nucleul a salvat parametrii lui exec
n pagini de memorie, acesta poate utiliza aceste pagini pentru stiv. n caz contrar, el
copiaz parametrii lui exec n stiva utilizatorului.
Nucleul terge adresele interceptorilor de semnale utilizator din u area, deoarece
aceste adrese nu mai au sens n noul context la nivel utilizator. Semnalele care au fost
ignorate rmn ignorate i n noul context. Nucleul seteaz apoi contextul regitrilor
salvai pentru modul utlizator, setnd SP-ul i PC-ul iniiale: ncrctorul a scris PC-ul
iniial n header-ul fiierului. Nucleul execut aciuni speciale pentru programele setuid
i pentru facilitile de execuie pas cu pas, aciuni descrise n capitolul 11. La sfrit,
el invoc algoritmul iput, elibernd inodul care a fost alocat iniial prin algoritmul
namei la nceputul lui exec. Utilizarea lui namei i iput n exec corespunde deschiderii
i nchiderii unui fiier; starea unui fiier n timpul apelului lui exec este asemntoare
celei a unui fiier deschis cu excepia absenei unei intrri n tabela de fiiere. Cnd
procesul execut ntoarcerea din apelul exec, el execut codul noului program.
Oricum, este acelai proces de dinainte de exec: identificatorul su de proces nu s-a
schimbat i nici poziia sa n ierarhia de procese. Numai contextul la nivel utilizator se
schimb.

main()
{
int status;
208

if(fork()==0)
exec("/bin/date","date",0);
wait(&status);
}
Figura 8.21 Utilizarea lui Exec
De exemplu, programul din figura 8.21 creaz un proces fiu care apeleaz exec.
Imediat dup ce printele i fiul se ntorc din apelul fork acestea execut copii
indepentente ale programului. Cnd procesul fiu este pe cale de a apela exec, regiunea
sa de cod (text) const din instruciunile programului, regiunea sa de date conine
irurile "/bin/date", i "date", iar stiva sa conine nivelele pe care procesul a executat
push pentru a putea apela exec. Nucleul gsete fiierul "/bin/date" n sistemul de
fiiere, observ c toi utilizatorii l pot executa i determin dac este un modul
executabil. Prin convenie primul parametru al listei de parametri argv pentru exec
este calea ctre numele fiierului executabil. Procesul are astfel acces la numele
programului la nivel de utlizator, ceea ce uneori reprezint o caracteristic util.
Nucleul copiaz apoi irurile "/bin/date" i "date" ntr-un domeniu de pstrare i
elibereaz reginile de cod, date i stiv ocupate de proces. El aloc procesului noi
regiuni de cod, date i stiv, copiaz seciunea di fiierul "/bin/date" ce conine
instruciuni n regiunea de cod (text) i copiaz seciunea de date a fiierului n regiune
de date. Nucleul reconstruiete lista original de parametri (aici irul de caractere
"date") i o pune n regiunea de stiv. Dup apelul lui exec, procesul fiu nu mai
execut vechiul program ci execut programul "date". Cnd programul "date" se
termin, procesul printe primete starea de ieire din propriul apel wait.
Pn acum, am presupus c, codul i datele procesului ocup prii separate n
programul executabil i, din acest motiv, regiuni separate la rularea programului. Sunt
dou avantaje pentru inerea codului i a datelor programului separate: protecie i
folosire n comun (sharring). Dac codul i datele s-ar afla n aceeai regiune,
sistemul nu ar putea preveni procesul la suprascrierea instruciunilor, pentru c nu ar
ti care adrese conin instruciuni i care date. Dar dac codul i datele sunt n regiuni
separate, nucleul poate activa mecanismul de protecie hard pentru a preveni
procesele la suprascriere n zona de cod. Dac procesul ncearc din greeal s
suprascrie spaiul su de cod, se creaz o ntrerupere de protecie care n mod obinuit
rezult n terminarea procesului.
#include <signal.h>
main()
{
int i, *p;
extern f(), sigcatch();
ip=(int *)f; /* asigneaz ip la adresa funciei f */
for(i=0; i<20; i++)
signal(i, sigcatch);
*ip=1;

/* ncearc s suprascrie adresa funciei f */

printf("dup asignarea lui ip\n");


f();
209

}
f()
{
}
sigcatch(n)
int n;
{
printf("recepionat semnalul %d\n", n);
exit(1);
}
Figura 8.22 Exemplu de program care i suprascrie codul
De exemplu, programul din figura 8.22 atribuie pointerului ip adresa funciei f i apoi
aranjeaz s fie interceptate toate semnalele. Dac programul este compilat aa nct
codul i datele sunt n regiuni separate, procesul care execut programul genereaz o
ntrerupere de protecie cnd se ncearc s se scrie coninutul lui ip, deoarece se
ncearc scrierea n regiunea de cod care este cu protecie la scriere. Pe un computer
AT&T 3B20, nucleul trimite semnalul SIGBUS la proces dar n alte implementri pot fi
trimise alte semnale. Procesul intercepteaz semnalul i apeleaz exit fr a executa
instruciunea printf din main. Totui, dac programul a fost compilat astfel nct codul
i datele s fie n aceeai regiune (regiunea de date), nucleul nu ar putea realiza c un
proces a suprascris adresa funciei f. Adresa funciei f conine valoarea 1! Procesul
execut instruciunea printf n main dar execut o instruciune nepermis cnd se face
apelul lui f. Nucleul trimite semnalul SIGILL i procesul apeleaz exit.
Pstrarea de date i instruciuni n regiuni separate face uoar protecia mpotriva
erorilor de adresare. Versiune anterioare de UNIX permiteau codului i datelor s se
afle n aceeai regiune din cauza limitrii lungimii proceselor impuse de calculatoarele
PDP: programele erau mai mici i necesitau civa regitri de "segmentare" dac
codul i datele ocupau aceeai regiune. Versiunea curent a sistemului nu are o astfel
de limitare a mrimii proceselor i viitoarele compilatoare nu vor accepta opiunea de
ncrcare a codului i datelor n aceeai regiune.
Al doilea avantaj al scrierii n regiuni separate a codului i datelor este permiterea
mpririi regiunii. Dac procesul nu poate scrie n regiunea de cod, codul nu se poate
schimba n timpul ce nucleul l ncarc din fiierul executabil. Dac mai multe procese
execut un fiier, ele pot s mpart regiunea de cod, salvnd memoria. Astfel, cnd
nucleul aloc o regiune de cod pentru un proces n exec, acesta verific dac fiierul
executabil permite ca codul su s fie mprit, indicaie dat de numrul magic. Dac
se ntmpl aceasta, se parcurge algoritmul xalloc pentru a gsi o regiune existent
pentru codul fiierului sau pentru a asigna una nou (vezi figura 8.23).
n xalloc, nucleul caut n lista regiunilor active dup regiunea de cod a fiierului,
identificnd-o pe cea al crei pointer inode se potrivete cu inodul fiierului executabil.
Dac o asemenea regiune nu exis, nucleul aloc o nou regiune (algoritmul alloreg),
o ataeaz procesului (algoritmul attachreg), o ncarc n memorie (algoritmul
loadreg) i i schimb protecia n read-only. Ultimul pas provoac o ntrerupere de
protecie a memoriei dac un proces ncearc s scrie n regiunea de cod. Dac, n
210

cutarea prin lista regiunilor active, nucleul localizeaz o regiune care conine codul
fiierului, se asigur c regiunea este ncrcat n memorie (altfel ateapt) i o
ataeaz procesului.

algoritmul xalloc

/* aloc i iniializeaz regiunea de cod */

intrri: inodul fiierului executabil


ieiri: niciuna
{
if(fiierul executabil nu are regiune de cod separat)
return;
if(regiunea de cod este asociat cu inodul codului)
/* regiunea de cod deja exist... se ataeaz la ea */
blocheaz regiunea;
while(coninutul regiunii nu este nc gata)
{
/* manipularea contorului de referin previne modificarea total
a regiunii */
incrementeaz contorul de referin al regiunii;
deblocheaz regiunea;
sleep pe evenimentul: (evenimentele cu coninutul regiunilor este
gata);
blocheaz regiunea;
decrementeaz contorul de referin al regiunii;
}
ataeaz regiunea procesului (algoritmul attachreg);
deblocheaz regiunea;
return;
}
/* nu exist o regiune de cod...creaz una */
aloc o regiune de cod (algoritmul allocreg); /* regiunea este
blocat */
if( inodul are sticky bit-ul resetat)
seteaz bitul sticky al regiunii;
ataeaz regiunea la adresa virtual indicat de header-ul fiierului
indicat de inod (algoritmul attachreg);
if(fiierul este formatat special pentru sistemul de paginare)
/* Capitolul 9 va discuta acest caz */
else /* nu este formatat pentru sistemul de paginare */

211

citete codul fiierului n regiune (algoritmul loadreg);


schimb protecia regiunii n tabela de regiuni de tip per proces n
read-

only;

deblocheaz regiunea;
}
Figura 8.23 Algoritmul pentru alocarea regiunilor de cod
Nucleul deblocheaz regiunea la sfritul lui xalloc i decrementeaz contorul regiunii
mai trziu, cnd acesta execut detachreg, n timpul apelului exit sau exec.
Implementarea tradiional a sistemului conine o tabel de coduri (text table) pe care
nucleul o manipuleaz n modul deja descris pentru regiunile de cod. Setul regiunilor
de cod poate fi astfel vzut ca o versiune modern a vechii tabele de cod.
S ne amintim c atunci cnd se aloc o regiune pentru prima dat n allocreg
(paragraful 6.5.2), nucleul incrementeaz contorul de referin al inodului asociat cu
regiunea, dup ce a incrementat contorul de referin n namei (apelul lui iget) la
nceputul lui exec. Pentru c nucleul decrementeaz contorul de referin o singur
dat n iput, la sfritul lui exec, contorul de referin al inodului fiierului care este
executat este cel puin egal cu 1: de aceea, dac un proces parcurge algoritmul
unlinks pentru un fiier, coninutul lui rmne intact. Nucleul nu mai are nevoie de
fiier dup ncrcarea sa n memorie, dar are nevoie de un pointer la inodul imaginii
fiierului din memoria intern n tabela de regiuni pentru a identifica fiierul care
corespunde regiunii respective. Dac contorul de referin a fost redus la 0, nucleul ar
putea realoca inodul imaginii fiierului din memoria intern altui fiier, compromind
coninutul pointerului inode n tabela regiunilor: dac utilizatorul a apelat exec pentru
noul fiier, nucleul ar gsi regiunea de cod a vechiului fiier cu erori. Nucleul evit
aceast problem incrementnd contorul de referine al inodului n algoritmul allocreg,
prevenind reasignarea inodului imaginii fiierului din memoria intern. Cnd procesul
detaeaz regiunea de cod n timpul lui exit sau exec, nucleul decrementeaz contorul
de referin n freereg, mai puin n cazul n care inodul are modul sticky-bit setat, aa
cum se va vedea.

Tabela inode-lor
.
.:
.

Tabela regiunilor
scenariu posibil dac
/bin/date are numrtorul
de referine 0

imaginea inode-lui
pentru /bin/date
:
:
:

:
regiune de text
pentru /bin/date
:
:
regiune de text
pentru /bin/date
:
:

Figura 8.24 Relaiile ntre tabela de inoduri i tabela de regiuni pentru


partajarea codului
De exemplu, reconsiderm execuia lui "/bin/date" din figura 8.21 i presupunem c
fiierul are zone de cod i date separate. Prima dat procesul execut "/bin/date", apoi
nucleul aloc o intrare n tabela regiunilor pentru cod (figura 8.24) i las contorul de
referine al inodului la 1 (dup execuia complet a lui exec). Cnd "/bin/date" iese
(exit), nucleul parcurge algoritmii detachreg i freereg, decrementnd contorul de
212

referine al inodului la 0. Totui, dac nucleul n-ar fi incrementat contorul de referine


pentru inodul fiierului "/bin/date" prima dat cnd s-a apelat exec, contorul de
referine ar fi 0 i inodul s-ar gsi n lista liber ct timp procesul era n rulare. S
presupunem c un alt proces apeleaz exec pentru fiierul "/bin/who" i nucleul aloc
inodul imaginii fiierului din memoria intern folosit anterior pentru "/bin/date" i
"/bin/who". Nucleul ar cuta n tabela regiunilor inodul pentru "/bin/who", dar ar gsi
inodul pentru "/bin/date". Presupunnd c regiunea conine codul pentru "/bin/who",
nucleul ar putea executa un program greit. n consecin, contorul de referine pentru
inodul fiierului care are zona de cod folosit n comun este cel puin 1, aa c nucleul
nu poate realoca inodul.
Capacitatea de partajare a regiunii de cod permite nucleului s scad timpul de start al
unui program invocat de exec utliznd sticky-bitul. Administratorul de sistem poate
seta modul fiierului sticky-bit cu apelul sistem chmod pentru utilizarea frecvent a
fiierelor executabile. Cnd un proces execut un fiier care are setat sticky-bitul,
nucleul nu poate elibera memoria alocat pentru cod cnd, mai trziu, este detaat
regiunea n timpul lui exit sau exec, chiar dac contorul de referine scade la 0.
Nucleul las regiunea de cod intact, cu numrtorul de referine al inoduului egal cu
1, chiar dac aceasta nu mai este ataat altui proces. Cnd alt proces execut
fiierul, gsete intrare n tabela regiunilor pentru codul fiierului. Timpul de start al
procesului este mic, pentru c nu a trebuit s citeasc codul fiierului din sistem: dac
codul se afl nc n memorie, nucleul nu face nici o operaiune de I/O pentru cod;
dac nucleul a evacuat (swapp-at) codul n zona de swap, acesta ncarc mai repede
codul din "dispozitivul de swap" dect din fiierul sistem, aa cum se va vedea n
capitolul 9.
Nucleul terge intrrile pentru regiunile de cod in modul cu sticky-bit
urmtoarele cazuri:

setat n

1.
Dac un proces deschide fiierul pentru scriere, operaiile de scriere vor
schimba coninutul fiierului, invalidnd coninutul regiunii.
2.
Dac un proces schimb modul de acces la fiier (chmod) astfel nct sticky-bit
nu mai este setat, fiierul nu ar mai rmne n tabela regiunilor.
3.
Dac un proces execut unlinks pentru fiier, nici un proces nu va mai putea
s-l execute n continuare pentru c fiierul nu mai are intrare n sistemul de fiiere;
din acest motiv nici un proces nou nu va accesa intrarea fiierului din tabela regiunilor.
Pentru c nu este nevoie de regiunea de cod, nucleul poate elibera unele resurse.
4.
Dac un proces "demonteaz" sistemul de fiiere, fiierul nu va mai fi accesibil
i nici un proces nu-l va mai putea executa, la fel ca n cazul precedent.
5.
Dac nucleul ruleaz n afara spaiului alocat de dispozitivul de swap, se
ncearc s se elibereze spaiul disponibil prin eliberarea regiunilor sticky-bit care sunt
n mod curent neutilizate. Dei alte procese pot avea nevoie de o regiune de cod
curnd, nucleul dorete acest lucru i mai repede.

Regiunea de cod supus aciunii lui sticky trebuie s fie tears n primele dou cazuri
pentru c ea nu mai reflect starea curent a fiierului. Nucleul terge intrrile sticky
n ultimele trei cazuri, pentru c acest lucru este mult mai practic. Desigur, nucleul
elibereaz doar regiunile pe care nici un proces nu le utilizeaz n momentul respectiv
(numrul lor de referine este 0); altfel apelurile sistem open, unlink, unmount
(cazurile 1,3 i 4)ar funciona greit.

213

Scenariul pentru exec este puin mai complicat dac procesul se execut (exec) pe el
nsui. Dac utilizatorul tiprete:

sh script

shell-ul face un fork i procesul fiu face un apel exec asupra shell-lui i execut
comenzile din fiierul "script". Dac un proces se execut singur i permite mprirea
propriei regiuni de cod, nucleul trebuie s evite situaiile de blocare a inodurilor i a
regiunilor. Adic, nucleul nu poate bloca vechea regiune de cod, s o in blocat, i
apoi s ncerce s blocheze noua regiune de cod, deoarece regiunile (veche i nou)
sunt una i aceeai. n schimb, nucleul las vechea regiune de cod ataat la proces,
din moment ce va fi reutilizat oricum.
Procesele apeleaz uzual exec dup fork; astfel procesul fiu copiaz spaiul de adrese
al printelui n timpul apelului sistem fork, scrie acest spaiu n timpul lui exec i
execut o imagine program diferit de cea a printelui. Nu ar fi mai natural s
combinm cele dou apeluri ntr-unul singur pentru a apela programul i a-l rula ca un
nou proces? Ritchie presupune c fork i exec sunt apeluri sistem diferite pentru c
atunci cnd s-a proiectat sistemul UNIX, el i Thomson au putut s adauge apelul
sistem fork fr a face prea multe schimbri de cod n nucleul existent.Separarea lui
fork de exec este funcional important, pentru c procesele pot manipula proprii
descriptori de fiiere de intrare i ieire standard independent pentru a seta pipe-urile
mai elegant dect ar face-o ambele apeluri combinate ntr-unul singur. n exemplul de
shell din paragraful 8.8. se va vedea aceast trstur.

8.6. Identificatorul utilizator al unui proces


Nucleul asociaz doi identificatori pentru un proces, indenpendent de ID-ul procesului:
identificatorul utilizator real (real user ID) i identificatorul utilizator efectiv sau setuid
(effective user ID). Identificatorul utilizator real identific utilizatorul care este
responsabil pentru rularea procesului. Identificatorul utilizator efectiv este folosit
pentru a asigna dreptul de proprietate ultimelor fiiere create, pentru a verifica dreptul
de acces la fiier i dreptul de a trimite semnale la procese prin intermediul apelului
sistem kill. Nucleul permite unui proces s-i schimbe propriul UID efectiv execut un
program (exec) setuid sau cnd se face apelul explicit al apelului sistem setuid.
Un program setuid este un fiier executabil care are setat bitul corespunztor lui setuid
n cmpul drepturilor de acces. Cnd un proces execut (exec)_un program setuid,
nucleul seteaz cmpurile identificatorului utilizator efectiv din tabela proceselor i u
area, la identificatorul proprietarului fiierului. Pentru a distinge cele dou cmpuri,
vom numi cmpul din tabela proceselor identificatorul utilizator salvat. Un exemplu va
ilustra diferena dintre cele dou cmpuri.
Sintaxa apelului sistem setuid este:

setuid(uid)

214

unde uid este noul identificator utilizator i rezultatul su depinde de valoarea curent
a identificatorului utilizator efectiv. Dac identificatorul utilizator efectiv al procesului
apelant este acela al superutilizatorului, nucleul reseteaz cei doi identificatori
(cmpurile lor) din tabela proceselor i u area, la uid. Dac UID-ul efectiv nu este
acela al superutilizatorului, nucleul reseteaz identificatorul utilizator efectiv n u area
la uid dac uid-ul are valoarea identificatorului utilizator real sau dac are valoarea
identificatorului utilizator salvat. n celelalte cazuri, apelul sistem se ntoarce cu
eroare. n general, procesul motenete identificatorii si (real i efectiv) de la printe
n timpul apelului sistem fork i pstreaz aceste valori pe tot parcursul execuiei
apelului sistem exec.
Programul din figura 8.25 demonstreaz apelul sistem setuid. Presupunem c fiierul
executabil produs prin compilarea programului are propietar pe "mauny"
(identificatorul utilizator 8319), bitul su pentru setuid este activat i toi utilizatorii au
permisiunea s-l execute. n plus, presupunem c utilizatorii "mjb" (identificatorul
utilizator 5088) i "maury" au n proprietate fiiere cu aceleai nume i ambele fiiere
au setat dreptul de acces read-only. Utilizatorul "mjb" vede urmtoarele date la ieire
cnd execut programul:

uid 5088 euid 8319


fdmjb -1 fdmaury 3
dup setuid (5088): uid 5088 euid 5088
fdmjb 4 fdmaury -1
dup setuid (8319): uid 5088 euid 8319
Apelurile sistem getuid i geteuid ntorc identificatorii real i efectiv ai procesului, 5088
i respectiv 8319 pentru utilizatorul "mjb". De aceea, procesul nu poate deschide
fiierul "mjb", pentru c utlizatorul efectiv (8319) nu are drept de citire a fiierului, dar
poate deschide fiierul "maury".
#include<fnctl.h>
main()
{
int uid, euid, fdmjb, fdmaury;
uid=getuid();
euid=geteuid();
printf("uid %d euid %d\n", uid, euid);
fdmjb=open("mjb", O_RDONLY);
fdmaury=open("maury", O_RDONLY);
printf("fdmjb %D fdmaury %d\n", fdmjb, fdmaury);
setuid(uid);

215

printf("dup setuid (%d): uid %d euid %d\n", uid, getuid(),


geteuid());
fdmjb=open("mjb", O_RDONLY);
fdmaury=open("maury", O_RDONLY);
printf("fdmjb %d fdmaury %d\n", fdmjb, fdmaury);
setuid(euid);
printf("dupq setuid(%d):uid %d euid %d\n", ei\uid, getuid(),
geteuid());
}
Figura 8.25 Exemplu de execuie a programului setuid
Dup apelarea lui setuid pentru a schimba identificatorul efectiv al procesului n
identificatorul utilizator real ("mjb') a doua instruciune printf tiprete valorile 5088 i
5088, identificatorul utilizator al lui "mjb". Acum procesul poate deshide fiierul "mjb",
pentru c identificatorul su utilizator are acces de citire pentru fiier, dar procesul nu
poate deschide fiierul "maury". n final, dup apelarea lui setuid, pentru a schimba
identificatorul utilizator efectiv n valoarea salvat de programi (8319), al treilea printf
va tipri din nou valorile 5088 i 8319. Ultimul caz arat c procesul poate executa un
program setuid i modifica identificatorul su utilizator efectiv cu identificatorul su
utilizator real sau cel executat de setuid.
Utilizatorul "maury" vede urmtoarele date la ieire cnd execut programul:

uid 8319 euid 8319


fdmjb -1 fdmaury 3
dup setuid (8319): uid 8319 euid 8319
fdmjb -1 fdmaury 4
dup setuid (8319):uid 8319 euid 8319

Identificatorii utilizator real i efectiv sunt ntotdeauna 8319: procesul nu poate


deschide fiierul "mjb" dar poate deschide fiierul "maury". Identificatorul utilizator
efectiv pstrat n u area este rezultatul celui mai recent apel sistem setuid sau a
execuiei unui program setuid; el este singur responsabil pentru determinarea
dreptului de acces la fiier. Identificatorul utilizator salvat n tabela proceselor permite
unui proces s-i schimbs propriul identificator utilizator efectiv n el prin execuia
apelului sistem setuid, astfel reapelnd identificatorul utilizator efectiv original.
Programul login executat de utilizator cnd dorete s intre n sistem este un program
tipic care apeleaz setuid. Login-ul are setuid-ul rdcinii (superutilizator) i de aceea
ruleaz cu identificatorul utilizator efectiv al rdcinii. El chestioneaz utilizatorul
pentru diferite informaii cum ar fi numele i parola; cnd este satisfcut apeleaz
setuid pentru a-i seta identificatorii efectiv i real la acela al utilizatorului care
ncearc s fac login (gsit n cmpurile din fiierul "/etc/passwd"). Login, n final,
216

execut shell-ul care ruleaz cu identificatorii si efectiv i real setai pentru


utilizatorul corespunztor.
Comanda mkdir este un program setuid tipic. Este, aa cum se arat n paragraful 5.8,
singurul proces cu identificator utilizator efectiv care poate crea un director. Pentru a
permite utilizatorilor obinuii posibilitatea de a crea directori, comanda mkdir este un
program setuid recunoscut de ctre rdcin (drept de superutilizator). Cnd se
execut mkdir, procesul ruleaz cu drept de acces de superutilizator, creaz directorul
pentru utilizator cu mkdir i apoi schimb proprietarul i dreptul de acces al
directorului la acela al utilizatorului real.

8.7. Schimbarea dimensiunii unui proces


Un proces poate incrementa sau decrementa mrimea regiunii sale de date prin
utilizarea apelului sistem brk. Sintaxa pentru apelul sistem este:

brk(endds);

unde endds devine valoarea celei mai mari adrese virtuale a regiunii de date a
procesului (denumit valoare de break). Ca o alternativ, utilizatorul poate apela:

oldendds=sbrk(increment);

unde increment schimb valoarea curent de break prin specificarea numrului de bii
i oldennds este valoarea de break nainte de apel. Sbrk este o subrutin din biblioteca
C care apeleaz brk. Dac spaiul dedate al procesului crete n urma apelului, noul
spaiu de date alocat este virtual contiguu la vechiul spaiu de date; de aceea, spaiul
virtual de adrese al procesului se extinde n continuarea spaiului de date nou alocat.
Nucleul verific dac mrimea noului proces este mai mic dect maxima sistemului i
dac noua regiune de date nu se suprapune cu spaiul de adrese virtual asignat
anterior (figura 8.26). Dac toate verificrile sunt executate, nucleul apeleaz growreg
pentru a aloca memorie auxiliar (de exemplu, tabele de pagini) pentru regiunea de
date i incrementeaz cmpul de mrime a procesului. n sistemul cu swapping,
ncearc de asemenea s aloce memorie pentru noul spaiu i i se iniializeaz
coninutul cu 0; dac nu exist spaiu de memorie se face swap-out pentru a crea
spaiu (explicat n detaliu ncapitolul 9). Dac procesul apeleaz brk pentru a elibera
spaiul alocat nainte, nucleul elibereaz memoria; dac procesul acceseaz adrese
virtuale n spaiul de adrese ale paginilor eliberate, se creaz o "ntrereupere" de
memorie.
algoritmul brk
217

intrri: noua valoare break


ieiri: vechea valoare a lui break
{
blocheaz regiunea de date a procesului;
if(mrimea regiunii se mrete)
if(noua mrime a regiunii este ilegal)
{
deblocheaz regiunea de date;
ntoarce eroare;
}
schimb mrimea regiunii (algoritmul growreg);
iniializeaz la 0 adresele din noul spaiu de date;
deblocheaz regiunea de date a procesului;
}

Figura 8.26 Algoritmul pentru brk


n figura 8.27 prezint un program care utillizeaz brk i simuleaz ieirea cnd
ruleaz pe un calculator AT&T 3B20. Dup ce se aranjeaz interceptarea semnalului de
violare a segmentrii prin apelul semnalului signal, procesul face apelul sbrk i
tiprete valoarea sa iniial de break. Atunci se cicleaz, incrementnd pointerul
caracter i i scrie coninutul pn cnd se incearc s se scrie la o adres n afara
regiunii de date, provocnd semnalul de violare a segmentrii. Interceptnd semnalul,
catcher apeleaz sbrk pentru a aloca ali 256 de octei n regiunea de date; procesul
continu de unde a fost ntrerupt n ciclu, scriind n noul spaiu de adrese alocat. Cnd
se cicleaz n afara regiunii de date din nou, se repet ntreaga procedur. Un
fenomen interesant se ntmpl la mainile a cror memorie este alocat prin pagini,
la fel ca la 3B20. O pagin este cea mai mic unitate de memorie care este protejat
prin hard i deci, prin hard nu se poate detecta atunci cnd un proces scrie la adrese
care sunt dincolo de valoarea break dar nc ntr-o pagina semilegal. Acest lucru
este artat n ieirea din figura 8.27: primul apel sbrk returneaz 140924, ceea ce
nseamn c sunt 388 octei lsai n pagin, care conine 2K pe 3B20.
#include <signal.h>
char *cp;
int callno;
main()
{
char *sbrk();
extern catcher();
signal(SIGSEGV, catcher);
cp=sbrk(0);
printf(" valoarea original a lui brk este %u\n", cp);
for(;;)
218

*cp++=1;
}
catcher (signo)
int signo;
{
callno++;
printf(" semnalul prins %d %d la adresa %u\n", signo, callno, cp);
sbrk (256);
signal ()SIGSEGV, catcher);
}

Valorile obinute sunt:


valoarea original a lui brk 140924
semnalul prins 11 1th la adresa 141312
semnalul prins 11 2th la adresa 141312
semnalul prins 11 3th la adresa 141312
(unele adrese sunt tiprite la al 10-lea apel)
semnalul prins 11 10th la adresa 141312
semnalul prins 11 11th la adresa 141312
(unele adrese sunt tiprite la al 10-lea apel)
semnalul prins 11 18th la adresa 141312
semnalul prins 11 19th la adresa 141312
Figura 8.27 Utilizarea lui brk i ieirea simulat pe un AT&T
Procesul va fi ntrerupt doar cnd se adreseaz pagina urmtoare, la adresa 141312.
Interceptorul adun 256 la valoarea break, fcnd-o 141180, valoare nc mic pentru
a fi adres n pagina urmtoare. De aceea, procesul se ntrerupe imediat, tiprind
aceeai adres, 141312. Dup urmtorul sbrk, nucleul aloc o nou pagin de
memorie, aa c nucleul poate accesa ali 2ko, la 143360, chiar dac valoarea break
nu este aa mare. Cnd se ntrerupe, va apela sbrk de 8 ori (pn cnd acesta poate
continua). Astfel, un proces poate uneori s se desfoare n afara adresei sale de
break, cu toate c acesta este un stil de programare necorespunztor.
Nucleul extinde automat mrimea stivei utilizator cnd este depit, urmnd un
algoritm similar cu acela pentru brk. Procesul original conine destul spaiu de stiv
pentru a pstra parametrii lui exec, dar poate aprea o depire de stiv n timp ce se
depun date n aceasta de-a lungul execuiei. Cnd se depete stiva proprie, maina
creaz o ntrerupere de memorie, deoarece procesul a ncercat s acceseze o locaie n
afara spaiului su de adrese. Nucleul determin c motivul pentru care a avul loc o
ntrerupere de memorie a fost depirea stivei prin compararea valorii SP-lui cu
mrimea regiunii de stiv. Nucleul aloc spaiu nou pentru regiunea de stiv aa cum
se aloc spaiu pentru brk, mai sus. Cnd se ntoarce din ntrerupere procesul are
spaiu de stiv pentru a continua.

219

8.8. Shell-ul
Acest capitol a acoperit suficiente noiuni pentru a explica cum lucreaz shell-ul. Shellul este mai complex dect este descris aici, dar relaiile procesului sunt ilustrate de un
program real. Figura 8.28 arat ciclul principal al shell-lui i demonstreaz execuia
asincron, redirectarea ieirii i conductele de tip pipe.

/* citete linia de comand pn la "sfritul fiierului" */


while (read(tdin, buffer, numchars))
{
/* analizeaz linia de comand */
if(/* linia de comand conine & */
amper=1;
else
amper=0;
/* pentru comenzi ce nu aparin limbajului de comand shell */
if(fork()==0)
{
/* redirectare a I/O */
if(/* redirectare ieire */
{
fd=creat(newfile, fmask);
close (stdout);
dup(fd);
close(fd);
/* stdout este acum redirectat */
}
if(/* cerere de redirectare ctre o conduct */
{
pipe(fildes);
if(fork()==0)
{
/* prima component a liniei de comand */
close(stdout);
dup(fildes[1]);
close(fildes[1]);
close(fildes[0]);
/* stdout este redirectat acum ctre o conduct */
220

/* procesul fiu execut comanda */


execlp(command1, command2, 0);
}
/* a doua component a liniei de comand */]
close(stdin);
close(fildes[0]);
close(fildes[1]);
/* intrarea standard este acum o conduct */
}
execv(command2, command2, 0);
}
/* printele continu de aici...
* ateapt terminarea fiului dac este nevoie*/
if(amper==0)
redit=wait(&status);

Figura 8.28 Ciclul principal al shell-lui


Shell-ul citete o linie de la intrarea sa standard i o interpreteaz dup un set fixat de
reguli. Descriptorii de intrare i ieire standard ai fiierelor pentru shell-ul login sunt
uzual terminalul pe care utilizatorul i folosete aa cum se va vedea n capitolul 10.
Dac shell-ul recunoate irul de intrare ca o comand intern (de exemplu, comenzile
cd, for, while i altele), se execut comanda intern dar fr a mai crea procese; altfel,
presupune comanda ca fiind numele unui fiier executabil.
Cea mai simpl linie de comand conine un nume de program i civa parametri cum
ar fi:

who
grep -n include *.c
ls -1

Shell-ul face un apel fork i creaz un proces fiu care execut programul pe care
utilizatorul l specific n linia de comand. Procesul printe, shell-ul care este folosit
de utilizator, ateapt pn cnd procesul fiu iese din comanda dat i atunci revine
pentru a citi comanda urmtoare.
Pentru a rula un proces asincron (n fundal), ca de exemplu

nroff -mm big document &

221

shell-ul seteaz o variabil intern amper cnd analizeaz caracterul &. Dac gsete
variabila setat la sfritul buclei, nu se execut wait dar imediat rencepe ciclul i
citete urmtoarea linie de comand.
Poza arat c procesul fiu are acces la copia liniei de comand dup fork. Pentru a
putea redirecta ieirea standard a fiierului, ca de exemplu

nroff -mm bigdocument>output

procesul fiu creaz fiierul de ieire specificat n linia de comand; dac apelul creat nu
se ncheie cu succes (de exemplu, la crearea fiierului n director fr drept de acces ),
procesul fiu ar trebui s apeleze imediat exit. Dc apelul creat se ncheie cu succes,
procesul fiu nchide fiierul de ieire standard anterior i duplic descriptorul de fiier
al noului fiier de ieire. Descriptorul fiierului de ieire standard refer acum fiierul
de ieire redirectat. Procesul fiu nchide descriptorul de fiier obinut prin apelul creat
pentru a conserva descriptorii de fiier pentru programul executat. Shell-ul
redirecteaz fiierele de intrare i eroare standard ntr-un mod similar.

Shell
atept
iese
wc
citete

scrie
ls-1
Figura 8.29 Relaiile proceselor pentru ls -l|wc
Codul arat cum shell-ul ar trata o linie de comand cu o singur conduct (pipe), ca
de exemplu

ls -l|wc

Dup ce procesul printe apeleaz fork i creaz un proces fiu, fiul creaz o conduct
de tip pipe. Procesul fiu execut fork; el i fiecare fiu al su trateaz fiecare cte o
component a liniei de comand. Procesul fiu (mai mare) creat prin al doilea fork
execut prima component a comenzii (ls): scrie n conduct, aa c nchide
descriptorul de fiier de ieire standard, duplic descriptorul de scriere al conductei i
nchide descriptorul original de scriere al conductei n momentul cnd nu mai este
necesar. Printele (wc) ultimului proces fiu (ls) este fiul procesului shell original (vezi
figura 8.29). Acest proces (wc) nchide propriul fiier de intrare standard i duplic
222

descriptorul de citire al conductei fcndu-l s devin propriul descriptor de fiier de


intrare standard. Apoi nchide descriptorul de citire al conductei originale pentru c nu
mai are nevoie de el i execut a doua component a comenzii din linia de comand
original. Cele dou procese care execut linia de comand, o execut asincron i
ieirea unui proces este intrare pentru cellalt proces. Procesul shell ntre timp
ateapt pentru ca procesul su fiu (wc) s ias (exit), apoi procedeaz n mod uzual:
ntreaga linie de comand este apelat atunci cnd procesul wc iese. Shell-ul face o
bucl i citete urmtoarea comand.

8.9. ncrcarea sistemului i procesul init

Pentru a iniializa un sistem dintr-o stare inactiv, un administrator trece printr-o


secven "bootstrap": administratorul boot-eaz sistemul. Procedura pentru boot
variaz n funcie de tipul mainii, dar scopul este comun la toate: de a face o copie a
sistemului de operare n memorie i de a ncepe execuia lui. Aceasta este fcut ntr-o
serie de stadii; de aici i numele de "bootstrap". Administratorul trebuie s seteze
switch-urile la consola calculatorului pentru a specifica adresa special hard a codului
programului bootstrap sau doar s apese un singur buton destinat s fac maina s
ncarce programul bootstrap din microcodul su. Acest program poate s conin doar
cteva instruciuni care coordoneaz maina s execute alt program. Pe sistemele
UNIX, procedurile bootstrap citesc eventual blocul boot (blocul 0) al discului i l
ncarc n memorie. Programul coninut n blocul boot ncarc nucleul din fiierul
sistem (din fiierul "/unix" de exemplu, sau alt nume specificat de administrator).
Dup ce nucleul este ncrcat n memorie, programul boot transfer controlul la adresa
de start a nucleului care pornete rularea (algoritmul start, figura 8.30).
Nucleul i iniializeaz structurile sale de date interne. De exemplu, construiete liste
link-editate de buffer-e i inoduri libere, construiete cozile de hash pentru buffere i
inoduri, iniializeaz structurile de regiune, intrrile n tabela de pagini i aa mai
departe. Dup terminarea fazei de iniializare, monteaz sistemul de fiiere rdcin
("/") i pregtete mediul pentru procesul 0, creaz u area, iniializeaz slotul 0 n
tabela proceselor i creaz rdcina directorului curent al procesului 0, printre altele.
Cnd mediul procesului 0 este setat, sistemul este n rulare ca proces 0. Procesul 0
apeleaz fork direct din nucleu pentru c el este executat n modul nucleu. Noul
proces, procesul 1, care ruleaz n modul nucleu, i creaz contextul la nivel de
utilizator prin alocarea unei regiuni de date i ataarea ei la propriul spaiu de adrese.
Mrete regiunea la mrimea potrivit i copiaz codul (cea mai scurt descriere) din
spaiul de adrese al nucleului n noua regiune: acest cod formeaz acum contextul la
nivel de utilizator al procesului 1. Procesul 1 seteaz apoi contextul registrului
utilizator salvat, se ntoarce din modul nucleu n modul utilizator i execut codul care
fusese copiat din nucleu. Procesul 1 este un proces la nivel utilizator care este n
opoziie cu procesul 0, care este un proces la nivel nucleu i care se execut n modul
nucleu. Codul pentru procesul 1, copiat din nucleu, const din apelul funciei sistem
exec pentru execuia programului "/etc/init". Procesul 1 apeleaz exec i execut
programul n mod normal. Procesul 1 este n mod normal numit init, pentru c el este
rspunztor pentru iniializarea noilor procese.
De ce nucleul copiaz codul apelului sistem exec n spaiul de adrese utilizator al
procesului 1? Ar putea s invoce versiunea intern a exec-lui direct din nucleu, dar
asta ar fi mai complicat dect implementarea deja descris.
223

algoritmul start

/* procedura de start a sistemului */

intrri: niciuna
ieiri: niciuna
{
iniializarea tuturor structurilor de date ale sistemului;
pseudo-montarea rdcinii;
pregtirea mediului pentru procesul 0;
apel fork pentru procesul 1:
{
/* procesul 1 */
aloc regiune;
ataeaz regiunea la spaiul iniial de adrese;
crete mrimea regiunii n concordan cu codul care va fi copiat
n ea;
copiaz codul din spaiul nucleu pentru a iniializa spaiul
utilizator pentru a executa init;
schimb modul din modul nucleu n modul utilizator;
/* init niciodat nu va fi aici... ca rezultat al modului schimbat
nainte, init

execut /etc/init i devine un proces utilizator "normal"

care respect apelurile sistem

*/

}
/* procesul 0 continu aici */
apel fork pentru procesele nucleu;
/* procesul 0 invoc swapper-ul pentru a face management-ul alocrii
spaiului de

adrese pentru proces n memoria principal i a dispozitivelor de swap.

Acesta este

un ciclu infinit;

bucl, dac nu este

procesul 0 este n mod obonuit n ateptare n

ceva de fcut pentru el */

execut codul pentru algoritmul swapper;


}
Figura 8.30 Algoritmul pentru boot-area sistemului
Pentru a urmri procedura descris mai nainte, apelul exec ar trebui s analizeze
numele fiierului n spaiul nucleului, nu doar n spaiul utilizator, ca n implementarea
curent. Aceast generalizare, dorit doar pentru init, ar complica codul lui exec i ar
micora performanele n cazurile obinuite.
Procesul init (figura 8.31) este un proces distribuitor, care creeaz procesele ce permit
utilizatorului s intre n sistem, printre altele. Init citete fiierul "/etc/inittab" pentru a
avea informaii referitoare la ce procese trebuiesc create (spawn). Fiierul
"/etc/inittab": conine linii care au un "id", un identificator de stare (unic utilizator,
224

multiutilizator, etc), o "aciune" (vezi exerciiul 8.43) i o specificaie de program (vezi


figura 8.32).
Init citete fiierul i, dac starea n care a fost apelat se potrivete cu identificatorul
de stare a liniei, creaz un proces care execut specificaia de program dat. De
exemplu, cnd se apeleaz init pentru starea de multiuser (starea 2), init n mod
obinuit lanseaz procesul getty pentru a supraveghea liniile terminale configurate
ntr-un sistem.

algoritmul init /* procesul de iniializare, procesul 1 al sistemului */


intrri: niciuna
ieiri: niciuna
{
fd=open("/etc/inittab",O_RDONLY);
while(linia cititq(fd, buffer))
{
/* citete fiecare linie a fiierului */
if(starea invocat!=starea citit n buffer)
continu;

/* sare napoi la while */

/* starea se potrivete */
if( fork()==0)
{
execl("procesul specificat n buffer");
exit();
}
/* procesul init nu ateapt */
/* sare la while */
}
while((id=wait((init *)0))!=-1)
{
/* aici verific dac procesul fiu lansat s-a terminat;
* consider relansarea lui */

225

/* n caz contrar, doar continu */


}
}
Figura 8.31 Algoritm pentru init

Format: identificator, stare, aciune, specificaie de program


Cmpuri separate de coloane
Comentarii la sfrit de linie precedate de '#'
co::respaw:/etc/getty console # consola ataat de main
46:2:respaw:/etc/getty -t 60 tty46 4800H # comentariu

Figura 8.32 Un fiier iniittab simplu


Cnd intr un utilizator cu succes, getty trece prin procedura login i execut shell-ul
login descris n capitolul 10. ntre timp, init execut apelul sistem wait, controlnd
terminarea propriilor procese fiu i terminarea proceselor fiu rmase "orfane" prin
terminarea proceselor printe (exit).
Procesele n sistemul UNIX sunt fie procese utilizator, fie procese de tip "daemon", sau
procese nucleu. Cele mai multe procese de pe sistemele obinuite sunt procese
utilizator asociate utilizatorilor de la un terminal. Procesele daemon nu sunt asociate
cu nici un utilizator, dar execut funcii ale sistemului, cum ar fi administrarea i
controlul reelelor, execuia activitilor dependente de timp, activiti de tiprire, etc.
Init trebuie s lanseze procese daemon care se nasc n timpul rulrii sistemului sau,
ocazional, acest lucru trebuie s-l fac# utilizatorul. Ele se aseamn cu procesele
utilizator n aceea c ele ruleaz n modul utilizator i fac apeluri sistem pentru a folosi
serviciile sistemului.
Procesele nucleu se execut doar n modul nucleu. Procesul 0 lanseaz procesele
nucleu, cum ar fi procesul vhand (care presupune paginarea) i apoi devine proces
swapper. Procesele nucleu sunt similare cu procesele daemon n aceea c ele
furnizeaz servicii ale sistemului, dar au un control mai mare asupra prioritilor de
execuie din moment ce codul lor face parte din nucleu. Ele pot accesa algoritmii
nucleului i structurile de date direct, fr a utiliza apelurile sistem, deci sunt extrem
226

de puternice. Totui, ele nu sunt aa flexibile ca procesele daemon, pentru c nucleul


trebuie recompilat pentru a le schimba.

227