Documente Academic
Documente Profesional
Documente Cultură
2
3.1. Exemple introductive.................................................................................................................................................2
3.1.1. Administrarea tamponat a intrrilor-ieirelor...................................................................................................2
3.1.2. Comanda unui proces industrial.........................................................................................................................3
3.2. Noiune de proces secvenial.....................................................................................................................................3
3.2.1. Proces unic. Context...........................................................................................................................................3
3.2.2. Relaii ntre procese............................................................................................................................................4
3.2.2.1. Mulimi de procese. Paralelism...................................................................................................................4
3.2.2.2. Concurena proceselor. Resurse virtuale.....................................................................................................5
3.2.2.3. Excludere mutual.......................................................................................................................................6
3.3. Sincronizarea proceselor...........................................................................................................................................6
3.3.1. Exprimarea i implementarea restriciilor de precedare.....................................................................................6
3.3.2. Probleme de realizare a sincronizrii.................................................................................................................7
3.3.3. Monitorul mecanism de sincronizare..............................................................................................................8
3.3.3.1. Definiii........................................................................................................................................................8
3.3.3.2. Exemple de utilizare....................................................................................................................................9
3.4. Implementarea sincronizrii....................................................................................................................................10
3.4.1. Probleme-tip.....................................................................................................................................................10
3.4.2. Administrarea unei resurse partajate................................................................................................................10
3.4.2.1. Alocarea resurselor banalizate...................................................................................................................10
3.4.2.2. Modelul cititorului i redactorului.............................................................................................................12
3.4.3. Comunicarea ntre procese...............................................................................................................................13
3.4.3.1. Modelul productorului i consumatorului...............................................................................................13
3.4.3.2. Primitive de comunicare............................................................................................................................14
3.4.3.3. Aplicaii : relaia client-server...................................................................................................................16
3.4.4. Administrarea intrrilor-ieirilor.......................................................................................................................16
3.4.4.1. Administrarea unui periferic......................................................................................................................16
3.4.4.2. Buferizarea imprimrii..............................................................................................................................17
3.4.5. Sincronizare temporal.....................................................................................................................................18
3.5. Gestionarea dinamic a proceselor..........................................................................................................................19
3.6. Sincronizarea n Windows.......................................................................................................................................21
3.6.1. Procese i fire...................................................................................................................................................21
3.6.2. Necesitatea sincronizrii...................................................................................................................................21
3.6.3. Structura mecanismului de sincronizare n Windows......................................................................................21
3.6.4. Administrarea obiectelor de sincronizare n Windows.....................................................................................22
3.6.4.1. Excluderea mutual...................................................................................................................................22
3.6.4.2. Evenimentele.............................................................................................................................................22
3.6.4.3. Semafoarele...............................................................................................................................................22
3.6.4.4. Seciunile critice........................................................................................................................................22
3.6.4.5. Protejarea accesrii variabilelor................................................................................................................23
3.6.4.6. Sincronizarea n MFC................................................................................................................................23
3.6.5. Exemplu de sincronizare n Windows..............................................................................................................23
3.6.6. Utilizarea seciunilor critice n Windows.........................................................................................................24
3.6.6.1. Structura RTL_CRITICAL_SECTION.....................................................................................................25
3.6.6.2. Funcii API pentru seciunile critice..........................................................................................................26
3.6.6.3. Clase de seciuni critice.............................................................................................................................28
3.6.6.4. Depanarea seciunilor critice.....................................................................................................................28
3.7. Exerciii la capitolul 3.............................................................................................................................................31
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.1
SL
SD
CD
tm1
(memorie)
IF
tm2
td
(memorie)
(disc)
Fig. 3.1. Gestionarea buferizat a unei imprimante
Aceste patru activiti sunt n mare msur autonome, or ele sunt executate pe procesoare distincte cu programe
diferite. Ele nu sunt totui independente, deoarece acceseaz obiecte comune: dou zone tampon n memoria operativ,
tm1 i tm2 i una pe disc, td. Pot fi evideniate dou tipuri de condiii, care trebuie respectate:
1) Condiii, care stabilesc posibilitatea existenei activitilor
O nregistrare nu poate fi preluat dintr-o zon tampon nainte de a fi depozitat aici. Zonele tampon au capaciti
limitate. Dac o zon tampon este total ocupat cu nregistrri, care nu au fost nc preluate, este imposibil depozitarea.
Astfel, aciunile de depozitare i preluare sunt supuse unor condiii de existen, formulate mai jos.
Activitate
Aciune
Condiie
SL
scriere n tm1
tm1 nu este plin
SD
citire din tm1
tm1 nu este vid
SD
scriere n td
td nu este plin
CD
citire din td
td nu este vid
CD
scriere n tm2
tm2 nu este plin
IF
citire din tm2
tm2 nu este vid
Derularea activitilor modific valorile de adevr ale acestor condiii. De exemplu, imprimarea unei linii pune n
TRUE condiia tm2 nu este plin.
O activitate, care nu poate executa o aciune, deoarece condiia asociat are valoare FALSE, trebuie s atepte, adic
execuia unei aciuni trebuie ntrziat pn cnd valoarea logic a condiiei devine TRUE. n capitolul 2 a fost discutat
realizarea acestui mecanism de ateptare i continuare cu ajutorul ntreruperilor.
2) Condiii de validitate a informaiilor partajate
Dac vom examina procesul de accesare a zonelor tampon vom descoperi o alt form de interaciune, cauzat de
posibilitatea de accesare simultan de ctre dou activiti a unui amplsament de memorie. Astfel, dac SD citete
coninutul unei nregistrri din tm1 pe care SL este n curs de a o modifica, rezultatul acestei citiri risc s fie incoerent,
dac nu vor fi luate msuri speciale de precauie. Problema poate fi rezolvat impunnd una din activiti, aflate n
conflict, s atepte pn cnd cealalt va termina accesarea.
Concluziile acestei prime analize:
Lucrarea imprimare buferizat este realizat prin intermediul a patru activiti simultane, n mare msur
autonome, care coopereaz pentru atingerea scopului final.
n realitate, citirea i scrierea pe disc sunt executate pe acelai canal, ceea ce poate impune unele restric ii suplimentare privind simultaneitatea
executrii lor.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.2
Executarea corect a lucrrii impune respectarea anumitor restricii logice n derularea activit ilor. Aceste restricii
pot conduce la ntrzierea execuiei unei activiti, care este obligat s atepte producerea unui eveniment, provocat de
o alt activitate.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.3
programului. Fiecare executare a unei instruciuni constituie o aciune: o aciune are ca efect trecerea n timp finit a
calculatorului dintr-o stare iniial ntr-o stare final. Acest efect este descris n specificaiile instruciunii.
Executarea unei instruciuni va fi considerat indivizibil sau atomar, adic este interzis observarea sau definirea
strii mainii n timpul acestei execuii. Timpul utilizat pentru descrierea evoluiei strii este un parametru t, care poate
lua o serie de valori discrete cresctoare, corespunztoare nceputului i sfritului instruciunilor. Aceste instane sunt
numite puncte de observare, iar strile corespunztoare ale mainii sunt numite puncte observabile. Prin abuz de
limbaj, acest ultim termen desemneaz n egal msur i punctele de observare. Acestea permit s se asocieze data
numit valoarea curent a lui t unei stri a mainii stare datat. O stare datat define te un eveniment. Evenimentele
permit reperarea modificrilor strii mainii. nceputul i sfritul unei aciuni a sunt evenimente datele crora vor fi
notate prin nceput(a) i sfrit(a); vom avea, evident, relaia nceput(a) precede (<) sfrit(a).
Astfel, executarea unui program se traduce ntr-o suit de aciuni a1, a2,..., ai,..., cu nceput(ai) < sfrit(ai). O
astfel de suit este numit proces secvenial (sau simplu proces).
Un proces poate, deci, fi descris cu ajutorul succesiunii evenimentelor nceput(a1), sfrit(a1), nceput(a2),
sfrit(a2),... Aceast suit de stri datate ale mainii se numete traiectorie temporal (sau istorie) a procesului. Putem
de asemenea defini un proces ca o suit de activiti n sensul lui 2.1.
Mulimea informaiilor pe care aciunile unui proces le pot consulta sau modifica se numete contextul procesului.
Relund modelul de execuie secvenial din 2.1, contextul unui proces rezultant din executarea unui program conine:
1) Contextul procesorului (cuvntul de stare i registrele),
2) Un context n memorie - segmente procedur, date, stiv de execuie,
3) O mulime de atribute ataate procesului i care specific diferite proprieti:
a) Nume. Numele unui proces, care servete pentru identificarea lui, este de regul, un numr intern atribuit la
crearea procesului i care permite s se ajung la reprezentarea contextului su (v. 4.2).
b) Prioritate. Prioritatea proceselor permite ordonarea lor pentru alocarea procesorului (v. 4.2, 4.3). Dac
toate procesele au aceeai prioritate, alocarea se face conform ordinii prim sosit, prim servit.
c) Drepturi. Drepturile unui proces specific operaiile care i sunt permise, n scopul asigurrii proteciei
informaiei (v. 5.1.4).
Traiectoria temporal a unui proces este definit de irul strilor contextului su (procesor i memorie) preluate dup
execuia fiecrei instruciuni. Prin definiie, starea restului mainii nu este modificat de execuia procesului.
Fie dou procese care partajeaz o procedur reentrant. Segmentul executabil, care conine aceast procedur aparine contextului
comun. Segmentul de date i stiva fiecrui proces formeaz contextul lor privat.
(2)
(3)
ntr-un mod mai riguros ar trebui s adugm aici punctele ntreruptible care pot fi prezente n unele instruciuni de lung durat; o executare a
unei asemenea instruciuni poate fi considerat o suit de mai multe aciuni.
Pentru comoditatea expunerii considerm, c sfritul unei aciuni i nceputul aciunii urmtoare sunt dou evenimente distincte, datele crora sunt
diferite, dei strile corespunztoare sunt identice.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.4
schema 1: este executat mai nti procesul p n ntregime, apoi procesul q la fel n totalitate,
schema 2: sunt executate iruri de instruciuni ale procesului p n mod alternativ cu iruri de instruciuni ale
procesului q, i tot aa pn la terminarea ambelor procese,
schema 3: executarea proceselor p i q este simultan; n acest caz sunt necesare dou procesoare.
Pentru compararea acestor scheme de execuie introducem noiunea nivel de observare. Putem considera o suit de
aciuni ale unui proces ca o aciune unic, adic s observm derularea unui proces considernd o unitate de execuie
mai puin fin dect instruciunea. De exemplu, dac vom redefini noiunea de aciune elementar ca execuie a unei
proceduri, traiectoria procesului va conine doar strile definite de fiecare apel i retur de procedur. Nivelul de
observare cel mai fin (cel al instruciunilor) este numit nivel de baz.
S ne situm mai nti la nivelul de observare la care, prin convenie, executarea complet a fiecrei dintre
programele P i Q reprezint o aciune unic. Definiiile care urmeaz sunt pentru acest nivel.
a) schema de tip 1 este a unei execuii secveniale a lui p i q. Ea este caracterizat de relaiile:
sfrit(q) < nceput(p) sau sfrit(p) < nceput(q)
b) schemele de tip 2 sau 3 sunt scheme de execuie paralel. Ele sunt caracterizate de
sfrit(p) > nceput(q) sau sfrit(q) > nceput(p).
Revenim la nivelul de baz. Putem face o distincie ntre schemele 2 i 3. ntr-adevr, n schema 2, din considerente
de existen a unui singur procesor, la un moment de timp dat doar o singur aciune poate fi executat, contrar schemei
3. Se va spune c n schema 3 are loc un paralelism real, iar n schema 2 un pseudo-paralelism. Paralelismul real
solicit procesoare distincte. Dou observaii importante sunt necesare:
1) Diferena acestor scheme de execuie este legat de alegerea nivelului de observare. Astfel, la nivelul de baz,
diferena dintre schemele 1 i 2 dispare: ambele sunt secveniale.
Exemplu 3.2.
Utilizatorii unui sistem de operare, care funcioneaz n timp partajat pe un singur procesor, au impresia c programele lor sunt
executate n mod paralel, deoarece nivelul lor de observare este cel al comenzilor limbajului de comand, compuse din mai multe
instruciuni. La nivelul de baz, ns, aceste instruciuni sunt atomare i executate de procesor n mod secvenial. Exemplul celor
dou reactoare chimice, prezentate n 3.1.2, conduce la observaii analogice.
2) Alegerea nivelului de observare depinde de fineea fenomenelor, care dorim s le considerm elementare. Dac
trebuie s studiem executarea instruciunilor n pipe-line pe un procesor microprogramat, n calitate de nivel
de observare va fi ales nivelul microinstruciunilor, iar contextul va fi completat cu memoria microprogramelor
i registrele interne.
Vom examina realizarea, la nivelul de baz, a unei scheme de execuie de tipul 2. La fiecare realocare a procesorului,
contextul procesului curent trebuie salvat, pentru a permite reluarea ulterioar a acestei execuii. Dac memoria are o
capacitate suficient pentru a pstra toate segmentele, doar contextul procesorului trebuie salvat. Dac memoria
principal poate conine, la un moment de timp dat, doar segmentul procedur i datele unui singur proces, aceste
segmente de asemenea trebuie salvate pe disc. Aceast remarc justific definiia operaional, adesea ntlnit, a
contextului unui proces ca mulime a informaiilor care trebuie salvat pentru a permite reluarea ulterioar a
procesului, dac execuia acestuia a fost ntrerupt.
3.2.2.2. Concurena proceselor. Resurse virtuale
Situaia descris de schemele 1 i 2 nu rezult dintr-o legtur logic ntre p i q, ci doar din existena unui singur
procesor. Ea poate fi caracterizat astfel: fie o mulime de procese contextele crora au un obiect comun, care poate fi
utilizat la un moment de timp dat de un singur proces. Se va spune n acest caz, c obiectul constituie o resurs critic
pentru procesele date sau c procesele sunt n excludere mutual (excludere reciproc sau concuren) pentru
utilizarea unei resurse. n situaia descris, procesorul este o resurs critic pentru procesele p i q.
Observm c excluderea mutual a unei resurse conduce la serializarea execuiei proceselor concurente, n cazul
unor aciuni, care cer aceast resurs (n exemplul dat, toate aciunile). Schemele 1 i 2 difer doar prin nivelul de finee
la care este executat serializarea.
Funcionarea corect a unei mulimi de procese, care particip la ndeplinirea unei lucrri comune, implic relaii
logice de cooperare (v.3.1.1). Este comod s se separe aceast cooperare de concurena pentru resursele fizice cu
scopul de a simplifica nelegerea i aplicarea celor dou tipuri de relaii. Pentru aceasta este folosit noiunea de
resurse virtuale: fiecrei resurse fizice critice i se asociaz tot attea copii imaginare (sau virtuale) ale acestei resurse
cte procese concurente solicit utilizarea ei. Suntem nevoii s tratm dou probleme distincte:
1) respectarea relaiilor de cooperare ntre procesele, care, fiecare posed (conceptual) resursele fizice solicitate i
pentru care paralelismul n execuie nu este restricionat de competiia pentru resurse,
2) reglarea problemei de concuren pentru resursele fizice printr-o serializare convenabil a execuiei proceselor
n cauz. Se va spune n acest caz, c realizm o alocare a resurselor fizice.
Introducerea resurselor virtuale are o consecin foarte important pe care o vom ilustra-o prin exemplul proceselor
p i q, definite n 3.2.2.1. S atam fiecrui proces un procesor virtual. Conceptual, totul va avea loc ca i cum
procesele s-ar derula paralel, conform unei scheme, numite logice sau virtuale, analogice schemei 3 din fig.3.2. Cu toate
acestea, trebuie de menionat, c aceast schem logic reprezint doar o notaie compact pentru mulimea schemelor
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.5
reale posibile i c ele sunt obligatoriu de forma 1 sau 2 din considerentele unicitii procesorului. Pentru o schem
real i una virtual a unui proces dat este pstrat doar ordinea de succesiune a evenimentelor (nceputul i sfritul
aciunii) i nu sunt pstrate valorile absolute ale intervalelor de timp, care le separ. n absena altor informaii, nu
putem spune nimic apriori despre ordinea evenimentelor, asociate unor procese distincte. Timpul folosit la reperarea
evenimentelor n schema logic este numit timp logic; relaiile sale cu timpul real sunt prezentate n fig.3.3.
a1
a2
+------+-----------------+------+
b1
b2
+--+-------+---------+
procesul p
(timp logic)
procesul q
a1
a2
+------+---+
+----------+
+----+------+
+--+--+
+-----+---------+
b1
b2
a1
a2
+---+
+---+-----------------+------+
+--+----+
+---+---------+
b1
b2
n toate cazurile a1 precede a2, b1 precede b2.
p
q
p
q
Dou procese p i q trebuie fiecare s actualizeze valoarea unei variabile comune n (de exemplu, n este starea unui cont bancar
pentru care p i q efectueaz o depozitare). Aciunile respective se vor scrie n programele lui p i q:
procesul p:
Ap : n=n+np
procesul q:Aq : n=n+nq
S realizm decompoziia acestor aciuni n instrucii, notnd prin Rp i Rq registrele locale ale proceselor p i q respectiv:
procesul p
procesul q
1.
load Rp, n
1. load Rq, n
2.
add Rp, np
2. add Rq, nq
3.
sto Rp, n
3. sto Rq, n
Dac aceste aciuni sunt executate n ordinea 1, 1, 2, 2, 3, 3, efectul lor global este de executat n:=n+nq i nu n:=n+np+nq: una
din actualizri a fost pierdut i valoarea final a variabilei n este incorect. Pentru evitarea acestei incoerene aciunile Ap i Aq
trebuie s fie executate n excludere reciproc; se zice de asemenea c ele constituie pentru p i q seciuni critice. Este necesar s se
respecte condiia
sfrit(Ap) < nceput(Aq) sau sfrit(Aq) < nceput(Ap).
Aceast condiie de serializare, care are efectul de a face Ap i Aq s devin atomare, este identic condiiei deja ntlnite n 3.2.2.2
la accesarea unei resurse fizice critice.
Excluderea mutual const n extinderea pentru secvena de aciuni a proprietii de indivizibilitate a aciunilor
nivelului de baz (aciuni atomare). Posibilitatea executrii unor aciuni atomare se afl la baza mecanismelor care
realizeaz sincronizarea.
Procesul p transmite informaii procesului q scriind ntr-un segment a, consultat de q (se presupune c aceast transmitere are loc o
singur dat). Este necesar s se verifice condiia:
sfrit(scriere(a)) < nceput(citire(a))
Aceast relaie exprim restricia, c citirea lui a de ctre q nu poate ncepe nainte de terminarea scrierii lui a de ctre p.
Exemplul 3.5.
Rendez-vous. Fie N procese p1,..., pN. Definim n programul fiecrui proces un punct, numit rendez-vous, pe care procesul nu-l poate
trece nainte ca alte procese s ajung la punctul lor propriu de rendez-vous.
Dac programul procesului pi are forma
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.6
<debut_i>;
<rendez-vous>
<continuare_i>;
atunci restriciile de sincronizare se vor exprima dup cum urmeaz:
Exemplul 3.6:
procesul p
procesul q
scriere(a);
f:=true;
<continuare_p>
<debut_q>;
ps :
citire(a)
aut(ps) :
f:=true
A fost introdus variabila de stare f pentru a exprima condiia procesul p a terminat aciunea scriere(a).
var n : integer init 0;
Exemplul 3.7:
ps:
procesul pi
<debut_i>;
n:=n+1
<continuare_i>
Exemplul 3.8.
27.11.16
10:03
procesul p
procesul q
scriere(a);
e:=TRUE;
<debut_q>;
ateptare(e);
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.7
<continuare_p>
citire(a)
Analiza acestui sistem (care nu conine alte variabile de stare, dect evenimentul memorizat e) poate fi fcut
enumernd traiectoriile temporale posibile. Aceast analiz arat, c sincronizarea este corect atunci cnd operaiile
asupra evenimentului memorizat sunt indivizibile.
var e : eveniment memorizat;
n : integer init 0;
Exemplul 3.9.
procesul pi
(A)
(B)
<debuti>;
n:=n+1;
if n<N then
ateptare(e)
else
e:=TRUE
endif
<continuarei>
O analiz mai atent ne arat c acest program nu este tocmai corect. Notnd un registru local al procesului i prin Ri,
aciunile (A) i (B) pot fi descompuse dup cum urmeaz:
(1)
(2)
(3)
(4)
load Ri, n
ai
Ri, 1
-- adunare imediat
ci
Ri, N
--comparare imediat
br () etichet
-- salt dac Ri N
<ateptare (e)>
...
etichet : ...
Presupunem, c toate proceselor sunt n ateptare n punctele lor de rendez-vous, cu excepia a dou, notate prin pj i
pk. Dac pj i pk vor fi executate pe traiectoria temporal (1j, 1k, 2j,...), atunci fiecare va atribui lui n valoarea final N-1
i se va bloca, drept rezultat toate procesele se vor bloca pentru un timp indefinit.
Reieind din analiza de mai sus putem concluziona, c implementarea condiiilor de sincronizare nu poate fi corect
realizat numai cu ajutorul operaiilor de ateptare. Consultarea i modificarea variabilelor de stare, care intervin n
aceste condiii, trebuie s fie executate n regim de excludere reciproc. Observaia dat ne impune s introducem un
mecanism de sincronizare, care n mod automat ar asigura acest regim de funcionare.
c.vid
: funcie cu valori booleene (true, dac nu exist nici un proces n ateptarea lui c, altfel false)
c.semnalizare : if non c.vid then <deblocarea proceselor care sunt n ateptarea lui c> endif
Procesele, care sunt n ateptarea unei condiii c, sunt grupate ntr-un fir de ateptare asociat lui c. Putem spune, c o
condiie furnizeaz proceselor un instrument de desemnare a unui astfel fir de ateptare.
Un proces deblocat de c.semnalizare i reia execuia de la instruciunea, care urmeaz imediat primitivei
c.ateptare, care-l blocase. Necesitatea asigurrii excluderii reciproce pentru variabilele de stare impune o restricie
suplimentar mecanismelor de deblocare: atunci cnd un proces p deblocheaz un proces q printr-o operaie de
semnalizare, p i q nu pot fi meninute simultan active. Se va impune blocarea lui p pn n momentul n care q, la
rndul su, se va bloca sau va prsi monitorul. Pentru evitarea unei blocri indefinite a lui p este necesar ca operaia de
transfer a comenzii de la p la q s fie atomar (indivizibil) i se va garanta, c un proces care a fost temporar blocat
este deblocat de operaia semnalizare nainte ca un nou proces s poat executa o procedur a monitorului.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.8
Observaie. Problema excluderii mutuale , legat de primitiva semnalizare nu se va pune, dac aceasta este ultima operaie ntr-o
procedur a monitorului. Acest caz este foarte frecvent n practic, unele monitoare (de exemplu, Concurrent Pascal) respect n mod
obligator restricia dat.
sinc: monitor;
var fact: boolean;
term: condiie;
procedura terminare_scriere;
begin
fact:=true;
term.semnalizare
end
procedura debut_citire;
if non fact then
term.ateptare
endif
begin
fact := false
end
end sinc
-- iniializare
Exemplul 3.11.
procesul p
procesul q
scriere(a);
sinc.terminare_scriere;
<continuare_p>
<debut_q>
sinc.debut_citire;
citire(a);
rendez-vous : monitor;
var n
toi_sosii
: integer;
: condiie;
procedura sosire;
begin
n := n+1;
if n < N then
toi_sosii.ateptare
endif
toi_sosii.semnalizare
end
begin
n:=0
end
end rendez_vous
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.9
modul de folosire a resursei presupune utilizarea obligatorie a procedurilor de acces asociate resursei; orice
tentativ de utilizare, care nu respect acest mod este detectat automat,
procedurile de accesare sunt grupate ntr-un monitor, sau mai multe, programul cruia impune respectarea
restriciilor de integritate.
Cel mai simplu caz este acela al unei resurse pentru care singura restricie de integritate este de a fi utilizat n
excludere reciproc. Simpla grupare a procedurilor sale de acces ntr-un monitor unic garanteaz respectarea acestor
restricii.
3.4.2.1. Alocarea resurselor banalizate
Considerm o resurs pentru care exist un numr fix de N exemplare. Un proces poate accesa la cerere n uniti din
cele N, le poate utiliza i apoi elibera. O unitate, folosit de un proces, se numete alocat procesului, care o utilizeaz,
pentru toat perioada de la accesare pn la eliberare. Toate unitile sunt echivalente din punctul de vedere al
proceselor utilizatoare, vom mai zice c resursa este banalizat. Zonele-tampon din memoria principal sau pe disc,
unitile de band magnetic, etc. sunt exemple de resurse banalizate.
Vom admite urmtoarele reguli de utilizare:
o unitate poate fi alocat la un moment de timp dat doar unui singur proces,
o unitate poate fi alocat unui proces, care cere alocarea, doar dac ea este liber (nu este alocat nici unui
proces),
o operaie de eliberare este aplicat totdeauna ultimelor resurse, achiziionate de procesul care execut
eliberarea,
o cerere de alocare este blocant n caz de eec (numr insuficient de uniti libere).
Definim dou proceduri cerere i eliberare ale unui monitor. Utilizarea resursei are loc conform schemei de mai
jos.
ps:resurse.cerere(n);
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.10
disp.ateptare;
endwhile;
nlibere:=nlibere-n
end;
procedura eliberare(n);
begin
nlibere:=nlibere+n;
disp.semnalizare -- deblocare n lan
end;
begin
nlibere:=N
end
end resurse
-- iniializare
Nu a fost fcut nici o ipotez despre ordinea proceselor n firul de ateptare al condiiei disp. Drept consecin, la
fiecare eliberare vor fi deblocate toate procesele. Este o soluie greoaie i poate fi foarte costisitoare, dac exist multe
procese. Este preferabil s se programeze n mod explicit administrarea firului de ateptare a cererilor, ceea ce oblig s
avem cte o condiie distinct pentru fiecare proces.
Pentru elaborarea programelor vom introduce un tip discret proces, variabilele cruia permit desemnarea proceselor.
resurse : monitor
type element : struct
lungime : integer
proc
: proces
end;
var nlibere : integer;
disp :array[proces] of condiie;
<declaraia firului de ateptare f i procedurilor sale de accesare: introducere, extragere, primul>
procedura cerere(n);
begin
var e: element;
if n>nlibere then
e.lungime:=n;
e.proc:=p;
introducere(e,f);
disp[p].ateptare
endif;
nlibere:=nlibere-n
end;
procedura eliberare(n);
var e: element;
begin
nlibere:=nlibere+n;
while primul(f).lungime nlibere do
extragere(e,f);
disp[e.proc].semnalizare
endwhile;
end;
begin
nlibere:=N;
f:=<vid>
end
end resurse
-- iniializare
Aceast soluie este mult mai compact i mai general, deoarece permite o mai bun separare a expresiei
sincronizrii (ceea ce rezult din structura monitorului) de politica de alocare (care este definit de procedurile de
gestionare a firului de ateptare introducere i extragere).
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.11
sau
(nscr=0) i (ncit0)
-- fiier n citire
-- fiier n scriere
Fie fich un monitor care asigur respectarea acestor restricii. Vom impune urmtoarea form a acceselor la fiier:
proces cititor
proces scriitor
fich.debut_citire;
<acces citire>
fich.terminare_citire;
fich.debut_scriere;
<acces scriere>
fich.terminare_scriere;
scr=false
aut(scriere):
scr=false i nc=0 (nici scrieri nici citiri n curs, nici cititori n ateptarea)
procedura terminare_scriere;
begin
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.12
scr:=false;
if nc>0 then
c_cit.semnalizare
else
c_scr.semnalizare
endif
end
begin
scr:=false;
nc:=0
end
end fich
-- iniializare
: 0..N;
: condiie;
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.13
begin
if n=0 then
non_vid.ateptare
endif;
preluare(m);
n:=n-1;
non_plin.semnalizare
end;
begin
-- iniializare
n:=0;
end
end tampon
Procedurile introducere(m) i preluare(m) definesc politica de gestionare a tamponului i reprezentarea intern a
mesajelor. Un caz frecvent este acela n care mesajele sunt reprezentate de elementele succesive ale unui tablou,
administrat n mod circular. n acest caz mesajele sunt preluate n ordinea depozitrii. Procedurile de gestionare a
tamponului se vor scrie astfel:
type mesaj : <descrierea formatului mesajelor>
ptr
: 0..N-1;
var fa
: array[ptr] of mesaj;
top, coad: ptr;
procedura intrare(m:mesaj);
begin
fa[coad]:=m;
coad:=coad+1 mod N
end;
procedura ieire(var m:mesaj);
begin
m:= fa[top];
top:=top+1 mod N
end;
<iniializarea se va completa cu top:=0; coad:=0;>
Aceast schem poate fi extins pentru mai muli productori i consumatori. Drept efect, procedurile monitorului
asigur accesarea exclusiv la mesaje ntre productori i consumtori. Totui, n cazul a mai muli consumatori schema
nu permite direcionarea unui mesaj ctre un consumator anumit: putem doar garanta, c un mesaj va fi preluat de un
consumator (i numai de unul singur) fr a specifica concret de care.
3.4.3.2. Primitive de comunicare
Schimbul de mesaje ntre procese, n afar de funcia de transmitere a informaiei, poate fi utilizat i pentru
ordonarea evenimentelor n cadrul unor procese distincte, deoarece emiterea unui mesaj precede ntotdeauna recepia sa.
Operaiile de schimb de mesaje pot fi definite ca nite mecanisme primitive i s le utilizm pentru sincronizarea
proceselor.
Primitivele de baz n comunicarea prin mesaje sunt:
emitere(mesaj,destinaie)
recepie(mesaj,origine)
Specificrile acestor primitive trebuie s precizeze:
natura i forma mesajelor,
modul de adresare a proceselor emitoare i destinatare,
modul de sincronizare a acestor procese,
tratarea erorilor.
1) Natura mesajelor
n conformitate cu nivelul de exprimare la care este definit mecanismul de comunicare, mesajele pot fi specificate de
un tip, analogic obiectelor unui limbaj sau prin lungimea fizic a lor. Lungimea poate fi constant sau variabil. Mai
frecvent sunt utilizate mesajele de lungime constant, care pot fi create mai simplu, mesajele de lungime variabil vor fi
transmise prin referin, pasnd adresa fizic sau identificatorul informaiei transmise.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.14
2) Modul de adresare
Procesele, care fac schimb de mesaje, se pot desemna reciproc prin numele lor (desemnare direct) sau pot utiliza
numele unui obiect intermediar ori cutie potal (desemnare indirect). Aceste nume sunt folosite ca parametri origine i
destinaie. Schema a doua faciliteaz modificarea dinamic a interconexiunilor proceselor sau chiar componentele unei
mulimi de procese, care comunic.
n cazul desemnrii directe parametrul origine a primitivei recepie poate fi interpretat n dou moduri:
fie ca dat: receptorul specific explicit c ateapt un mesaj de la un destinatar special (recepie selectiv),
fie ca rezultat: receptorul primete un mesaj care i-a fost adresat mpreun cu identitatea emitorului.
n cazul desemnrii indirecte asocierea proceselor cutiilor potale poate fi static sau dinamic. n ultimul caz, sunt
utilizate dou primitive conectare i deconectare pentru ataarea procesului la o cutie potal (n calitate de receptor) i
de abrogare a acestei atari, respectiv. n unele sisteme un receptor sau mai multe pot fi ataate unei cutii potale date;
cutiile potale supuse unor asemenea restricii sunt adesea numite pori. Este posibil i situaia invers cnd un proces
poate fi asociat la mai multe pori distincte. Dac asocierea ntre procesul receptor i poart este static, un nume de
poart specific fr ambiguitate un proces receptor ca i n metoda desemnrii directe.
3) Moduri de sincronizare
Pentru primitivele de comunicare pot fi specificate mai multe moduri de sincronizare. n caz general, operaia de
recepie blocheaz, n absena mesajului, receptorul. Unele sisteme pun la dispoziie primitive care dau posibilitatea s
se determine dac o cutie potal este vid, ceea ce permite evitarea blocrii. Pentru emitere sunt utilizate dou moduri
de sincronizare:
Schema productor-consumator, n care cutia potal este realizat printr-o zon tampon. Emiterea nu este
blocant, cu excepia cazului n care tamponul este plin. Folosirea tampoanelor de lungime variabil cu alocarea
dinamic a locaiunilor reduce probabilitatea blocrii emitorului.
Schema rendez-vous, n care emitorul este blocat pn la preluarea mesajului de ctre receptor. Aceast
schem poate fi considerat caz limit a precedentei cu lungimea nul a tamponului.
n fine, atunci cnd un proces este asociat n recepie la mai multe pori, poate fi definit un mod de sincronizare, zis
ateptare multipl, n care sosirea unui mesaj la o oarecare din aceste pori deblocheaz receptorul.
4) Tratarea erorilor
Scopul tratrii erorilor este de a evita blocrile infinite ale proceselor, care se pot produce n diverse circumstane:
Emiterea unui mesaj cu o destinaie (proces sau poart) inexistent. n acest caz primitiva nu este blocant;
eroarea este tratat prin emiterea unui cod de eroare sau printr-o deviere.
Distrugerea unui proces de la care alte procese ateapt un mesaj sau un rspuns: procesele n ateptare sunt
blocate i recepioneaz un cod de eroare.
Ultima situaie nu este totdeauna detectabil. O tehnic uzual const n stabilirea unui interval de timp maxim de
ateptare a unui mesaj i deblocarea procesului care ateapt la expirarea acestui interval (v. 3.4.5).
Vom ilustra prin cteva exemple reprezentative utilizarea acestor mecanisme de comunicare.
Exemplul 3.12. Sistemul de operare Thoth [8]. n acest sistem comunicarea folosete desemnarea direct i sincronizarea prin rendez-vous. Mesajele
sunt de lungime constant. Sunt utilizate patru primitive:
id:=send(message, id_dest)
emite procesului id_dest un mesaj; blocheaz emitorul pn la primirea unui rspuns, transmis n message. Aceast primitiv
indic identitatea procesului care a transmis rspunsul (sau nil, dac destinatarul nu exist).
id:=receive(message, id_orig)
recepioneaz un mesaj; procesul origine poate s nu fie specificat. Valoarea transmis este identitatea emitorului.
reply(message, id_orig, id_dest)
trimite un rspuns destinatarului specificat (care trebuie s-l atepte); nu este blocant; fr consecine, dac rspunsul nu era
ateptat.
forward(message, id_orig, id_dest)
aceast operaie non blocant este utilizat de un proces dup recepionarea unui mesaj trimis de ctre id_orig, pentru ca s impun
mesajul s ajung la id_dest, care are acum obligaia de a rspunde lui id_orig.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.15
Exemplul 3.13. Sistemul de operare Unix [9]. n sistemul Unix comunicarea ntre procese utilizeaz tampoane, numite pipes (tuburi), administrate
conform schemei productor-consumator. Mesajele transmise sunt caractere. Un pipe (tub) leag un emitor i un receptor,
conexiunea fiind stabilit dinamic.
Exemplul 3.14. Rendez-vous n limbajul de programare Ada [10, 11]. Limbajul Ada permite definirea proceselor. Forma sintactic a comunicrilor
ntre procese este apelarea procedurii, ns transmiterea parametrilor i a rezultatelor are loc conform principiului de transmitere a
mesajelor cu rendez-vous. Recepionarea poate fi condiionat (un apel este acceptat doar dac o condiie specificat este
satisfcut) i exist posibilitatea de ateptare multipl.
procesul client
poart_serviciu.emitere(cerere)
...
...
[poart_client.recepionarere(rezultat)]
monitor;
var ..., sfr_schimb_i,...: condiie;
<declaraiile variabilelor de stare ale perifericului>
...
procedura schimb_i(<parametri>);
begin
<mascarea ntreruperilor>;
if starea preg then
<tratare eroare(perifericul nu este gata)>
endif;
lansare_transfer_i(parametri);
sfr_schimb_i.ateptare;
if starea ok then
<tratare eroare(incident de transfer)>
endif;
<demascare ntreruperi>
end;
-- ntrerupere demascat
-- n timpul ateptrii
...
begin
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.16
<iniializare>
end
end perif
Procedura lansare_transfer_i pregtete programul pentru schimbul cerut (construirea programului canalului sau al
ADM - Acces Direct la Memorie, innd cont de parametrii schimbului) i lanseaz execuia sa (instruciunea SIO
Start Input Output). Procesele apelante ateapt sfritul transferului datorit condiiei sfr_schimb_i. Sosirea unei
ntreruperi, care marcheaz sfritul schimbului de tip i provoac n mod automat executarea urmtoarei secvene:
if sfr_schimb_i.vid then <tratarea eroare ntrerupere care nu este ateptat>
else sfr_schimb_i.semnalizare
endif
Pentru un proces care execut o intrare-ieire apelnd o procedur de schimb a acestui monitor, totul se petrece ca i
cum schimbul este sincron: la returul din procedur, informaia a fost efectiv transferat (sau o eroare a fost detectat i
semnalizat). Mecanismul de blocare evit ateptarea activ i procesorul poate fi utilizat n timpul transferului de un alt
proces.
3.4.4.2. Buferizarea imprimrii
Putem elabora acum programul administrrii tamponate a imprimrii, descrise n 3.1. Avem nevoie de trei tampoane
tm1 i tm2 de capacitate N1 i N2 n memoria central i unul pe disc, td, de lungime Ndisc. Pentru simplitate
presupunem, c transferurile se fac cu blocuri constante egale cu o linie. Fiecare tampon este comandat de un monitor
cu aceeai structur care are rolul de a asigura excluderea mutual i sincronizarea condiiilor tampon plin i tampon
vid.
Aceste tampoane, numite tampon1, tampon2 i tampon_disc au aceeai structur cu tamponul descris n 3.4.3,
nlocuind N cu N1, N2 i Ndisc, respectiv. Definind tm1, tm2 i td ca tablouri de elemente de lungimea unei linii i
pointerii top i coad locali fiecrui monitor, procedurile de depozitare i preluare pot fi:
<pentru tamponul 1>
procedura intrare(l:linie);
tm1[coad] := l;
coad := coad+1 mod N1
procedura intrare(l:linie);
tm2[coad] := l;
coad := coad+1 mod N2
proces scriere_disc
proces citire_disc
ciclu
tampon2.preluare(l);
impr.scriere(l);
endciclu
ciclu
tampon1.preluare(l);
tampon_disc.scriere(l);
endciclu
ciclu
tampon_disc.citire(l);
tampon2.depozitare(l);
endciclu
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.17
Putem constata, c programele de mai sus sunt mult mai simple dect cele care folosesc direct ntreruperile.
Structura modular, introdus de monitoare permite separarea total a gestionrii tampoanelor de cea a perifericelor.
Schimbarea capacitii unui tampon modific doar monitorul care comand acest tampon; nlocuirea unui periferic cu
un altul implic rescrierea doar a monitorului, care comand acest periferic.
valoarea absolut a timpului (ora absolut), care msoar la orice moment timpul trecut de la o instan
iniial,
un registru, adic o list a proceselor care ateapt deblocarea, ordonat conform timpului absolut de deblocare.
Toate procesele, care apeleaz primitiva suspendare(t) sunt inserate n registru n poziia, care corespunde orei sale
absolute de deblocare.
Numim ora_de_baz ora absolut a ultimei nnoiri a ceasului, adic a ultimei iniializri a contorului; fie t_at
ultima valoare ncrcat. Ora absolut exact este dat la fiecare moment de timp de relaia
ora_exact = ora_de_baz + t_at - contor
(t_at - contor este timpul care s-a scurs dup ultima nnoire a contorului). De la o ntrerupere de ceas (la trecerea
contorului prin 0), dup ultima nnoire s-a scurs un timp egal cu t_at; ora_de_baz poate, deci, fi iniializat conform
relaiei
ora_de_baz := ora_de_baz + t_at
Variabila ora_de_baz, odat iniializat, va fi corect ntreinut cu condiia ca registrul s nu fie nicicnd vid; n caz
general aceast condiie va fi asigurat introducnd un proces, numit paznic:
procesul paznic
ciclu
suspendare(tmax)
endciclu
n care tmax este un interval foarte mare de timp, paznicul rmnnd pentru toat perioada de lucru n coada registrului.
Mecanismele descrise sunt realizate ntr-un monitor numit ceas, care are dou intrri: procedura suspendare (apelat
prin ceas.suspendare(t)) i procedura tratare_ntrerupere, pentru tratarea ntreruperii de ceas (trecerea contorului prin
zero). Registrul este realizat cu ajutorul unui fir de ateptare, care conine descriptorii proceselor. Un descriptor este
format din numele procesului i timpul absolut de deblocare; firul de ateptare este ordonat n ordinea creterii timpului
deblocrii.
Presupunem, c ntreruperea de ceas activeaz un proces, unica activitate a cruia const n apelarea procedurii
ceas.tratare_ntrerupere.
ceas: monitor;
type descriptor: struct
i
: proces;
ora_debl : integer
Problema ar fi uor de rezolvat, dac fiecare proces ar dispune de un ceas propriu: ar fi suficient s se introduc n contor valoarea t i s se atepte
ntreruperea la trecerea prin zero. Realizarea primitivei suspendare cu ajutorul unui ceas unic comun presupune ataarea fiecrui proces a unui ceas
virtual.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.18
end;
var contor, t_at;
ora_de_baz, ora_exact: integer
deblocare
: array[proces] of condiie;
<declaraiile firului de ateptare i procedurilor sale de acces>
procedura suspendare(durata:integer);
var proc: descriptor;
begin
ora_exact:=ora_de_baz+t_at-contor;
proc.ora_deblocrii:=ora_exact+durata
proc.i := p;
intrare(proc,fir)
if proc=primul(fir) then
t_at:=contor:=durata;
ora_de_baz:=ora_exact;
endif;
deblocare[p].ateptare
end;
procedura tratare_ntrerupere;
-- contor n zero
var proc: descriptor;
delta_t:integer;
begin
ieire(proc,fir);
-- primul din firul de ateptare
ora_de_baz := ora_exact+t_at;
if vid(fir) then
delta_t := tmax
else
delta_t := primul(fir).ora_deblocrii ora_de_baz;
endif;
t_at := contor := delta_t;
-- deblocarea viitoare
deblocare[proc.i].semnalizare
end;
begin
ora_de_baz := <ora_iniial>;
contor := t_at := tmax;
intrare(paznic,fir)
end
end ceas
Metoda de mai sus (ntreinerea unui registru i a orei absolute) este utilizat pentru realizarea programelor de
simulare discret. Un atare program realizeaz un model n care diferite procese, executate n regim pseudo-paralel,
reprezint activiti care se deruleaz n paralelism real. Timpul utilizat este un timp simulat, adic o variabil, care
reprezint trecerea timpului fizic, respectnd proporionalitatea intervalelor timpului simulat cu intervalele
corespunztoare ale timpului fizic.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.19
executat de un proces p (printe), creaz un proces nou q (fiul), care va fi executat paralel cu p. Primitiva fork prezint
ca rezultat identificatorul lui q (sau nil, dac crearea este imposibil). Contextul iniial al lui q este o copie a lui p, mai
puin contorul ordinal, care este fixat la prima instruciune a lui p. Procesul fiu se termin cu o primitiv, numit exit sau
quit, care provoac dispariia sa. Dup ce fork creaz un proces fiu q, primitiva join q permite procesului printe s
fixeze un punct de rendez-vous cu acest fiu. Executarea lui join q blocheaz procesul printe pn cnd q nu va executa
exit. Primitivele fork i join au avantajele i dezavantajele instruciunii go to din programarea secvenial.
Exemplul 3.15. n sistemul de operare Unix crearea unui proces poate fi realizat de ctre interpretorul limbajului de comand (shell) sau cu ajutorul
instruciunii fork() de un program. Ultima situaie este prezentat schematic n fig.3.4.
procesul 2
procesul 1
copie
date
date
stiv
stiv
procesul fiu
procesul printe
procesul printe
procesul fiu
if (fork() == 0)
codul procesului fiu
else
codul procesului printe
if (fork() == 0)
codul procesului fiu
else
codul procesului printe
returnare 0
Altfel spus, n Unix primitiva fork (fr parametri) creeaz un proces al crui spaiu de lucru este o copie a spaiului de lucru a
creatorului, inclusiv i contorul ordinal. Diferena poate fi determinat consultnd valoarea returnat de primitiv (0 pentru fiu;
identificatorul fiului sau nil pentru printe). O primitiv wait permite printelui s atepte terminarea execuiei unuia dintre
programele fiu (fr a putea alege care anume, dac exist mai multe). Un proces termin execuia sa cu primitiva exit. Primitiva
exec(p) permite unui proces s schimbe contextul, apelnd o procedur specificat de p.
La lansarea Unix-ului sunt create dou procese: procesul numit Swaper, care administreaz memoria, cu pid=0 i procesul Init cu
pid=1, care creeaz toate celelalte procese.
Ilustrm folosirea primitivelor fork i exec:
...
id := fork();
if id = 0 then
exec(p)
else
if id = -1 then
<tratare eroare>
else
<programul printelui>
endif
endif
-- eu sunt fiul
-- programul fiului
-- eu sunt printele
-- nil : creare imposibil
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.20
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.21
important s se sublinieze, c atunci cnd un fir lucreaz cu mecanismele de sincronizare (le creeaz, le modific starea)
sistemul nu ntrerupe execuia firului, pn nu va fi terminat aceast aciune, adic toate operaiile finite din
mecanismele de sincronizare sunt atomare (nu pot fi ntrerupte).
Menionm de asemenea, c nu exist nici o legtur real ntre mecanismele de sincronizare i resurse.
Mecanismele de sincronizare nu pot interzice accesul nedorit la o resurs, ele doar indic firului momentul cnd acesta
poate accesa resursa, sau cnd acesta trebuie s atepte (de exemplu, un semafor la o intersecie doar indic cnd este
permis trecerea. Cineva poate trece pe rou, dar consecinele pot fi grave).
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.22
fir prsete seciunea critic (funcia LeaveCriticalSection). Diferena de mutex const n faptul c seciunea critic
este utilizat numai pentru firele unui singur proces.
Cu ajutorul funciei TryEnterCriticalSection se poate stabili, dac seciunea critic este liber. Utiliznd aceast
funcie, un proces, fiind n ateptarea resursei, poate s nu se blocheze, ndeplinind operaii utile.
3.6.4.5. Protejarea accesrii variabilelor
Exist o serie de funcii, care permit lucrul cu variabilele globale ale tuturor firelor, fr a ne preocupa de
sincronizare, deoarece aceste funcii singure rezolv problema sincronizrii. Aceste funcii sunt InterlockedIncrement,
InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd i InterlockedCompareExchange. De
exemplu, funcia InterlockedIncrement incrementeaz valoarea unei variabile pe 32 bii cu o unitate.
3.6.4.6. Sincronizarea n Microsoft Fundation Classes
Biblioteca MFC conine clase speciale pentru sincronizarea firelor (CMutex, CEvent, CCriticalSection i
CSemaphore). Aceste clase corespund obiectelor de sincronizare WinAPI i sunt derivate de la clasa CSyncObject.
Pentru utilizarea acestor clase trebuie consultai constructorii i metodele lor Lock i Unlock. n principiu, aceste
clase sunt doar un fel de ambalaj pentru obiectele de sincronizare.
O alt modalitate de utilizare a acestor clase const n crearea aa numitelor clase thread-safe. O clas thread-safe
reprezint o anumit resurs n program. Tot lucrul cu resursa este realizat numai prin intermediul acestei clase, care
conine toate metodele necesare. Clasa este proiectat n aa mod, ca metodele ei s rezolve problema sincronizrii,
adic n cadrul aplicaiei s apar ca o simpl clas. Obiectul de sincronizare MFC este inclus n aceast clas n calitate
de membru privat i toate funciile clasei, care realizeaz accesarea resursei, i coordoneaz lucrul cu acest membru.
Utiliznd funciile Lock i Unlock clasele de sincronizare MFC pot fi utilizate direct, iar n mod indirect prin
funciile CSingleLock i CmultiLock.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.23
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.24
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.25
Windows NT/2000 genereaz o excepie, dac ncercarea de a crea un eveniment a euat. Aceasta este just att
pentru funciile ::Enter/LeaveCriticalSection(), ct i pentru ::InitializeCriticalSectionAndSpinCount() cu bitul cel
mai semnificativ al parametrului SpinCount setat. n cazul sistemului de operare WindowsXP creatorii nucleului au
procedat un pic altfel. Aici, funciile ::Enter/LeaveCriticalSection() n loc s genereze o excepie, dac nu pot crea un
eveniment propriu, vor folosi un obiect global, unic pentru toi, creat n avans. n aa mod, n caz de deficien
catastrofal a resurselor de sistem, programul sub comanda lui WindowsXP chiopteaz un timp oarecare mai
departe. ntr-adevr, este foarte complicat s scrii programe, care ar fi n stare s continue lucrul dup ce
::EnterCriticalSection() a generat o excepie. De obicei, chiar dac programatorul a prezis o astfel de situaie, nu
se ajunge mai departe de generarea unui mesaj de eroare i stoparea forat a execuiei programului. Drept rezultat,
WindowsXP ignoreaz bitul cel mai semnificativ al cmpului LockCount.
n sfrit, cmpul LockCount. Acest cmp este folosit doar n sistemele multiprocesorale. n sistemele
monoprocesorale, dac seciunea critic este ocupat de un fir oarecare, putem doar comuta comanda la aceast seciune
i trece n ateptarea evenimentului nostru. n sistemele multiprocesorale exist o alternativ: s executm de cteva ori
un ciclu vid, testnd de fiecare dat dac seciunea critic nu a fost eliberat (ateptare activ). Dac dup un numr de
SpinCount ori seciunea critic nu a fost eliberat, trecem n starea blocat. Aceast modalitate este mult mai eficient
dect comutarea la planificatorul nucleului i napoi. n WindowsNT/2000 bitul cel mai semnificativ al acestui cmp
indic dac obiectul nucleului, variabila handle a cruia se afl n cmpul LockSemaphore, trebuie creat anticipat.
Dac pentru aceasta nu dispunem de resurse de sistem suficiente, sistemul genereaz o excepie i programul i poate
micora posibilitile funcionale sau chiar termina execuia.
3.6.6.2. Funcii API pentru seciunile critice
Descriem mai jos cteva funcii din API pentru lucrul cu seciunile critice.
Mai nti funciile BOOL InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) i BOOL
InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount).
Completeaz cmpurile structurii lpCriticalSection adresate. Dup apelare seciunea critic este gata de lucru. Iat
pseudocodul funciei RtlInitializeCriticalSection din ntdll.dll
VOID RtlInitializeCriticalSection(LPRTL_CRITICAL_SECTION pcs)
{
RtlInitializeCriticalSectionAndSpinCount(pcs, 0)
}
VOID RtlInitializeCriticalSectionAndSpinCount(LPRTL_CRITICAL_SECTION pcs,
DWORD dwSpinCount)
{
pcs->DebugInfo = NULL;
pcs->LockCount = -1;
pcs->RecursionCount = 0;
pcs->OwningThread = 0;
pcs->LockSemaphore = NULL;
pcs->SpinCount = dwSpinCount;
if (0x80000000 & dwSpinCount)
_CriticalSectionGetEvent(pcs);
}
Funcia DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD
dwSpinCount) seteaz valoarea cmpului SpinCount i returneaz valoarea precedent a acestuia. Amintim, c bitul
cel mai semnificativ este responsabil de legarea evenimentului, folosit pentru ateptarea accesului la seciunea critic
dat. Pseudocodul funciei RtlSetCriticalSectionSpinCount din ntdll.dll este listat mai jos.
DWORD RtlSetCriticalSectionSpinCount(LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount)
{
DWORD dwRet = pcs->SpinCount;
pcs->SpinCount = dwSpinCount;
return dwRet;
}
VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection) elibereaz resursele, ocupate de
seciunea critic. Are urmtorul pseudocod:
VOID RtlDeleteCriticalSection(LPRTL_CRITICAL_SECTION pcs)
{
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.26
pcs->DebugInfo = NULL;
pcs->LockCount = -1;
pcs->RecursionCount = 0;
pcs->OwningThread = 0;
if (pcs->LockSemaphore)
{
::CloseHandle(pcs->LockSemaphore);
pcs->LockSemaphore = NULL;
}
}
VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection), BOOL
TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) permit intrarea n seciunea critic. Dac
seciunea critic este ocupat de un alt fir, atunci ::EnterCriticalSection() va atepta pn aceasta va fi eliberat, iar
::TryEnterCriticalSection() va returna valoarea FALSE. Listingurile din ntdll.dll sunt:
VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)
{
if (::InterlockedIncrement(&pcs->LockCount))
{
if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())
{
pcs->RecursionCount++;
return;
}
RtlpWaitForCriticalSection(pcs);
}
pcs->OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->RecursionCount = 1;
}
BOOL RtlTryEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)
{
if (-1L == ::InterlockedCompareExchange(&pcs->LockCount, 0, -1))
{
pcs->OwningThread = (HANDLE)::GetCurrentThreadId();
pcs->RecursionCount = 1;
}
else if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())
{
::InterlockedIncrement(&pcs->LockCount);
pcs->RecursionCount++;
}
else
return FALSE;
return TRUE;
}
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) elibereaz seciunea critic.
Pseudocodul este urmtorul:
VOID RtlLeaveCriticalSectionDbg(LPRTL_CRITICAL_SECTION pcs)
{
if (--pcs->RecursionCount)
::InterlockedDecrement(&pcs->LockCount);
else if (::InterlockedDecrement(&pcs->LockCount) >= 0)
RtlpUnWaitCriticalSection(pcs);
}
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.27
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.28
m_vectSinks[i]->DoSomething();
m_lockObject.Lock();
}
}
Apelul ::LeaveCriticalSection() fr ::EnterCriticalSection() va conduce la faptul c chiar primul apel
::EnterCriticalSection() va stopa execuia firului pentru totdeauna.
n fragmentul de cod de mai jos lipsete apelul ::LeaveCriticalSection():
void Proc()
{
m_lockObject.Lock();
if (!m_pObject)
return;
// ...
m_lockObject.Unlock();
}
n acest exemplu are sens s fie utilizat o clas de tipul CSopeLock. Se mai poate ntmpla ca
::EnterCriticalSection() s fie apelat fr iniializarea seciunii critice cu ajutorul ::InitializeCriticalSection() (de
exemplu, n proiectele scrise cu ajutorul lui ATL). n versiunea debug totul poate lucra foarte bine, iar n versiunea
release moare. Aceasta are loc din cauza aa-zisului CRT (_ATL_MIN_CRT) minimal, care nu apeleaz
constructorii obiectelor statice (Q166480, Q165076). n versiunea ATL 7.0 aceast problem a fost rezolvat. Pot
s apar probleme, dac atunci cnd este folosit o clas de tipul CScopeLock a fost omis identificatorul variabilei, de
exemplu, CScopeLock (m_lock). Compilatorul apeleaz constructorul CScopeLock i imediat distruge acest obiect
fr nume (n conformitate cu standardul!). Adic, imediat dup apelarea metodei Lock() are loc apelarea metodei
Unlock() i sincronizarea nu se produce.
Dintre erorile de arhitectur cea mai frecvent este mbriarea fatal (deadlock, v.5.1.3.3), cnd dou fire
ncearc s acceseze dou i mai multe seciuni critice. Prezentm un exemplu pentru dou fire.
void Proc1()
// Firul #1
{
::EnterCriticalSection(&m_lock1);
// ...
::EnterCriticalSection(&m_lock2);
// ...
::LeaveCriticalSection(&m_lock2);
// ...
::LeaveCriticalSection(&m_lock1);
}
// Firul #2
void Proc2()
{
::EnterCriticalSection(&m_lock2);
// ...
::EnterCriticalSection(&m_lock1);
// ...
::LeaveCriticalSection(&m_lock1);
// ...
::LeaveCriticalSection(&m_lock2);
}
Pot s apar probleme i n cazul copierii unor seciuni critice. Este greu de presupus c codul de mai jos a fost scris
de un programator sntos:
CRITICAL_SECTION sec1;
CRITICAL_SECTION sec2;
// ...
sec1 = sec2;
Din atribuirea de mai sus este dificil s obii foloase. Dar fragmentul urmtor poate fi adesea ntlnit:
struct SData
{
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.29
CLock m_lock;
DWORD m_dwSmth;
} m_data;
void Proc1(SData& data)
{
m_data = data;
}
i totul ar fi OK, dac structura SData ar avea on constructor de copiere, de exemplu:
SData(const SData data)
{
CScopeLock lock(data.m_lock);
m_dwSmth = data.m_dwSmth;
}
Presupunem c programatorul a considerat, c este suficient s se ndeplineasc o simpl copiere a cmpurilor i, n
rezultat, variabila m_lock a fost copiat, iar anume n acest moment ea fusese accesat dintr-un alt fir i valoarea
cmpului LockCount este 0. Dup apelul ::LeaveCriticalSection() din acel fir valoarea cmpului LockCount pentru
variabila iniial m_lock a fost decrementat cu o unitate, pentru variabila copiat rmnnd fr schimbare. Ca
rezultat, orice apel ::EnterCriticalSection() nu se va ntoarce niciodat n acest fir. Va rmne pentru totdeauna n
ateptare. Pot exista situaii mult mai complicate.
Fie un obiect care apeleaz metodele unui alt obiect, obiectele aflndu-se n fire diferite. Apelurile se fac n mod
sincron, adic obiectul #1 transmite execuia firului obiectului #2, apeleaz metoda i se va comuta napoi la firul su.
Execuia firului #1 va fi suspendat pentru toat perioada de execuie a firului obiectului #2. Presupunem acum, c
obiectul #2 apeleaz o metod a obiectului #1 din firul su. Controlul va fi ntors obiectului #1, dar din firul obiectului
#2. Dac obiectul #1 apelase metoda obiectului #2, intrnd ntr-o seciune critic oarecare, atunci la apelarea metodei
obiectului #1 acesta se va bloca pe sine nsui la intrarea repetat n aceeai seciune critic. Fragmentul de cod care
urmeaz vine s exemplifice aceast situaie.
// Firul #1
void IObject1::Proc1()
{
// Intrm n seciunea critic a obiectului #1
m_lockObject.Lock();
// Apelm metoda obiectului #2, are loc comutarea la firul obiectului #2
m_pObject2->SomeMethod();
// Aici nimerim numai dup ntoarcerea din m_pObject2->SomeMethod()
m_lockObject.Unlock();
}
// Firul #2
void IObject2::SomeMethod()
{
// Apelm metoda obiectului #1 din firul obiectului #2
m_pObject1->Proc2();
}
// Firul #2
void IObject1::Proc2()
{
// ncercm s intrm n seciunea critic a obiectului #1
m_lockObject.Lock();
// Aici nu vom ajunge niciodat
m_lockObject.Unlock();
}
Dac n acest exemplu nu ar fi avut loc comutarea firelor, toate apelurile ar fi avut loc n firul obiectului #1 i nu am
fi avut probleme. Exemple de acest gen stau la baza tehnologiei compartimentului COM (apartments). Nu sunt
recomandate apelurile obiectelor, dac au avut loc intrri n seciunile critice. Primul exemplu din acest subparagraf va
fi rescris astfel:
// Firul #1
void Proc1()
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.30
{
m_lockObject.Lock();
CComPtr<IObject> pObject(m_pObject); // apelarea pObject->AddRef();
m_lockObject.Unlock();
if (pObject)
pObject->SomeMethod();
}
// Firul #2
void Proc2(IObject *pNewObject)
{
m_lockObject.Lock();
m_pObject = pNewobject;
m_lockObject.Unlock();
}
Accesul la obiect a rmas ca i mai nainte sincronizat, dar apelul SomeMethod() are loc n afara seciunii critice.
Situaia a fost aproape rezolvat. Mai exist o problem mic. S cercetm mai atent Proc2():
void Proc2(IObject *pNewObject)
{
m_lockObject.Lock();
if (m_pObject.p)
m_pObject.p->Release();
m_pObject.p = pNewobject;
if (m_pObject.p)
m_pObject.p->AddRef();
m_lockObject.Unlock();
}
Este evident, c apelurile m_pObject.p->AddRef() i m_pObject.p->Release() au loc n interiorul seciunii
critice. i dac apelarea metodei AddRef() nu genereaz, de obicei probleme, apelarea metodei Release() poate fi
ultimul apel al Release() i obiectul se va autodistruge. n metoda FinalRelease() a obiectului #2 poate fi orice, de
exemplu, eliberarea unor obiecte, care se afl n alte compartimente. Dar aceasta din nou va conduce la comutarea
firelor i poate genera autoblocarea obiectului #1 ca i n exemplul de mai sus. Pentru a prentmpina aceasta vom folosi
aceeai tehnic ca i n Proc1().
// Firul #2
void Proc2(IObject *pNewObject)
{
CComPtr<IObject> pPrevObject;
m_lockObject.Lock();
pPrevObject.Attach(m_pObject.Detach());
m_pObject = pNewobject;
m_lockObject.Unlock();
}
Potenial, acum ultimul apel IObject2::Release() va fi executat dup prsirea seciunii critice. Iar atribuirea unei
valori noi este sincronizat ca i mai nainte cu apelul IObject2::SomeMethod() din firul #1.
Concluzii:
seciunile critice sunt executate relativ repede i nu cer multe resurse de sistem;
pentru sincronizarea accesrii a mai multor variabile independente este mai bine s fie utilizate cteva
seciuni critice (nu una pentru toate variabilele);
codul unei seciuni critice va fi redus la minimum;
nu este recomandat s fie apelate metode ale unor obiecte strine dintr-o seciune critic.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.31
1) Propunei o soluie cu prioritate pentru redactori n care accesarea resursei ar fi interzis unor noi cititori, dac
exist un redactor n ateptare.
2) Soluia de mai sus prezint un risc de ateptare pentru un timp nedefinit a cititorilor. Specificai un mod de
funcionare care ar trata ntr-o manier echitabil cititorii i redactorii i elaborai programul monitorului
respectiv.
Exerciiul 3.3. Buticul unui brbier este organizat conform schemei de mai jos:
Ua de intrare i cea de comunicare dintre sala de ateptare i salonul brbierului nu permit intrarea simultan a mai
multor clieni (doar unul singur poate trece prin fiecare ua la un moment de timp dat). Aceste ui au un mecanism
culisant, care face ca una din ele s fie nchis, atunci cnd cealalt este deschis. Brbierul invit urmtorul client,
atunci cnd termin deservirea clientului curent. Dac sala de ateptare este goal el doarme. Dac un client gsete
brbierul dormind, l trezete, altfel i ateapt rndul n sala de ateptare.
Reprezentnd brbierul i clienii prin procese, programai funcionarea acestui sistem cu ajutorul unui monitor.
Exerciiul 3.4. O linie de cale ferat, care leag dou orae A i B, conine o poriune cu un singur drum. Vom
reprezenta trenurile prin procese conform schemei de mai jos:
trenurile A B
...
sens_unic.intrare_vest
<traversare sens unic>
sens_unic.ieire_est
...
trenurile B A
...
sens_unic.intrare_est
<traversare sens unic>
sens_unic.ieire_vest
...
B
Rolul monitorului sens_unic este s garanteze c toate trenurile, angajate la un moment de timp dat pe poriunea cu
un singur drum, circul n aceeai direcie.
1) S se elaboreze programul monitorului sens_unic, presupunnd c numrul trenurilor prezente pe poriunea cu
un singur drum, nu este limitat.
2) Aceeai problema, dar pentru un numr limitat N de trenuri.
3) Examinai n ambele cazuri riscul de blocare i mijloacele de evitare ale acestora.
Exerciiul 3.5. Cinci filozofi stau la o mas conform schemei de mai jos.
Filosoful 0
Filosoful 4
Filosoful 3
Filosoful 1
Filosoful 2
Fiecare, schematic prezentat printr-un proces pi, (i=0..4), se comport conform programului:
ciclu
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.32
gndire
mncare
endciclu
Fiecare filozof poate utiliza doar cele dou furculie, plasate de o parte i alta a farfuriei sale, la terminare
ntorcndu-le n poziia iniial. Durata celor dou faze este arbitrar, dar finit.
1) S se arate, c alocarea furculielor poate conduce la blocarea sistemului (blocarea a dou sau mai multe
procese).
2) S se elaboreze un monitor furculie, care ar realiza alocarea furculielor fr blocare. Apelarea procedurii
mncare va fi nlocuit n programul procesului pi prin secvena
furculie.luare(i)
mncare
furculie.eliberare(i)
Apelarea procedurii luare l blocheaz pe apelant pentru perioada de timp n care cele dou furculie adiacente
farfuriei sale sunt utilizate de ctre vecinii si. Apelarea procedurii eliberare elibereaz dou furculie.
3)
Examinai posibilitatea de blocare indefinit a unui proces i propunei soluii pentru a o evita.
luare_furculi(i)
P(mutex);
stare[i] = FOAME;
test(i);
V(mutex);
P(s[i]);
ntoarcer_furculi(i)
P(mutex);
stare[i] = GNDIRE;
test(STNGA);
test(DREAPTA);
V(mutex);
test(i)
if(stare[i] = FOAME && stare[DREAPTA] != MNCARE && stare[STNGA] != MNCARE) then
stare[i] = MNCARE;
V(s[i]);
Exerciiul 3.6. Specificai i elaborai cu ajutorul unui monitor dou primitive emitere i recepie, care realizeaz
comunicarea ntre dou procese via un tampon de capacitatea unui singur mesaj cu sincronizare prin metoda rendezvous.
Exerciiul 3.7. Programul de mai jos reprezint dou procese, care funcioneaz conform schemei productorconsumator i sincronizate prin ateptare activ.
productor
ciclu
consumator
ciclu
test_c: if n=0 then
generare(mesaj);
test_p: if n=N then
go to test_p
endif;
n:=n+1;
intrare(mesaj);
endciclu
go to test_c
endif;
n:=n-1;
ieire(mesaj);
consumare(mesaj)
endciclu
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.33
consumator
ciclu
test_c: if NC-NP0 then
go to test_c
endif;
ieire(mesaj);
consumare(mesaj)
NC:=NC+1
endciclu
S se discute validitatea acestor programe. Ce reprezint variabilele ntregi NP i NC i care sunt valorile lor
iniiale?
Exerciiul 3.9. Un sistem const din n cupluri de procese comunicante dou cte dou conform schemei productorconsumator. Tampoanele sunt de lungime variabil i administrate dinamic. Atunci cnd un productor are nevoie de o
caset pentru a depozita un mesaj, el cere aceasta unei rezerve de casete, o umple i o pune n firul de ateptare al
tamponului su. Cnd un consunator a preluat un mesaj dintr-o caset, el restituie caseta rezervei de casete. Iniial
rezerva conine N casete, fiecare fiind identificat printr-o adres adr[0..N-1]. Elaborai monitorul gestionrii rezervei
de casete n urmtoarele dou ipoteze:
1) Toate cuplurile de procese sunt tratate identic.
2) Prioritatea cuplului este cu att mai mare cu ct mai puine tampoane cuplul ocup (aceast politic vizeaz
restabilirea echitii pentru cazul unor procese care deruleaz cu viteze diferite).
Exerciiul 3.10. S se programeze cu ajutorul monitoarelor problema de culegere a rezultatelor de msurare propus
n exerciiul 2.2.
Exerciiul 3.11. Modificai monitorul gestionrii unui periferic asociindu-i un monitor de gard, astfel ca tratarea
unei erori s fie declanat, dac transferul nu a fost terminat dup un interval dat de timp.
Exerciiul 3.12. Pentru realizarea unui server de imprimare se cere s se utilizeze metoda de buferizare, descris n
3.4.4. Un client se poate adresa acestui server printr-o comand imprimare(f), n care f este un nume de fiier.
Presupunem c serverul dispune de informaiile necesare citirii unei serii de linii, care formeaz fiierul f.
1) Elaborai programul serverului imprimantei.
2) Modificai acest program astfel nct clientului s i se permit:
3) Modificai programul serverului pentru a permite mai multor imprimante s funcioneze n paralel.
Exerciiul 3.13. Vrem s construim un sistem de tratare a lucrrilor organizate pe loturi, analogic exerciiului 9.15.
Lucrrile sunt citite secvenial de un cititor de cartele, rezultatele sunt listate la o imprimant.
Sistemul este construit din trei procese: citire, care comand cititorul, execuie, care realizeaz execuia unei lucrri
i imprimare, care comand imprimarea. Procesul imprimare este de forma:
ciclu
<citire cartel>
<tratare cartel>
endciclu
programul tratare cartel poate conine apelri <imprimare linie>.
1) Presupunem mai nti c toate intrrile-ieirile sunt tamponate n memoria central. Realizai cu ajutorul
monitoarelor cooperarea celor trei procese.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.34
2) Modificai programul precedent pentru a putea trata cazul erorilor n cursul execuiei (presupunem, c procesul
citire poate detecta prima cartel a unei lucrri).
3) Modificai programul precedent pentru a integra aici administrarea consolei operatorului (cu specificaiile
exerciiului 9.15).
4) Modificai programele precedente pentru a introduce administrarea tamponat a intrrilor i ieirilor (exerciiul
precedent).
Exerciiul 3.14. Clienii unui server sunt mprii n dou clase, numite 1 i 2. Cererile clienilor din clasa 1 sunt
prioritare: serverul va trate o cerere din clasa 2 doar n lipsa de cereri de clasa 1.Cererile din aceeai clasa vor fi tratate
n ordinea sosirii. Un cmp al cererii identific prioritatea.
1) Programai sincronizarea clienilor i a serverului utiliznd monitoare. S se foloseasc un fir de ateptare
pentru fiecare clas de cereri i un proces dispecer pentru alimentarea acestor fire.
2) Schema de mai sus conine un risc de blocare. Modificai specificaiile i realizarea pentru a elimina acest risc,
pstrnd tratarea privilegiat a cererilor din clasa 1.
27.11.16
10:03
/var/www/apps/conversion/tmp/scratch_7/338332949.doc
p.35