Sunteți pe pagina 1din 93

Sisteme de operare.

Mecanisme interne și principii de proiectare


1 . Introducere
Cursul tratează conceptele și principiile fundamentale ale teoriei și practicii sistemelor de operare (SO). Sunt
prezentate definițiile principale și unele clasificări, interfețele sistemelor de operare, modul de organizare a procesului
de calcul, gestionarea memoriei și a dispozitivelor periferice, administrarea informației și gestiunea fișierelor.
Lucrarea acoperă aspectele asociate bazelor contemporane ale metodelor şi mijloacelor de elaborare a resurselor
program de sistem (inclusiv, operaţii asincrone, tratarea întreruperilor, compromisele dintre dispozitivele tehnice şi
resursele program, interfeţele sistemelor de operare), având drept obiectiv final pregătirea cititorului pentru analiza şi
proiectarea sistemelor de operare. Cursul include capitole teoretice și materiale ilustrative practice, chemate să
familiarizeze cititorul nu atât cu modalitatea utilizării unor sisteme de operare concrete, ci în special cu metodele și
algoritmii, care stau la baza mecanismelor interne ale sistemelor de operare și modul în care acestea sunt concepute,
proiectate și implementate.
Un sistem de calcul constă din două tipuri de resurse: resursele fizice și resursele logice. Resursele fizice posedă
caracteristici tehnice avansate şi pot fi utilizate în cele mai diverse scopuri. Însă aceste resurse fără componentele
logice de sistem (software de sistem) întâmpină dificultăţi mari în relaţia cu mediul în care trebuie să funcţioneze.
Acesta este unul dintre motivele principale ale creării sistemelor de operare, destinaţia cărora este administrarea
(gestiunea, controlul) resurselor tehnice principale şi asigurarea unei interfeţe comode (plăcute, prieteneşti) între
utilizator şi calculator (fig.1.1, [1]).

Utilizatorii finali Programatorii Dezvoltatorii


de SO

Aplicații

Utilitare, compilatoare, editoare, interpretoare

Sistemul de operare
Limbajul cod-mașină

Microarhitectura (regiștrii UC, UAL)

Dispozitivele fizice (controlere, magistrale, monitoare etc.)

Fig.1.1. Locul sistemului de operare în cadrul unui sistem de calcul


Există mai multe motivaţii ale necesităţii studierii de către viitorii specialişti IT a sistemelor de operare, cele mai
importante fiind următoarele:
 pentru utilizarea resurselor hardware în scopuri speciale poate fi necesar să fie creeat un sistem de operare
propriu sau să se introducă modificări într-unul existent;
 de alegerea corectă a sistemului de operare şi a versiunii concrete poate depinde viabilitatea şi eficacitatea
sistemului de calcul;
 este ideal ca utilizatorul să interacţioneze cu sistemul de operare cunoscând toate subtilităţile ultimului,
deoarece sistemul de operare este un intermediar între calculator şi utilizator;
 multe metode şi concepte, utilizate în domeniul SO, pot fi implementate cu succes şi în alte domenii.
Prin noţiunea sistem de operare înţelegem în primul rând modulele program ale unui sistem de calcul, care
administrează resursele tehnice (procesoare, memoria operativă şi secundară, dispozitive de intrare/ieşire, fişiere).
Modulele în cauză soluţionează situaţiile de conflict, optimizează productivitatea sistemului, sporesc eficienţa
utilizării lui. Ele sunt un fel de intermediar (interfaţă) între programele utilizatorului şi componentele tehnice ale
calculatorului. Alte denumiri istorice: program de administrare, monitor, supervizor.
Modulele destinate unor domenii anume, cum ar fi translatoarele, depanoarele, bibliotecile, mediile integrate de
dezvoltare etc., nu sunt incluse în definiţia unui SO, fiind considerate şi ele „utilizatori” ai sistemului de operare.
1.1. Noţiuni de bază şi clasificări
Un calculator constă dintr-un ansamblu de componente funcţionale fizice şi logice, care cooperează pentru a
satisface cerinţele utilizatorilor privind introducerea, stocarea, prelucrarea, transmisia şi căutarea informaţiilor.
Aceste componente funcţionale sunt structurate pe niveluri, care interacţionează prin interfeţe bine definite.
Suportul fizic (resurse tehnice, hardware) constituie nivelul inferior al sistemului de calcul construit pe baza unor
componente electronice, magnetice, optice, mecanice etc., mai mult sau mai puţin sofisticate în funcţie de stadiul de
dezvoltare a tehnologiilor respective.
1.1.1. Noţiuni şi termeni din domeniul resurselor tehnice
Pentru a trece la noţiunile principale, legate de hardware, vom face cunoştinţă mai întâi cu funcţiile de bază ale
unui calculator. Pot fi evidenţiate cinci funcţii esenţiale [2]: iniţializarea (bootup), introducerea datelor, procesarea
datelor, stocarea datelor şi extragerea rezultatelor:
 Iniţializarea implică testarea părţilor importante ale calculatorului, rularea fişierelor de pornire şi încărcarea
altor fişiere necesare, cum ar fi driverele de dispozitive;
 Introducerea reprezintă transferul datelor dintr-o sursă externă în calculator. Surse externe pot fi dischetele,
tastatura, mouse-ul etc.;
 Procesarea se referă la manipularea datelor introduse în scopul producerii unui rezultat (ieşirea);
 Stocarea constituie procesul salvării informaţiilor (date sau programe) într-un dispozitiv de păstrare, de
exemplu discul fix, pentru recuperarea ulterioară.
Prin structura unui calculator vom înţelege componentele (dispozitivele) care formează calculatorul şi legăturile
dintre ele. Componentele principale sunt: procesorul, memoria, inclusiv unităţile de stocare pe termen lung,
dispozitivele de intrare-ieşire (tastatura, display-ul, mouse-ul etc.). Relaţiile (legăturile) dintre aceste componente
pot fi foarte variate, o structură devenită apoi clasică, este structura John von Neumann (fig.1.2).

Hard disc Placă PC Unitatea de control


(UC)
Controler HDD
Port paralel Unitatea logico-
aritmetică (ULA)
Imprimantă
Bus de extensie

Placă PC Procesorul central


(UCP)
Controler I/O
Modem
Port serial

Monitor
Controler de Tastatură
(ieşire) Placă PC (Intrare)
tastatură
Controler afişare

Memoria centrală

Fig. 1.2. Structura unui calculator


Componentele principale se conectează la placa de bază (motherboard) direct sau prin conectoare speciale,
numite plăci de extensie (daughterboards). Unitatea centrală de procesare – procesorul (CPU) se găseşte într-un
singur circuit integrat (cip) incorporând unitatea de comandă (Control Unit, CU) şi unitatea logico-aritmetică
(Arithmetic Logical Unit, ALU).
Unitatea de comandă controlează funcţionarea unităţii logico-aritmetice. Memoria este o zonă de lucru de mare
viteză, unde sunt stocate datele şi programele pentru a fi accesate de CPU în mod rapid. Memoria poate fi organizată
în mod ierarhic, caz în care există cel puţin două nivele de ierarhie – memoria centrală (operativă, internă) şi
memoria secundară (externă, de lungă durată). Memoria operativă este electronică sub formă de cipuri, de obicei
cu acces aleator (RAM – Random Access Memory) şi trebuie să fie alimentată cu tensiune pentru a păstra datele
(memorie vie). Pentru salvarea datelor atunci când se întrerupe alimentarea sau pentru păstrare de lungă durată,
datele sunt stocate în memoria secundară, care le reţine oricât de mult timp. Dispozitivele cele mai obişnuite de
introducere a datelor sunt tastatura şi mouse-ul, iar dispozitivul de ieşire cel mai utilizat este monitorul. Procesorul şi
memoria operativă formează nucleul calculatorului, toate celelalte dispozitive fiind cunoscute sub denumirea de
periferie (dispozitive periferice).
Instrucţiunile care vor fi îndeplinite de calculator sunt stocate în memorie sub formă de programe. Unitatea de
comandă ţine evidenţa şi interpretează instrucţiunile dintr-un program, fiind responsabilă cu transmiterea de sarcini
specifice diferitelor elemente ale calculatorului. Unitatea de control controlează în principal funcţiile de intrare-
ieșire (I/O), de memorizare şi stocare, colaborează cu ALU, care răspunde de efectuarea operaţiilor de calcul.
Mai menţionăm noţiunile: mărimea magistralelor interne şi externe de date, mărimea adresei de memorie,
frecvenţa ceasului.
Mărimea magistralei interne de date: procesoarele păstrează datele în locaţii speciale, numite registre. Datele
sunt transferate între registre, CU, ALU şi alte componente ale procesorului prin intermediul unei magistrale
realizate în circuitele procesorului. Numărul de linii în această magistrală oferă o măsură a cantităţii de date pe care
procesorul o poate transfera într-o singură operaţie. Valoarea ei poate varia între 8 şi 32 de biţi (în scopuri speciale
64, 128 sau chiar mai mult).
Mărimea magistralei externe de date măsoară câte date pot fi transferate între procesor şi dispozitivele
periferice într-un tact de ceas. Magistrala este un sistem de conectări şi cablări ce distribuie datele prin calculator.
Cu cât magistrala de date e mai mare, cu atât performanţele calculatorului se îmbunătăţesc. Numărul de linii şi aici
variază între 8 şi 32 de biţi.
Mărimea adresei de memorie determină volumul de memorie care poate fi gestionat de calculator fără eforturi
speciale. Prin eforturi speciale înţelegem resurse fizice sau logice, care permit gestionarea unei memorii cu
capacitate mai mare decât volumul obţinut prin calcularea adresei reieşind din numărul de linii fizice ale memoriei.
Un ceas electronic (generator de sincronizare) asigură coordonarea corespunzătoare a componentelor
calculatorului. Componentele calculatoarelor cu performanţe superioare pot opera la frecvenţe de ceas mai mari.
Frecvenţa ceasului indică viteza de operare a CPU şi se măsoară în miliarde de impulsuri pe secundă, adică în
GigaHertzi (GHz).
1.1.2. Noţiuni şi termeni din domeniul sistemelor de operare
Un sistem de operare este un ansamblu de programe de control şi de serviciu care ghidează un calculator în
executarea sarcinilor sale şi asistă programele de aplicaţie şi utilizatorul prin intermediul anumitor funcţii. Natura
funcţiilor şi modul în care acestea sunt realizate determină atributele care caracterizează un sistem de operare:
timpul de răspuns, simultaneitatea utilizării, eficienţa, partajarea şi protecţia, universabilitatea, flexibilitatea,
extensibilitatea, fiabilitatea şi disponibilitatea, transparenţa şi vizibilitatea [3].
Timpul de răspuns exprimă durata intervalului delimitat de lansarea unei cereri de serviciu şi achitarea acesteia
de către sistem. Are două componente: timpul de aşteptare pentru ca cererea să fie luată în consideraţie şi timpul de
execuţie a acestei cereri.
Simultaneitatea utilizării măsoară gradul în care un sistem poate să execute în acelaşi timp mai multe lucrări.
Eficienţa măsoară proprietatea unui sistem de a folosi în mod optim resursele de care dispune.
Partajarea şi protecţia caracterizează nivelul la care utilizatorii au posibilitatea să utilizeze în comun informaţia
prezentă în sistem şi nivelul la care pot să comunice între ei, în deplină siguranţă (în sensul evitării accesului
neautorizat şi/sau alterării intenţionate sau accidentale a informaţiei).
Unversalitatea, flexibilitatea, extensibilitatea măsoară gradul în care un sistem poate fi folositor
(universalitate) şi adaptabil (flexibilitate) unui context specific, exprimat prin nivelul de limitare impus programelor
utilizatorului, precum şi gradul în care se pot include în sistem noi componente hardware şi software fără eforturi de
proiectare şi programare suplimentare (extensibilitate).
Fiabilitatea şi disponibilitatea exprimă proprietatea unui sistem de a cădea foarte rar în pană şi de a evita goluri
în funcţionare din cauza defectării uneia sau mai multor componente ale sale.
Transparenţa şi vizibilitatea exprimă pe de o parte proprietatea unui sistem de a face invizibil utilizatorului
ceea ce se află sub interfaţa de utilizare care i se oferă şi, pe de altă parte, capacitatea de a permite utilizatorilor săi
să obţină anumite informaţii despre modul cum el lucrează, informaţii de care în mod teoretic ei nu au nevoie pentru
a beneficia de o utilizare completă, însă care ar putea să-i ajute la obţinerea unei utilizări mai eficiente [3].
Resursele program reprezintă seturi de programe şi date utilizate pentru soluţionarea anumitor probleme.
Programul este transcrierea într-un limbaj de programare a unui algoritm, altfel – programul este o secvenţă de
instrucţiuni sau simplu cod. Utilizatorul este oricare doritor să îndeplinească anumite lucrări la calculator. Prin
lucrare (sarcină, task) vom înţelege un set de acţiuni, necesare pentru îndeplinirea unui lucru anume. Sarcina poate
conţine mai mulţi paşi. Paşii de sarcină sunt unităţi de lucru, care vor fi îndeplinite consecutiv, de exemplu trei paşi
– compilare, încărcare şi executare. Primind o lucrare de la utilizator, sistemul de operare poate creea câteva
procese, prin proces înțelegând la moment, calcule care pot fi efectuate paralel cu alte calcule.
Procesul mai poate fi definit drept traiectoria procesorului, atunci când ultimul îndeplineşte un set oarecare de
programe. Ansamblul programelor şi datelor accesate în timpul procesului, formează spaţiul de adrese. Una din
destinaţiile sistemului de operare este de a asigura proiectarea spaţiului de adrese a unui proces în memoria fizică.
Pentru rezolvarea acestei probleme sunt utilizate atât resurse tehnice (sisteme cu organizarea memoriei pe segmente
sau pe pagini), cât şi resurse program speciale.
Multiprogramarea este un termen utilizat în cazul unui sistem în care pot exista simultan câteva procese în
etapa de execuţie. Un proces se consideră în execuţie, dacă calculele au început, dar la momentul considerat nu au
fost terminate sau întrerupte (terminare din cauza unei erori sau din alte motive). Nu este obligatoriu ca un proces
care se află în execuţie să fie şi executat de procesor la un moment dat.
Resursele hardware de protecţie sunt utilizate cel mai des pentru controlul accesării memoriei. Întreruperea
este un mecanism care impune procesorul să observe anumite evenimente. Pot exista mecanisme care permit să nu
se acorde atenţie unei anume întreruperi – întrerupere mascată. Resursele hardware de întrerupere permit
sistemului de operare să coordoneze operaţiile simultane; pot fi utilizate şi pentru a schimba ordinea de execuţie a
programelor.
1.1.3. Tipuri și exemple de sisteme de operare
Valorile concrete ale atributelor sistemelor de operare şi combinaţii ale acestora determină diverse tipuri de
sisteme şi restricţii de implementare. Conform acestor atribute pot fi evidenţiate sisteme de operare [3]:
 secvenţiale
 cu multiprogramare
 cu prelucrare multiplă
 în timp real, etc.
Majoritatea sistemelor de operare recunosc programul ca cea mai mică unitate de prelucrare, căreia i se
atribuie o identitate şi pe care un utilizator o poate prezenta spre execuţie. Unele sisteme permit ca un program să fie
considerat ansamblu de sarcini ale căror execuţii (inclusiv în paralel) contribuie la atingerea obiectivului urmărit de
acest program.
Un sistem secvenţial (tratare pe loturi, batch processing en., traitement par lots fr.) execută la un moment dat un
singur program, care trebuie terminat înainte de a lansa un alt program în execuție.
Sistemele cu multiprogramare acceptă la un moment de timp dat mai multe programe în memoria centrală,
acestea aflându-se în diverse stadii de execuţie.
Un sistem de calcul cu prelucrare multiplă dispune de mai multe procesoare, care pot să execute simultan unul
sau mai multe programe. Utilizarea efectivă a prelucrării multiple necesită atributul de multiprogramare. Execuţia
simultană a unui singur program de către mai multe unităţi presupune existenţa posibilităţii de a descompune acest
program în mai multe procese sau fire de execuție.
Sistemele în timp real sunt dedicate, de obicei, funcţionării în cadrul unor sisteme de comandă şi este necesar ca
valorile anumitor atribute să se încadreze în anumite limite, dictate de dinamica proceselor comandate.
Clasificarea pe tipuri de sisteme de operare enumerate mai sus nu este nici disjunctă şi nici exhaustivă.
Majoritatea sistemelor existente pot fi încadrate în mai multe clase, atunci când se face o analiză prin prisma
obiectivelor pe care le urmăresc. La capitolul obiective vom aminti în primul rând maximizarea eficienţei
sistemului de calcul şi a satisfacţiei utilizatorilor. Tot la obiective poate fi trecută şi cererea de minimizare a
posibilităţii de apariţie a erorilor şi de maximizare a transparenţei sistemului de operare, garantarea securităţii
datelor, optimizarea controlului comunicaţiilor în cazul SO de reţea.
Un obiectiv foarte important este minimizarea efortului concepţie-realizare a sistemului, ultim în enumerare,
dar foarte important pentru specialişti.
Toate aceste obiective sunt consecinţe ale dezideratului principal: un sistem de operare este destinat să
administreze resursele sistemului de calcul şi anume memoria, procesorul (procesoarele), dispozitivele şi informaţia.
Un sistem de operare este obligat:
 să păstreze informaţia despre starea fiecărei resurse
 să ia decizia cărui proces să i se aloce resursa, în ce cantitate şi când
 să aloce resursa şi
 la momentul oportun să o retragă.
Exemplele care urmează [4] vor ilustra diversitatea funcţiilor îndeplinite de către un sistem de operare, fără
pretenţii de exhaustivitate. Pentru fiecare exemplu vom indica funcţiile puse în şarja sistemului de operare şi
caracteristicile principale ale acestuia.
1.1.3.1. Cazul calculatoarelor personale
Configuraţia cea mai simplă a unui calculator personal (PC) include o unitate centrală, o memorie principală, un
display, o tastatură şi un mouse. Această configuraţie, de obicei, este completată de o memorie secundară şi o
imprimantă (fig.1.3).
Memoria
secundară Imprimanta

Procesorul

Memoria
Display Tastatura Mouse
principală

Fig. 1.3. Structura unui calculator personal


Utilizatorul unui atare sistem va cere minimum următoarele două tipuri de servicii:
 executarea unor programe existente în PC sau introduse sub formă de fişiere; introducerea datelor necesare
pentru executarea programului (de la tastatură, din fişier sau din alte surse); listarea rezultatelor la display,
imprimantă sau copierea lor într-un fişier;
 identificarea şi crearea unor fişiere sau mulţimi structurate de informaţii; stocarea acestor fişiere în memoria
secundară; transferarea informaţiilor între fişiere şi dispozitivele de intrare/ieşire.
Sistemul de operare poate acorda aceste servicii prin intermediul unui limbaj special, numit limbaj de comandă,
introducându-se de la tastatură instrucţiuni de forma <acţiune> <parametri>, sau utilizând mouse-ul şi o interfaţă
grafică a utilizatorului (GUI - graphical user interface), acţionările mouse-lui fiind imediat interpretate de sistem.
Iată două exemple de secvenţe tipice de activităţi în cazul unui PC:
 elaborarea unui program;
 introducerea programului cu ajutorul tastaturii şi a unui editor de texte;
 executarea programului introducându-se datele necesare de la tastatură şi extrăgând rezultatele la display sau
imprimantă;
 modificarea programului, dacă rezultatele nu sunt satisfăcătoare şi repetarea execuţiei;
 perfectarea versiunii finale a programului, inclusiv documentarea la necesitate a acestuia;
 exploatarea unui program;
 cererea de executare a unui program deja existent. Vor fi pregătite în prealabil date de intrare sau acestea vor
fi introduse in mod interactiv la cerere cu ajutorul tastaturii;
 afişarea rezultatelor pe ecran, listarea la imprimantă sau copierea lor într-un fişier pentru o utilizare ulterioară.
Într-un atare sistem funcţia partajare a resurselor poate fi lipsă, or PC-ul este folosit de un singur utilizator care
are controlul total asupra acestuia. Alocarea resurselor este legată de gestionarea memoriei şi administrarea
fişierelor. Funcţiile principale vizibile ale sistemului de operare constau în administrarea fişierelor, realizarea
operaţiilor de intrare/ieşire şi interpretarea comenzilor provenite de la interfaţa utilizator-sistem de operare.
Pentru acest tip de sisteme cele mai importante caracteristici sunt:
 fiabilitatea;
 eficacitatea;
 simplitatea utilizării;
 facilitatea extensibilităţii prin adăugarea unor utilite noi sau adaptarea la periferice noi.
Ultimele două aspecte pun în evidenţă importanţa interfeţelor oferite de sistem (limbajul de comandă sau GUI).
1.1.3.2. Comanda unor procese industriale
La o uzină chimică sunt utilizate doua materii prime A şi B pentru sinteza produsului C conform fig. .4. Procesul
de producere este comandat de un calculator care îndeplineşte următoarele funcţii:
 Reglare. Pentru o derulare bună a procesului de fabricaţie parametrii de funcţionare (temperatura, presiunea,
concentraţia, etc.) trebuie să se afle într-o plajă de valori predefinite. Pentru aceasta va fi acţionat debitul de
intrare a materiilor prime A sau B. Parametrii de funcţionare sunt măsuraţi cu ajutorul unor captoare.
Calculatorul preia aceste măsurări şi, în dependenţă de algoritmul de comandă, acţionează robinetele de
intrare.
 Înregistrare. Rezultatele măsurărilor sunt periodic înregistrate; valorile lor sunt afişate pe un tablou de bord
şi recopiate într-un fişier ("jurnal de bord") în scopul unor prelucrări ulterioare (date statistice).
 Securitate. În cazul în care unul dintre parametrii măsuraţi depăşeşte o valoare critică predefinită reactorul
trebuie oprit imediat.
Reactor

Robinete
A Captoare C

Semnale de
măsură
Înregistrări
Semnale de comandă
Calculator

Fig. 1.4. Schema unui proces chimic


Acest mod de funcţionare introduce unele restricţii:
1. Măsurările se produc periodic; fie T valoarea intervalului de timp dintre două măsurări consecutive
(perioada de eşantionare), iar t - timpul total de prelucrare a datelor de către calculator (măsurarea propriu-
zisă a semnalelor observate, înregistrarea, calcularea semnalelor de comandă şi acționarea robinetelor).
Sistemul va funcţiona doar în cazul respectării relaţiei t  T.
2. Securitatea sistemului are prioritate maximă. Depăşirea unor valori critice trebuie să fie detectată în orice
moment şi tratarea acestor accidente va întrerupe toate operaţiile în curs de execuţie.
Funcţiile principale ale sistemului de operare sunt:
 acţionarea organelor externe (citirea semnalelor captoarelor, comanda robinetelor);
 evidenţa timpului real (declanşarea periodică a ciclului de calculare a semnalelor de comandă);
 reacţia la evenimentele exterioare (oprire de urgenţă);
 gestiunea informaţiilor (păstrarea şi întreţinerea jurnalului de bord).
Existenţa unor restricţii stricte privind durata de prelucrare a informaţiilor, noţiunea de tratare prioritară,
conectarea la dispozitive exterioare de măsurare şi acţionare sunt caracteristice aplicaţiilor informatice în timp real.
Pot fi menţionate şi alte domenii cu comandă în timp real: centralele telefonice, comanda aparatelor de zbor,
robotica, monitoringul medical, etc.
În cazul acestor sisteme caracteristica principală este fiabilitatea, or rezultatele unei funcţionări neadecvate
pot fi catastrofale. Sistemul trebuie să garanteze un serviciu minim în cazul unor căderi în pană a dispozitivelor
tehnice, unor evenimente accidentale sau erori umane.
1.1.3.3. Sisteme tranzacţionale
Caracteristicile principale ale sistemelor tranzacţionale sunt următoarele:
 sistemul gestionează un set de informaţii sau baze de date, care pot atinge volume importante;
 asupra acestor informaţii pot fi executate anumite operaţii predefinite, sau tranzacţii, adesea interactive;
 sistemul este dotat cu un mare număr de puncte de acces şi un mare număr de tranzacţii pot derula simultan.
Ca exemplu pot fi menţionate sistemele de rezervare a biletelor, de gestiune a conturilor bancare, de arhivare şi
consultare a documentelor.
Restricţiile sunt în primul rând legate de integritatea şi coerenţa internă a informaţiilor, care formează bazele de
date. Aceste restricţii depind, evident de aplicaţie. De exemplu, numărul de locuri rezervate într-un avion nu poate
depăşi numărul locurilor disponibile, un loc distinct poate fi atribuit unei singure persoane, etc.
Calităţile obligatorii ale unui sistem tranzacţional sunt disponibilitatea şi fiabilitatea; pentru unele sisteme poate
fi importantă şi toleranţa la defecţiuni. O caracteristică importantă ale sistemelor tranzacţionale este multitudinea
activităţilor paralele, iar în multe cazuri şi repartizarea geografică a componentelor.
1.1.3.4. Sisteme cu partajarea timpului
Destinaţia principală a unor astfel de sisteme este furnizarea serviciilor necesare unei mulţimi de utilizatori,
fiecare dintre ei beneficiind de servicii:
 echivalente serviciilor unui calculator individual;
 legate de existenţa unei comunităţi de utilizatori: partajarea informaţiilor, comunicaţii între utilizatori.
Problemele care apar datorită conceptului de partajare a timpului sunt o combinaţie a problemelor existente în
cazul unui calculator individual cu cele din sistemele tranzacţionale şi pot fi clasificate după cum urmează:
 definirea maşinii virtuale, oferite fiecărui utilizator;
 partajarea şi alocarea resurselor fizice comune: procesoare, memorii, organe de comunicaţie;
 gestionarea informaţiilor partajate şi a comunicaţiilor.
Caracteristicile obligatorii unui atare sistem combină în egală măsură calităţile unui sistem de operare al unui
calculator individual şi al unui sistem tranzacţional: disponibilitatea, fiabilitatea, securitatea, exploatarea optimă a
resurselor fizice, calitatea interfeţei şi serviciilor utilizatorului, simplitatea adaptării şi extensibilităţii.

1.2. Puncte de vedere asupra sistemelor de operare


1.2.1. Sistemul de operare şi procesele
Noţiunea de proces, introdusă mai sus, este asociată conceptului de lucrare (pentru a lua în considerare aspectele
dinamice) şi poate fi definită altfel ca o suită temporală de execuţii de instrucţiuni, fiind o entitate de bază în
descrierea sau analiza funcţionării unui sistem de operare. Evoluţia în timp a unui proces presupune un consum de
resurse, dictat de natura şi complexitatea instrucţiunilor de executat. Orice utilizare a unei resurse este asociată, la un
moment dat, unui proces şi procesul respectiv îşi asumă răspunderea utilizării acestei resurse. În particular, rezultă
că ori de câte ori se execută procedurile de sistem, resursele pe care le utilizează sistemul intră în administrarea
procesului (fie el şi al utilizatorului), care a cerut serviciul. Mulțimea resurselor (procesorul, memoria centrală,
informaţia, dispozitivele) alocate unui proces variază în timp (dinamica procesului).
Anterior un sistem de operare a fost definit ca un set de programe destinat să administreze resursele. Care sunt
relaţiile dintre programele sistemului de operare? Atunci când un proces este creat, care este ordinea de utilizare a

Procesul 1
Procesul 2
unui anume program al SO? Pentru a răspunde la aceste întrebări (şi la altele) vom face cunoştinţă cu ciclul de viaţă
a unui proces. În fig. 1.5 sunt prezentate trei procese (trei sarcini ale utilizatorilor, lansate în execuție) existente într-
un sistem cu multiprogramare.

Fig. 1.5. Trei procese într-un sistem cu multiprogramare


Ciclul de viaţă a unui proces poate fi reprezentat printr-un set de stări ale procesului şi tranzițiile de la o stare la
alta. Vom evidenţia trei stări elementare ale unui proces: proces ales (sau exe) – procesului i s-a alocat un procesor,
este în curs de execuţie, proces blocat (wait)– procesul aşteaptă să se producă un anumit eveniment a cărui apariţie
este indispensabilă, proces eligibil (ready)– procesul are la dispoziţie toate resursele necesare lipsă fiind doar
procesorul, adică este pregătit să fie execute din momentul alocării unităţii centrale (fig. 1.6).

Procesului i
s-a alocat Procesul
procesorul Ales aşteaptă
(Exe) terminarea
operaţiei I/O

Operaţia
de I/O s-a
terminat

Eligibil Blocat
(Ready) (Wait)
Fig. 1.6. Stările elementare din ciclul de viaţă a unui proces
Modelul prezentat în figura 1.6 este unul puternic simplificat, în realitate existând abateri substanțiale în direcția
complexităţii (fig. 1.7). Sistemul de operare este acea componentă, care va ghida procesul prin toate aceste stări.

Administrarea
memoriei
Administrarea
Expirarea dispozitivelor Administrarea
intervalului Eliberarea procesoarelor
de timp memoriei
Starea Eliberarea Eliberarea
procesorulu dispozitivelor procesorulu
i Administrarea i
procesoarelor
Ales Terminare
Administrarea
procesoarelor
Cerere de citire
informaţii

Prezentare Păstrare Eligibil Blocat


Gestiunea
informaţiei

Administrarea Iniţiere I/O


Administrarea procesoarelor
resurselor
Administrarea
Toate procesoarelor Administrarea
MO este accesibilă? dispozitivele sunt dispozitivelor
alocate?
Creare proces I/O
Gestiunea Semnal
informaţiei Administrarea Administrarea
memoriei dispozitivelor
Fig. 1.7. Modelul stărilor unui proces şi componentele SO responsabile de tranziții
1.2.2. Maşină ierarhică şi maşină extinsă
Astăzi este greu de închipuit că doar cu câteva decenii în urmă un utilizator era nevoit să programeze în zerouri
şi unităţi, utilizând o “maşina goală”. Chiar şi specialiştii nu prea iubesc să scrie programe într-un limbaj de
asamblare sau (şi mai evident) utilizând doar instrucţiuni din setul, garantat de resursele fizice. Un program elaborat
de un specialist poate fi de forma [1]:
1. Transferă C, B Stabileşte C=B
2. Găseşte zona 80, X Să se găsească 80 de octeţi de memorie liberi şi să se plaseze adresa zonei în X
3. Introdu date în X Să se citească datele indicate în zona X
4. Compară X(2), ‘/*’ Coincide conţinutul primilor 2 octeţi ai zonei X cu ‘/*’?
5. Dacă da, STOP Dacă coincid, salt la STOP
Instrucţiunile 1, 4 şi 5 sunt instrucţiuni standard în multe calculatoare contemporane. Însă execuţia corectă şi
eficientă a instrucţiunilor 2 şi 3 poate solicita îndeplinirea a zeci, sute sau chiar mii de comenzi din setul standard,
deoarece aceste instrucţiuni cer interacţiunea cu unele resurse cheie, cum ar fi memoria operativă şi dispozitivele de
intrare-ieşire. Sistemul de operare asigură instrucţiuni pentru realizarea unor astfel de funcţii de administrare a
resurselor: instrucţiunile 2 şi 3 sunt instrucţiuni ale maşinii extinse ele neavând echivalenţi în setul de instrucţiuni
hardware. Sistemul de operare completează setul standard, realizat hardware, cu instrucţiuni de acest gen.
Setul de instrucţiuni realizat hardware împreună cu instrucţiunile suplimentare ale sistemului de operare
formează sistemul de comenzi al maşinii extinse. Grafic conceptul de maşină extinsă poate fi reprezentat conform
fig. 1.8. Sistemul de operare este executat pe maşina “goală”, iar programele utilizatorului – pe maşina extinsă.
Să facem cunoştinţă acum cu modul în care sunt organizate într-un tot întreg componentele sistemelor de
operare.
Procesul 1 Procesul 2
Maşina extinsă

Maşina “goală” Programele


utilizatorului
(procese)

Sistemul de operare
Procesul 4 Procesul 3

Fig. 1.8. Maşina extinsă

Primele sisteme de operare erau formate dintr-un singur program mare. Dar, odată cu sporirea complexităţii
sistemelor, această abordare liniară conducea la dificultăţi serioase şi s-a propus să se utilizeze şi în acest domeniu
conceptul de maşină extinsă. Acest concept, în cazul sistemelor de operare, poate fi utilizat în două nivele (fig. 1.9)
şi conduce la noţiunea de maşină ierarhică [4]: primul nivel - funcţiile cheie, utilizate de majoritatea modulelor de
sistem, pot fi realizate în cadrul unei maşini extinse interne şi nivelul doi - unele module pot fi executate în cadrul
unei maşini extinse externe, analogic proceselor utilizatorului.
Componente ale S.O.

Sistemul de operare Sistemul de operare


Procesul A Procesul B

Procesul 1 Procesul 2
Maşina extinsă externă

Maşina extinsă
internă

Maşina Programele
“goală” utilizatorului
(procese)

Funcţii cheie ale


S.O.

Procesul 4 Alte funcţii Procesul 3


ale S.O.

Fig. 1.9. Ilustrarea conceptului de maşină ierarhică


În această abordare ierarhică apare imediat problema alegerii corecte a nivelului de ierarhie pentru fiecare modul
al sistemului de operare (în maşina extinsă internă, externă sau va fi prezent în calitate de proces extern). Pot fi
evidenţiate subnivele ale celor două maşini extinse, iar interacţiunea proceselor sistemului de operare ne conduce la
necesitatea introducerii mai multor straturi ale proceselor. Modulele sistemului, plasate în cadrul maşinii extinse,
spre deosebire de modulele care aparţin straturilor proceselor, formează nucleul sistemului de operare. Concepţia
maşinii ierarhice este pe larg utilizată în proiectarea programelor mari utilizând metodele, cunoscute sub denumirea
programare modulară sau programare structurală. Nu există reguli stricte în privinţa numărului de nivele,
amplasării modulelor pe nivele, componenţa nucleului. De obicei, nucleul conţine doar cele mai necesare şi evidente
funcţii, celelalte, atunci când este posibil, vor fi prezente ca procese de sistem separate. O detalizare a conceptului de
maşină ierarhică este adusă în fig. 1.10. Procesele (incluse în dreptunghiuri) se adresează către funcţiile nucleului şi
utilizează împreună sursele sistemului. Unele procese generează sau comandă alte procese (graniţa dintre ele este
prezentată de liniile în zig-zag, care separă diferite straturi ale proceselor). Într-o realizare strict ierarhică modulele,
situate într-un nivel oarecare, pot accesa (se pot adresa) numai resursele nivelelor inferioare.
Taskuri

Planificator taskuri

Procesul 2
Procesul 1 Procesul 3 Procese de I/O care
I/O de sistem deservesc procesul 3
al utilizatorului
Proces creat Stratul 2
de utilizator

Proces de I/O
Nivelul 5
Stratul 1
Nivelul 4
La nivelul 1 (nivelul cel mai inferior) sunt situate funcţiile de care au nevoie toate componentele administrării
resurselor. Una dintre acestea este funcţia care urmăreşte repartizarea resurselor, funcţie, care la rândul său, solicită
anumite mijloace de sincronizare. Aceste operaţii elementare sunt numite P-operator (ocuparea resursei sau cererea
de acces) şi V-operator (eliberarea resursei). Sincronizarea se face printr-o tehnică de programare, numită semafor.
Fiecărei resurse îi este ataşat un semafor. Atunci când în sistem apare o cerere pentru o resursă oarecare, pentru
testarea semaforului respectiv este utilizat P-operatorul; dacă semaforul este “pe verde” (resursa este liberă), P-
operatorul îl trece “pe roşu” (îl închide) şi returnează controlul. În caz contrar, procesul care a generat cererea este
trecut în stare de aşteptare, pentru ca mai târziu, atunci când V-operatorul va elibera resursa şi va trece semaforul
“pe verde”, să acceseze resursa.
Amplasarea funcţiilor elementare pe nivele poate fi făcută în felul următor:
Nivelul 1. Administrarea procesoarelor (nivelul inferior)
 P - operatorii de sincronizare
 V - operatorii de sincronizare
 planificarea proceselor (aparatul de multiprogramare)
Nivelul 2. Administrarea memoriei
 alocarea memoriei
 eliberarea memoriei
Nivelul 3. Administrarea procesoarelor (nivelul superior)
 crearea şi distrugerea unui proces
 transmiterea şi recepţionarea mesajelor între procese
 lansarea unui proces
 oprirea unui proces
Nivelul 4. Administrarea dispozitivelor
 urmărirea stărilor tuturor dispozitivelor periferice
 planificarea intrărilor/ieşirilor
 iniţierea operaţiilor de intrare/ieşire
Nivelul 5. Administrarea informaţiei
 crearea şi distrugerea unui fişier
 deschiderea şi închiderea unui fişier
 citirea şi înscrierea unui fişier.
Nucleul sistemului de operare este format de subprogramele, care asistă execuţia proceselor. Pentru a decide care
funcţii pot fi realizate în formă de procese separate este necesar să se stabilească funcţiile care pot fi executate
independent şi în mod paralel cu procesele utilizatorului (nu se va câştiga nimic evidenţiind în procese separate
funcţii care trebuie să fie îndeplinite secvenţial). Sistemele avansate permit crearea oricărui număr de procese, ceea
ce este foarte comod pentru organizarea calculelor paralele sau a regimurilor de timp real. În cadrul tehnologiilor noi
noţiunea de proces a fost substanţial modificată, introducându-se alte concepte (fire, thread en.), care exploatează
într-un mod mai eficient ideile multitasking-ului şi multiprogramării.
1.2.3. Alte puncte de vedere
În compartimentele precedente au fost tratate sistemele de operare din diferite puncte de vedere, cum ar fi SO şi
procesele, SO şi maşina extinsă sau SO şi maşina ierarhică. Există şi alte puncte de vedere asupra sistemelor de
operare pe care un specialist ar trebui să le cunoască.
Pentru un utilizator obişnuit, convins că un calculator este doar un instrument care îl ajută în rezolvarea unor
probleme din domeniul său de activitate, noţiuni cum ar fi administrarea memoriei cu paginaţie sau driverele
dispozitivelor nu semnifică prea multe. Destinaţia principală a unui sistem de operare pentru această categorie de
utilizatori este punerea la dispoziţie a unui set de programe care ar ajuta în formularea şi rezolvarea problemelor
concrete. Abordarea sistemelor de operare din acest punct de vedere (abordare funcţională) poate conduce la
confundarea lor cu unele programe, utile şi foarte importante cum sunt translatoare, bibliotecile, mediile integrate,
etc. Pentru a evita posibilitatea apariţiei unei astfel de probleme aceste programe, de obicei, nu sunt considerate
componente ale sistemului de operare.
Interfaţa sistemului de operare cu utilizatorul prezintă un interes aparte. Progresul în acest domeniu este
spectaculos, dacă vom lua în consideraţie că în primele sisteme de operare utilizatorul era obligat să indice în mod
explicit şi manual fiecare pas, oricât de nesemnificativ ar fi părut. Formularea paşilor cu ajutorul limbajului de
control al lucrărilor (Job Control Language, JCL) nu a schimbat substanţial situaţia. Acest limbaj, nefiind agreat de
utilizatorii simpli, care l-au denumit limbaj păsăresc, aşa şi nu a fost acceptat de către aceştia.
Conform JCL utilizatorul trebuie să încorporeze programul propriu într-un set de instrucţiuni, care indicau
începutul, sfârşitul programului şi al datelor de intrare, paşii şi conţinutul concret al paşilor. JCL în principiu era un
metalimbaj de programare (programare la nivel macro). Pentru mijloacele tehnice de la acea perioadă JCL a sporit
substanţial eficienţa sistemelor de calcul, deşi au existat multe inconveniente, principalul fiind lipsa posibilităţii
lucrului interactiv.
Microprocesoarele şi memoriile anilor 1970 au pus problema lansării pe piaţă a calculatoarelor personale (PC) cu
toate consecinţele asociate. Una din consecinţe este şi interfaţa utilizator-calculator, sistemul de operare devenind
până la urmă responsabil de aceasta. Interfaţa grafica a utilizatorului (Graphical User Interface - GUI) a apărut mai
întâi ca un complement al sistemului de operare (pentru MS DOS - Windows 1, Windows 2 sau chiar Windows 3,
de exemplu), pentru ca mai apoi să fie integrată în cadrul sistemului (Windows 95, Windows NT, etc.). Un sistem de
operare nu este, în principiu, obligat să posede o interfaţă sofisticată, totul este determinat de baza tehnică utilizată şi
de necesităţile concrete. Oricum, un specialist trebuie să distingă aceste două noţiuni – sistemul de operare şi
interfaţa utilizatorului.

1.3. Evoluţia sistemelor de operare


O analiză cronologică a dezvoltării sistemelor de operare este greu de realizat, deoarece multe din principiile
foarte importante au fost realizate pentru prima dată cu mult înainte de a deveni unanim acceptate. De exemplu,
conceptele de memorie paginată şi memorie virtuală au fost realizate pentru prima dată în 1959 în cadrul sistemului
“Atlas” [5], fiind utilizate la mijlocul anilor 1960 în unele sisteme cu destinaţie specială, pentru ca în 1972 să fie
preluate de firma IBM în cadrul familiei de calculatoare mari.
Primele sisteme erau caracterizate prin prelucrarea secvenţială a sarcinilor. Timpul de execuţie a programelor
era relativ mare, instrumentele de depanare – primitive, fiecare programator îşi încărca în mod individual programul
(pachetul de cartele perforate), apăsa butoane, controla conţinutul locaţiunilor de memorie, etc. (1950 – 1956).
Sporirea vitezei de calcul, dar şi a preţului calculatoarelor cerea o utilizare mai eficientă a timpului de calculator.
Nu putea fi tolerată situaţia ca un calculator să “nu facă nimic”, atunci când utilizatorul îşi încarcă în mod manual
programul. Au fost propuse programe de monitorizare (monitoare), care treceau de la o lucrare la alta în mod
automat, utilizatorul fiind responsabil de organizarea corectă a programelor în cadrul unui pachet – primele încercări
de prelucrare pe loturi (1956 – 1959).
Odată cu creşterea complexităţii calculatoarelor, îndeosebi în ceea ce consta administrarea dispozitivelor
periferice, au fost propuse sisteme supervizoare (executive), care se aflau în memoria calculatorului în mod constant
şi acordau utilizatorilor servicii în gestiunea operaţiilor de intrare/ieşire (1959 – 1963). În aceste sisteme de operare
erau realizate şi o serie de facilităţi noi, cum ar fi controlul unor posibile încercări din partea programului de a
încălca restricţiile existente în sistem, culegerea informaţiilor de evidenţă, etc.
Au urmat apoi sistemele cu multiprogramare menite la început să rezolve problema armonizării vitezei de
calcul a unităţii centrale şi a perifericelor. Drept consecinţă, au apărut o mulţime de limbaje de control a lucrărilor, a
fost realizată o standardizare substanţială a operaţiilor de intrare-ieşire.
După 1965 au apărut primele sisteme cu partajare a timpului (time sharing), au fost propuse sisteme sofisticate
de administrare a informaţiei (sisteme de gestiune a datelor sau sisteme de fişiere, File Systems). Principiul time
sharing oferea posibilitatea lucrului interactiv a mai multor utilizatori pe un singur calculator, fiecărui utilizator în
mod ciclic acordându-i-se un interval anume de timp (cuantă de timp) şi, datorită vitezei mari de calcul a unităţii
centrale, creându-i-se impresia posesiei tuturor resurselor calculatorului.
Memoria virtuală şi maşinile virtuale sunt nişte principii care nici până astăzi nu au fost exploatate până la
capăt. Progresele ultimilor ani în domeniul resurselor tehnice au permis implementarea acestor principii nu numai în
cadrul sistemelor de calcul mari, ci şi pentru calculatoarele personale. Specificaţiile sistemelor de operare au fost în
mare măsură standardizate, diversitatea SO devine tot mai mică, mulţi specialişti exprimându-şi îngrijorarea de o
posibilă monopolizare a domeniului într-un viitor apropiat. Evident, aceasta nu poate să sugereze nici într-un caz
ideea că studierea principiilor de bază (mai vechi şi mai noi) ale sistemelor de operare ar fi de prisos, ca şi
familiarizarea sau chiar cercetarea minuţioasă a unor sisteme existente, nicidecum nu poate însemna, în special
pentru un specialist, pierderea interesului faţă de analiza şi concepţia sistemelor de operare.
O prezentare succintă a evoluţiei sistemelor de operare facilitează înţelegerea caracteristicilor actuale ale
acestora şi a termenilor deja introduşi.
1.3.1. De la "poartă deschisă " la tratarea pe loturi
Primele calculatoare nu dispuneau de sisteme de operare. Fiecărui utilizator i se rezerva pentru un timp
determinat calculatorul cu toate resursele acestuia. Interacţiunea era directă, programul şi datele fiind introduse în
mod manual sub formă de zerouri şi unităţi. Utilitarele care au apărut aveau destinaţia de a asista elaborarea
programelor (asambloare, compilatoare, etc.) sau de a facilitata operaţiile de intrare-ieşire.
Acest mod de exploatare, numit "poartă deschisă", era de o eficacitate minimă, dispozitive foarte costisitoare
fiind utilizate ineficient. Din această cauză la sfârşitul anilor 1950 au apărut primele "monitoare de înlănţuire" -
programe care permiteau executarea secvenţială a unui set de lucrări, pregătite anticipat, trecerea de la o lucrare la
alta fiind realizată în mod automat.
Funcţia principală a unui atare sistem era gestiunea resurselor: memoria, procesorul, dispozitivele perifierice.
Automatismul acestei gestionări implică o funcţie de protecţie a setului de lucrări contra unor riscuri perturbatorii in
caz de eroare:
 limitarea timpului de ocupare a procesorului pentru a evita blocarea sistemului atunci când un program
conţine o buclă infinită;
 administrarea corectă a intrărilor-ieşirilor pentru a evita blocajele în utilizarea perifericelor;
 protecţia zonei de memorie rezervate monitorului pentru a împiedica modificarea accidentală a acestuia.
Deşi utilizarea monitoarelor de înlănţuire a ameliorat notabil randamentul utilizării procesorului, acest randament
rămânea foarte scăzut din cauza că procesorul nu era folosit în timpul operaţiilor de intrare-ieşire. O soluţionare ar fi
constat în utilizarea a două calculatoare - unul (principal) pentru executarea programelor şi altul (auxiliar) pentru
operaţiile de intrare-ieşire. O planificare adecvată a executării lucrărilor permitea utilizarea celor două calculatoare
în paralel, dând posibilitatea sporirii la maximum a randamentului calculatorului principal.
1.3.2. Multiprogramarea
Progresul tehnologic şi conceptual al anilor 1960 - 1970 a permis excluderea unor limitări caracteristice
sistemelor de tratare pe loturi. Astfel, au fost introduse procesoare specializate pentru operaţiile de intrare-ieşire
(unităţile de schimb sau canalele), care permiteau eliberarea procesorului central de gestionarea dispozitivelor de
intrare-ieşire. Introducerea principiului multiprogramării cu partajarea memoriei între mai mulţi utilizatori au permis
o utilizare şi mai bună a procesorului central.
1.3.2.1. Organizarea intrărilor - ieşirilor în zone de memorie tampon
Un canal este un procesor specializat în executarea autonomă a operaţiilor de intrare-ieşire, paralel cu procesul
de prelucrare a informaţiilor. Viteza de lucru a organelor periferice este relativ mică din cauza unor dispozitive
mecanice care intră în componenţa acestora. Pentru excluderea influenţei perifericelor asupra vitezei de lucru a
sistemului de calcul s-a propus să se păstreze în memorie în anumite zone tampon datele de intrare (care vor fi
utilizate la necesitate) şi rezultatele (care vor fi imprimate în mod autonom) mai multor lucrări. Această situaţie este
prezentată în fig.1.11.

Debit înalt
Lucrări la Rezultate
intrare la ieşire

Debit mic Debit mic


Intrări Prelucrarea informaţiilor Ieşire

Fig.1.11. Buferizarea intrărilor-ieşirilor

Pentru a exclude utilizarea ineficientă a memoriei operative prin formarea zonelor-tampon, acestea erau
organizate în memoria secundară de capacitate înaltă, transferurile între aceste două nivele ale memorie fiind de
asemenea comandate de un canal.
Deşi utilizarea memoriilor tampon prezintă o serie de avantaje privind sporirea randamentului dispozitivelor
calculatorului, totuşi două momente negative pot fi menţionate:
 atunci când lucrarea în curs de execuţie are nevoie de date unitatea centrală rămâne inactivă pe toată perioada
citirii acestora;
 o lucrare de scurtă durată, sosită în timpul execuţiei unei lucrări "lungi", trebuie să aştepte terminarea acesteia
din urmă.
1.3.2.2. Multiprogramarea
Ultimele două momente au condus la ideea utilizării unui mod de funcţionare a SO în care:
 o lucrare aflată în starea eligibil să poată folosi unitatea centrală, eliberată de o lucrare care trece în aşteptarea
datelor,
 unitatea centrală să poată modifica timpul său de alocare înainte de terminarea unei lucrări în scopul
satisfacerii cerinţelor legate de timpul de răspuns.
În acest caz este necesar ca valoarea timpului de comutare a unităţii centrale să fie mai mică în raport cu durata
unui transfer între nivelele memoriei. Aceasta implică prezenţa simultană în memoria operativă a mai multor
programe sau părţi de programe. Acest mod de funcţionare este numit multiprogramare [6] şi este prezentat în
figura 1.12.
Schema din figura 1.12 evidenţiază două momente importante: rolul principal al memoriei operative (în SO
anterioare acest rol era deţinut de către unitatea centrală) şi fluxul informaţional între memoria operativă şi cea
secundară. Drept consecinţă, volumul memoriei operative şi viteza de transfer între cele două nivele ale memoriei
devin caracteristici determinante ale performanţei sistemului.
Unitatea
centrală
Execuţie
Citire Imprimanta

Memoria operativă
Principalele avantaje şi restricţii ale multiprogramării pot fi prezentate în rezumat după cum urmează:
 un sistem cu multiprogramare este mai complicat de conceput și implementat pentru că el trebuie să asigure
partajarea memoriei şi protecţia reciprocă a programelor;
 multiprogramarea solicită dispozitive speciale pentru relansarea programelor şi protecţia memoriei;
 un sistem cu multiprogramare asigură o utilizare mai uniformă a resurselor: unitatea centrală, memoria,
organele de intrare-ieşire;
 multiprogramarea permite reducerea timpului de răspuns în cazul lucrărilor de durată mică într-un sistem cu
prelucrare secvenţială.
1.3.2.3. Partajatarea timpului
Un sistem de operare cu partajarea timpului trebuie să garanteze fiecărui utilizator un timp acceptabil de răspuns.
Acest rezultat este obţinut prin alocarea succesivă a procesorului pentru tranşe de timp (cuante) relativ mici
programelor utilizatorilor. Viabilitatea unei asemenea tehnici este legată de caracteristicile lucrului interactiv.
Activitatea unui utilizator conţine două componente: timpul de reflecţie (gândire), în care utilizatorul elaborează,
propune subiecte de lucru, introducând în calculator informaţii şi timpul de aşteptare, când aşteaptă prestarea
serviciului solicitat. Prima componentă este de o durată medie mult mai mare decât a doua şi sistemul poate să
servească simultan mai mulţi utilizatori, folosind timpul mort, datorat perioadei de reflecţie.
Ca exemplu, fie un sistem cu partajarea timpului, care deserveşte 1000 utilizatori cu un comportament mediu
identic. Admitem că durata timpului de gândire este în medie de 9 ori mai mare decât a timpului de aşteptare, acesta
din urmă reprezentând 10% din timpul total. Avem în medie 100 utilizatori activi (care se află în aşteptarea unui
răspuns la un serviciu cerut). Presupunem că valoarea cuantei este de 5 ms. Dacă executarea unei cereri durează o
cuantă, timpul de răspuns va fi de ordinul unei jumătăţi de secundă.
Am admis în acest exemplu că toate programele utilizatorilor activi se află în memoria principală: timpul de
comutare între programele a doi utilizatori se reduce la timpul de realocare a unităţii centrale, care poate fi neglijat
în comparaţie cu valoarea cuantei. Multiprogramarea devine obligatorie, dacă ţinem cont de raportul dintre acest
timp şi timpul necesar pentru încărcarea unui program în memoria secundară. Situaţia este de fapt şi mai complicată:
volumul memoriei principale nu permite aflarea aici a tuturor programelor utilizatorilor activi. Este posibil ca o
informaţie să fie lipsă în memoria operativă atunci când unitatea centrală are nevoie de ea. Rolul sistemului de
operare este de a minimiza pe cât este posibil probabilitatea apariţiei unui atare eveniment.
Dezvoltarea sistemelor de operare cu partajarea timpului a pus în evidenţă importanţa interacţiunii om-calculator.
Acest aspect a fost mult timp neglijat, dar cei care au procedat altfel au avut doar de câştigat.
Apariţia microprocesoarelor cu posibilităţi mari de calcul şi preţuri tot mai mici a permis pe de o parte utilizarea
la scară largă a calculatoarelor personale, iar pe de altă parte, satisfacerea cerinţelor utilizatorilor sistemelor
informatice de partajare a resurselor fizice şi logice distribuite geografic. Au apărut noi tipuri de sisteme
informatice: calculatoarele personale, reţelele locale şi reţelele globale.
Conceptele şi tehnicile elaborate pentru sistemele centralizate au rămas valabile şi în cazul serverelor sau
sistemelor de operare pentru calculatoarele personale. Repartizarea distribuită a informaţiei, precum şi transmiterea
ei la distanţe mai mari sau mai mici a ridicat probleme noi, cum ar fi coordonarea activităţilor la distanţă sau
menţinerea coerenţei informaţiilor distribuite. Toate acestea au trebuit să fie luate în consideraţie la elaborarea
sistemelor de operare de rețea.
1.3.3. Windows, Unix şi alte sisteme
Paralel cu evoluţia tehnică şi funcţională a sistemelor de operare a avut loc şi o importantă evoluţie conceptuală,
care a permis o mai bună înţelegere a funcţionării SO şi a condus la elaborarea unor metode proprii de concepere.
Anul 1964 poate fi considerat de debut pentru cercetările ştiinţifice în domeniul sistemelor de operare, cercetări
care au fost generate de rezultatele obținute în sfera tehnică - primele sisteme cu partajare a timpului (Thor, CTSS),
anunţarea seriei IBM 360 şi introducerea canalelor de intrare-ieşire, prima memorie paginată (Atlas), etc.
În perioada 1965-1968 au fost fundamentate concepte teoretice importante, necesare pentru conştientizarea
gestionării activităţilor fizice sau logice paralele: proces secvenţial, excluderea mutuală, sincronizarea, semaforul.
Acestea au fost aplicate cu succes la elaborarea sistemelor de operare: sistemul THE (1967) utilizează semafoarele,
sistemul Multics (1964-1970) conţine un nucleu de gestionare a proceselor. Metode şi utilitare destinate sistemelor
distribuite sunt propuse la sfârşitul anilor 1970. Utilitarele de sincronizare încep să fie introduse în limbajele de
programare. Sunt propuse primele metode de specificare şi control al validităţii sincronizărilor.
Problema definirii informaţiei a fost pusă iniţial în seama limbajelor de programare: fiecare limbaj definea
universul său de obiecte accesibile şi mijloacele efective de accesare. Sistemul de operare trebuia doar să
implementeze obiectele definite de limbajul de programare în memoria fizică, direct adresabilă.
Restricţiile de capacitate şi preţ a memoriei principale a condus foarte repede la modificarea modului de utilizare
a memoriei secundare şi introducerea unor mecanisme de schimb de informaţii între aceste două nivele ale
memoriei. Prima memorie paginată apare în 1962 (Atlas), tot atunci compilatoarele limbajului Algol 60 folosesc
paginaţia virtuală pentru gestionarea programelor în curs de execuţie. În 1965 este fundamentată noţiunea de
segmentare, deşi încă în 1959 sistemul Burroughs B5000 utiliza un mecanism de adresare logică prin unităţi de
volum variabil - segmente. În 1966 este propus un model de arhitectură pentru calculatoarele cu partajare a timpului,
implementat în IBM 360/370. În acest calculator segmentele sunt doar simulate printr-o dublă paginaţie, din care
cauză noţiunea de segmentare va rămâne pentru un timp puţin înţeleasă de comunitatea informatică. Problemele
partajării şi protecţiei informaţiei, alocării resurselor conduc la noţiunea de memorie virtuală, care permite izolarea
mecanismelor de gestionare a ansamblului “memorie principală - memorie secundară”, lăsând utilizatorilor simpli
impresia unui spaţiu de adrese uniform (contiguu). Tot atunci au fost introduse noţiunile de modularitate şi
structurare ierarhică, obiect, etc.
Astăzi sistemele de operare în sensul tradiţional de concepere sunt mai mult obiectul unor elaborări industriale,
decât de cercetare. Aceasta se datorează atât existenței unei rezerve extraordinare de metode şi algoritmi, cât şi
standardizării stricte a funcţiilor şi interfeţelor sistemelor de operare.
1.3.3.1. SО UNIX şi sistemele deschise
Sistemul de operare UNIX, primul sistem “mobil” care asigură un mediu fiabil de dezvoltare şi utilizare a
softului de aplicaţie, este fundamentul practic de elaborare a sistemelor fizico-logice deschise. Implementarea largă
a SO UNIX a permis trecerea de la declararea conceptului sistemelor deschise la dezvoltarea în practică a acestui
concept. Este greu de supraestimat şi influenţa activităţilor de standardizare a interfeţelor SO UNIX asupra
dezvoltării domeniului sistemelor deschise. Cu toate acestea, pot fi evidenţiate câteva versiuni ale SO UNIX, care
diferă atât prin realizare, cât şi prin interfeţe şi semantică, chiar dacă, odată cu dezvoltarea procesului de
standardizare, aceste diferenţe devin tot mai nesemnificative.
Modulele sursă ale SO UNIX au fost scrise de către colaboratorii companiei AT&T şi timp îndelungat drepturile
de autor, ca şi drepturile de licenţiere, au aparţinut acestei companii. Mai târziu, din mai multe motive (complexitate
tehnică în elaborarea şi mentenanța acestui produs program complicat, probleme juridice, etc.), compania AT&T a
creat entitatea USL (UNIX System Laboratories), responsabilă exclusiv de dezvoltarea şi susţinerea SO UNIX.
Compania USL a propus versiunea UNIX System V 4.0, care a devenit standardul de-facto şi baza mai multor
versiuni UNIX, create de producătorii de staţii de lucru şi servere. În ultimul succes al USL în calitate de filială a
firmei AT&T - versiunea SVR 4.2 - pentru prima oară în istoria SO UNIX a fost realizat mecanismul proceselor
“lejere” (fire, en. thread), care erau executate în baza unei memorii virtuale comune şi permitea exploatarea
conceptului “arhitectură multiprocesorală simetrică” în care mai multe procesoare au drepturi egale de accesare a
memoriei operative comune.
În anul 1993 USL a fost absorbită de compania Novell, devenind astăzi departament al acesteia, marca
înregistrată UNIX fiind cedată consorţiumului X/Open. La începutul anului 1995 compania Novell a anunţat o
variantă nouă a SO UixWare 2.0 bazată pe System V 4.2. Era un SO cu un sistem de fişiere foarte fiabil, fiind admis
accesul la fişierele păstrate pe serverele NetWare, administratorul avea la dispoziţie o interfaţă grafică bine pusă la
punct, etc.
Pe parcursul a mai multor ani sistemul de operare de bază al companiei Sun a fost UNIX BSD. Însă, începând cu
SunOS 4.0, s-a trecut la System V 4.0, firma Sun introducând o serie de modificări şi extensii în această versiune.
Ca exemplu, Sun a implementat paralelizarea programelor pentru sistemele multiprocesorale simetrice. Solaris este
o interfaţă a lui SunOS cu mijloace suplimentare GUI şi resurse de nivel înalt pentru organizarea lucrului în reţea (de
exemplu, apelarea procedurilor la distanţă – RPC). Şi la baza SO HP/UX, DG/UX şi AIX se află SVR 4.x din care
cauză setul de bază al funcţiilor de sistem şi biblioteci este acelaşi. Variantele SO UNIX, propuse de compania SCO
şi destinate exclusiv platformelor Intel, sunt bazate pe modulele iniţiale ale System V 3.2, fiind compatibile cu toate
standardele de bază
Open Software Foundation (OSF) a fost prima companie comercială, care a încercat elaborarea SO UNIX în
baza micronucleului Mach. A fost creat sistemul de operare OSF-1, care nu era „curat” (în sens de licenţiere),
deoarece folosea o parte a modulelor iniţiale din SVR 4.0.
Variantele SO UNIX, propuse de Universitatea din California, sunt o alternativă reală pentru UNIX AT&T. De
exemplu, UNIX BSD 4.2 era pus la dispoziţie în module sursă şi folosită chiar în fosta URSS pe calculatoarele de
tip CM-3, CM-4. Grupul BSD a influenţat enorm dezvoltarea sistemelor de operare UNIX, printre realizări amintim
mult controversatul SO UNIX BSD 4.4, construit în baza principiilor „micronucleare”, sistemul FreeBSD şi altele.
Nu putem să nu amintim aici şi de realizarea originală a SO UNIX pentru platformele Intel, propusă de Torvald
Linus – LINUX. Este un sistem de operare foarte popular în mediul studenţesc, care are şi una din cele mai populare
teleconferinţe în Internet.
1.3.3.2. Standarde UNIX
Odată cu ieşirea SO UNIX pe piaţă şi creşterea substanţială nu numai a numărului de utilizatori, ci şi a numărului
de specialişti din toate colţurile lumii, care propun modificări şi chiar variante proprii, a apărut necesitatea elaborării
unor standarde, care ar asigura compatibilitatea.
Unul dintre primele standarde de-facto a fost cel publicat de USL pentru versiunea SO UNIX System V Release
4 - System V Interface Definition (SVID). Majoritatea variantelor comerciale respectau standardul SVID. Evident,
SVID fiind un document de firmă, publicat fără discuţii publice nu putea fi adoptat ca și standard oficial.
Paralel exista direcţia BSD (Berkeley Standard Distribution), susţinută de comunitatea universitară. Deşi
majoritatea realizărilor comerciale se bazau pe Sistem V, UNIX BSD era foarte popular în universităţi din care
cauză a fost elaborat un standard, care a unit practic AT&T cu BSD. Acest lucru a fost început de asociaţia
programatorilor profesioniști din cadrul UniForum (Sistemele Deschise) şi continuat de grupurile de lucru POSIX
(Portable Operating System Interface). Cel mai popular standard, adoptat de ISO la recomandarea IEEE, POSIX
1003.1 defineşte cerinţele minime pentru componentele unui sistem de operare.
Organizaţia internaţională X/Open, care activează în domeniul elaborării şi propagării ideilor sistemelor
deschise, culege şi sistematizează standardele de-jure şi de-facto de importanţă industrială în aşa numitul X/Open
Common Application Environment (CAE). Specificaţiile interfeţelor componentelor, care formează CAE, sunt
publicate în X/Open Portability Guide (XPG).
Pentru lumea UNIX este foarte important şi standardul limbajului de programare C, adoptat mai întâi de ANSI şi
apoi de ISO. În acest standard sunt specificate, în afara limbajului C, bibliotecile obligatorii într-o realizare standard.
Deoarece chiar de la apariţie limbajul C, sistemele de programare erau strâns legate de UNIX, componentele
bibliotecilor standard corespundeau exact mediului standard al SO UNIX.
Mai menţionăm standardul de-facto SPARC Complience Definition, propus de organizaţia SPARC International,
propunerea organizaţiei 88/Open pentru procesoarele RISC Motorola, standardul sistemului de ferestre, susţinut de
X Consorţium (Institutul de Tehnologie din Massachussets) şi OSF/Motif, elaborat de Open Software Foundation.
1.3.3.3. Sisteme de operare cu micronucleu
Micronucleul este partea minimă principală a unui sistem de operare, folosită pentru asigurarea modularităţii şi
transportabilităţii. Noţiunea de micronucleu a fost introdusă de compania Next prin sistemul de operare cu
micronucleul Mach. Nucleul acestui sistem de operare, de dimensiuni mici, în jurul căruia se situau subsistemele
executate în regim user, trebuia să asigure o flexibilitate şi modularitate foarte înaltă. Dar în realitate acestea au fost
umbrite de prezenţa serverului monolit, care realiza sistemul de operare UNIX BSD 4.3, ales de compania Next în
calitate de nivel superior pentru micronucleul Mach. Totuşi, utilizarea micronucleului Mach a permis introducerea
administrării mesajelor şi a unei serii de funcţii de serviciu orientate pe obiecte, în baza cărora a fost creată o
interfaţă grafică elegantă a utilizatorului cu mijloace simple de configurare, administrare şi dezvoltare program.
Următorul SO cu micronucleu a fost MS Windows NT, în care momentul principal declarat era, în afara
modularităţii, transportabilitatea. Acest sistem de operare poate fi utilizat în sistemele mono- şi miltiprocesor, bazate
pe procesoarele Intel, Mips, şi Alpha. Mai mult, deoarece NT trebuia să execute şi programele scrise pentru DOS,
Windows, OS/2 şi SO, compatibile cu standardele Posix, compania Microsoft a folosit modularitatea abordării
„micronucleare” pentru crearea unei structuri generalizate, care nu repetă sistemele de operare existente, fiecare SO
fiind emulat printr-un modul separat sau printr-un subsistem.
Au aderat la tehnologia „micronucleară” şi companiile Novell/USL, Open Software Foundation (OSF), IBM,
Apple şi altele. Unul din concurenţii principali ai lui NT în domeniul SO cu micronucleu sunt Mach 3.0, creat în
Universitatea Carnegy-Mellon, şi Chorus 3.0 al companiei Chorus Systems.
Un exemplu practic
Scopul exerciţiilor de mai jos [7] este de a pune în evidenţă influenţa evoluţiei istorice a sistemelor de operare
asupra unor caracteristici ale acestora.
Vom considera un calculator periferia căruia este un dispozitiv de introducere a cartelelor perforate (1 000
cartele pe minut) şi o imprimantă (1 000 linii pe minut). O lucrare medie este definită astfel:
 citeşte 300 de cartele,
 utilizează procesorul 1 minut,
 tipăreşte 500 de linii.
Se presupune, că toate lucrările au caracteristici identice cu ale lucrării medii.
Definim două caracteristici de performanţă a sistemului:
 debitul mediu D - numărul de lucrări executate într-o oră,
 randamentul unităţii centrale ŋ - partea din timpul total de utilizare a unităţii centrale în care aceasta execută
lucru util, altul decât gestionarea perifericelor.
Exercițiul 1. Presupunem că periferia este gestionată de către unitatea centrală. Să se calculeze D şi ŋ în
următoarele ipoteze de funcţionare:
 sistemul este exploatat în regim "poartă deschisă"; durata unei sesiuni de exploatare este de 15 minute,
 sistemul este exploatat utilizând un monitor cu înlănţuire secvenţială a lucrărilor.
Exercițiul 2. Presupunem că periferia este gestionată de un calculator separat, care conţine o bandă
magnetică în care sunt introduse datele la intrare şi o bandă magnetică pentru ieşire (tipărirea se va efectua de pe
această bandă). Datele la intrare sunt introduse în calculatorul principal de pe banda magnetică, acesta producând
datele la ieşire pe bandă. Timpul necesar citirii de pe şi scrierii pe benzi nu se va lua în consideraţie. Timpul de
transfer a benzilor de pe un calculator pe altul este de 5 minute în fiecare sens; se presupune că o bandă va grupa un
lot de 50 de lucrări (v. fig. 1.13).

intrări

Calculatorul Calculatorul
pentru I/E Procesare principal

ieşiri

Fig. 1.13. Exemplificarea cazului 2.


Presupunem că intensitatea sosirii lucrărilor este suficientă pentru a ocupa tot timpul calculatorului central.
Calculaţi D şi ŋ.
Stabiliţi modul de planificare a seturilor de lucrări şi calculaţi timpul mediu de aşteptare a unui utilizator (timpul
din momentul predării lucrării şi până la recepţionarea rezultatelor). Vom admite, că lucrările sosesc într-un ritm
regulat, că timpul necesar formării unui program (pregătirea unui complet de cartele perforate corespunzătoare unui
program) este 10 minute şi timpul distribuirii rezultatelor unui lot (decuparea şi trierea listelor utilizatorilor) la fel
este 10 minute.
Exercițiul 3. Periferia este administrată de un canal de intrare-ieşire. Sistemul este monoprogram, iar
monitorul de înlănţuire permite unităţii centrale să prelucreze o lucrare paralel cu citirea următoarei şi tipărirea
precedentei lucrări. Calculaţi în aceste condiţii D şi ŋ. Aceeaşi întrebare dacă lucrarea medie presupune citirea a
1200 de cartele şi imprimarea a 1500 linii într-un minut de utilizare a unităţii centrale.
Exercițiul 4. Operaţiile de intrare-ieşire sunt gestionate cu ajutorul unei memorii-tampon (bufer) pe discul
fix (spooling pentru citire şi imprimare). Lucrarea medie este cea definită în punctul 3.
Presupunem că o cartelă conţine 80 octeţi, iar o linie de imprimare 120. Care este capacitatea minimă a
memoriilor-tampon pentru citire şi imprimare necesare pentru ca unitatea centrală să fie folosită la randamentul
maxim? Care va fi în acest caz debitul lucrărilor?
Intensitatea sosirii lucrărilor şi capacitatea memoriei-tampon pentru citire sunt cele din a), iar memorie-tampon
pentru tipărire este de 2 Mo. Care este randamentul unităţii centrale?
2. TEHNICI DE EXECUŢIE ŞI COMUNICAŢIE ................................................................................................ 21
2.1. MODUL SECVENŢIAL DE EXECUŢIE A UNUI PROGRAM ........................................................................................ 21
2.1.1. Noţiuni fundamentale................................................................................................................................. 21
2.1.2. Starea şi contextul procesorului ................................................................................................................ 24
2.2. ÎNTRERUPERI, DEVIERI, APELAREA SUPERVIZORULUI......................................................................................... 25
2.2.1. Activităţi asincrone .................................................................................................................................... 25
2.2.2. Mecanisme de comutare a contextului ....................................................................................................... 26
2.2.3. Întreruperi ................................................................................................................................................. 27
2.2.4. Devieri şi apelarea supervizorului............................................................................................................. 29
2.2.5. Exemple de sisteme de întreruperi ............................................................................................................. 30
2.3. IMPLEMENTAREA MECANISMELOR DE COMUTARE A CONTEXTULUI ................................................................... 31
2.3.1. Utilizarea devierilor şi apelării supervizorului ......................................................................................... 31
2.3.2. Exemple de utilizare a întreruperilor ........................................................................................................ 34
2.3.3. Concluzii .................................................................................................................................................... 37
2.4. PROGRAMAREA OPERAŢIILOR DE INTRARE-IEŞIRE ............................................................................................. 37
2.4.1. Organizarea generală ................................................................................................................................ 38
2.4.2. Metode de comandă a perifericelor ........................................................................................................... 40
2.4.3. Intrări-ieşiri buferizate în memorie ........................................................................................................... 43
2.4.4. Încărcarea iniţială ..................................................................................................................................... 47
EXERCIŢII ................................................................................................................................................................. 47
2. Tehnici de execuţie şi comunicaţie
Mecanismele, care dau posibilitatea unui calculator să interacţioneze cu mediul exterior, pe de o parte, şi să manipuleze mai multe sarcini, pe
de alta, formează baza oricărui sistem de operare. Cunoaşterea detaliată a structurii şi modului de funcţionare a acestor mecanisme este
obligatorie pentru un specialist în domeniu.

1.4. Modul secvenţial de execuţie a unui program


Nucleul unui calculator este format dintr-o memorie adresabilă, în care sunt păstrate instrucţiunile şi datele
programului, şi procesorul, care interpretează instrucţiunile. Instrucţiunile şi datele se află în segmente distincte, prin
segment înţelegând o mulţime de informaţii, desemnate şi manipulate ca un tot întreg, care ocupă în memorie o suită
contiguă de amplasamente. Vom presupune că segmentele care conţin programul nu pot fi modificate în timpul
execuţiei acestuia.
Execuţia instrucţiunilor, care formează un program secvenţial, conduce la evoluţia stării calculatorului. Evoluţia
stării calculatorului poartă un caracter discret, starea este observabilă doar în momente de timp numite puncte de
observare, care corespund, de obicei, începutului şi sfârşitului unei instrucţiuni. Unele instrucţiuni mai complexe pot
conţine puncte observabile intermediare.
Starea calculatorului este definită de starea procesorului şi starea memoriei. Starea memoriei este determinată de
conţinutul locaţiunilor segmentelor încărcate în memorie. Starea procesorului este definită de conţinutul unui set de
registre (programabile şi interne), ataşate procesorului.
2.1.1. Noţiuni fundamentale
Orice program secvenţial constă dintr-o mulţime de proceduri, care se pot apela reciproc. Fiecărei proceduri îi
este asociat un segment distinct de procedură. Datele sunt reprezentate de asemenea prin segmente, care pot fi proprii
unei proceduri sau partajate între mai multe proceduri.
Numim activitate fenomenul care rezultă din execuţia neîntreruptă a unei proceduri unice. În acest fel, execuţia
unui program secvenţial constă dintr-un lanţ de activităţi.
Numim context al unei activităţi mulţimea informaţiilor accesibile procesorului în cursul acestei activităţi.
Contextul activităţii este compus din contextul procesorului (registrele programabile şi interne) şi contextul memoriei
(segmentul procedurii şi segmentul datelor). Trecerea de la o activitate la alta este realizată de instrucţiuni speciale:
apelarea şi returul din procedură, care realizează comutarea contextului.
Exemplul 2.1. O procedură p (apelantă) provoacă execuţia unei proceduri q (apelate) cu ajutorul unei secvenţe de
apel care conţine următoarele etape:
 Pregătirea parametrilor, transmişi de către procedura p procedurii q,
 Salvarea parţială a contextului lui p, necesar la retur,
 Înlocuirea contextului procedurii p prin contextul lui q.
La revenire din procedura q avem o schemă practic simetrică, doar contextul lui q fiind pierdut:
 Pregătirea rezultatelor, transmise de către procedura q procedurii p,
 Restabilirea contextului procedurii p, salvat înainte de apel.◄
Exemplul 2.2. În cazul funcţionării în corutine (proceduri simetrice, recursie mutuală – două proceduri se pot apela
reciproc), procedurile apelantă şi apelată joacă un rol simetric: secvenţa de retur este identică cu cea
de apel. Activitatea care rezultă din apelul unei proceduri p ia în calitate de context iniţial contextul
salvat la ultimul apel executat de p. Primul apel al unei proceduri trebuie să specifice valoarea iniţială
a contextului său.
Secvenţa de schimbare a contextului (reluarea) conţine următoarele etape (p este corutina părăsită, q este cea
reluată):
 Pregătirea parametrilor, transmişi de către procedura p procedurii q,
 Salvarea parţială a contextului lui p, care trebuie să fie regăsit la reluare,
 Restabilirea contextului salvat la ultimul abandon al lui q sau formarea contextului iniţial în cazul
primului apel. ◄
2.1.1.1. Mecanisme de execuţie secvenţială
Vom descrie un mecanism general pentru execuţia programelor secvenţiale, formate dintr-o suită de activităţi,
rezultate din execuţia unei proceduri sau corutine. Acest mecanism realizează funcţiile următoare:
 Salvarea şi restabilirea contextului la apelare şi retur,
 Transmiterea parametrilor între procedurile apelantă şi apelată,
 Administrarea unei zone de lucru proprii fiecărei proceduri, cu permisiunea apelurilor recursive.
Cazul procedurilor.
Structura de date utilizată în acest caz este o stivă de execuţie. Sunt utilizate diverse variante, care diferă în mod
esenţial prin specificarea detaliată a contextului şi prin modul de comutare a acestuia la apelare sau retur. Schema
execuţiei poate fi programată direct (de exemplu, în limbajul de asamblare) sau determinată de structura mecanismului
de execuţie, definit de un limbaj de programare. În ultimul caz prin context înţelegem mulţimea variabilelor accesibile
conform regulilor de vizibilitate definite în limbaj (structura blocurilor, modulele, etc.). Descriem o organizare foarte
apropiată schemei de execuţie din limbajul de programare C, presupunând respectarea următoarelor ipoteze:
 Parametrii unei proceduri sunt transmişi prin valori; la retur un singur rezultat este returnat,
 Procedurile pot fi apelate recursiv (direct sau indirect).
La fiecare apelare a procedurii o structură de date numită regiune a mediului (sau simplu mediul procedurii)
este creată în vârful stivei de execuţie, care va dispare la returul respectiv. O procedură apelată recursiv are în stivă
atâtea medii câte execuţii neterminate există pentru această procedură. La un moment concret de timp mediul din top-
ul stivei corespunde procedurii q în curs de execuţie (procedura activă), mediul imediat următor este al procedurii p,
care a apelat procedura q, etc. Stiva este administrată prin intermediul a doi pointeri (fig.2.1.):
baza pointerul care indică baza mediului procedurii active,
top pointerul care indică primul amplasament liber pentru a crea un nou mediu.

top
q
top
baz
p
p
baz
Stivă Mediul
Stivă
Înainte de apelarea
procedurii q de către După apelarea

Fig.2.1. Stiva de

Mediul conţine următoarele informaţii:


a) informaţii de salvare şi legăturile de retur
 un pointer la baza contextului procedurii apelante
 adresa returului
 un amplasament pentru rezultat
b) parametri
 n+1 amplasamente, primul conţinând numărul de parametri, iar următoarele - valorile acestora
 variabilele locale şi spaţiul de lucru al procedurii.
Aceste informaţii, cu excepţia spaţiului de lucru, sunt păstrate în amplasamente pentru care deplasarea în raport
cu originea mediului este cunoscută şi fixă, fiind posibilă adresarea relativă a lor (faţă de baza mediului). Spaţiul de
lucru este adresat din vârful stivei.
Operaţiile executate la apelarea şi returul procedurii sunt următoarele:
Apelare
1) alocarea unei zone în stiva de execuţie pentru mediul procedurii apelate (dimensiunea acestei zone, cu
excepţia spaţiului de lucru, este cunoscută anticipat)
temp:=baza
baza:=top
top:=top+dimensiunea mediului
2) salvarea informaţiilor de retur
baza_veche:=temp
memorizarea adresei de retur
3) ordonarea parametrilor
4) executarea unei ramificaţii la procedura apelată.
Retur
1) salvarea rezultatului într-un amplasament stabilit
2) restabilirea informaţiilor de retur şi eliberarea mediului
temp:=adresa de retur
top:=baza
baza:=baza_veche
3) returul
ramificare *temp ramificare indirectă
O eventuală salvare şi restabilire a registrelor sunt lăsate în seama procedurii apelante.
Cazul corutinelor.
Schema precedentă este funcţională doar pentru cazuri simple de execuţie a două corutine pentru care mediul
salvat se reduce la adresa de retur. Pentru cazuri mai generale, îndeosebi când o corutină este ea însăşi formată din
mai multe proceduri, schema stivei unice nu poate fi aplicată. Fie ca exemplu următorul caz:
corutina p corutina q
begin procedura q1
… …
reluare q; reluare p;
… …
reluare q retur
… end q1;
end p begin

q1;

end q
În momentul în care q execută reluarea lui p stiva conţine mediile p, q şi q1. Mediul care trebuie reluat este cel al
lui p, dar mediile lui q şi q1 trebuiesc păstrate. Soluţia este de a avea câte o stivă distinctă pentru fiecare corutină
(fig.2.2).
adres

reluare
adres

reluare p p
stiva stiva
Fig.2.2. Schema de execuţie pentru corutine
Pentru un moment concret de timp stiva curentă este stiva corutinei în curs de execuţie. O operaţie schimbă_stiva
permite schimbarea stivei curente, salvând adresa de reluare în top-ul fostei stive curente:
schimbă_stiva (p):
stiva_curentă.top:=adresa de reluare;
stivă_сurentă :=stiva(p)
Secvenţa de reluare va arăta astfel
reluare (p):
schimbă_stiva (p);
if prima apelare then
apelare (p) ---- secvenţa de apel primar
else
temp:=stiva_curentă.top;
ordonează parametrii în stiva_curentă;
ramificare *temp
endif
Rezumat: contextul memoriei unei activităţi la un moment curent de timp conţine, în afara segmentului
procedurii în curs de execuţie şi segmentele de date, zona mediului curent din stiva de execuţie.
2.1.2. Starea şi contextul procesorului
Starea procesorului este determinată de conţinutul registrelor acestuia. Registrele pot fi:
 adresabile, numite registre generale, manipulate de programe,
 registre speciale, de obicei grupate sub forma unor informaţii sintetice, numite cuvânt de stare program
sau cuvânt de stare a procesorului (în engleză, program status word, PSW).
Cuvântul de stare a procesorului conţine informaţii, care pot fi clasificate astfel:
1) Informaţii referitoare la starea procesorului:
(a) Starea de execuţie. Procesorul se poate afla în starea activ în care îndeplineşte instrucţiuni, sau în
starea de aşteptare, când execuţia este suspendată. Tranziţia activ  aşteptare se realizează prin
încărcarea cuvântului de stare sau prin executarea unei instrucţiuni speciale; trecerea inversă
aşteptare  activ este în mod obligator rezultatul unei întreruperi.
(b) Modul de funcţionare. Din considerente de protecţie este util să se permită doar programelor
sistemului de operare executarea unor instrucţiuni anumite (speciale), programele utilizatorilor
neavând acces la aceste instrucţiuni. Aceasta poate fi realizat definind două moduri de funcţionare
a calculatorului, care se deosebesc printr-un bit indicator în cuvântul de stare: modul stăpân
(master, eng., maître, fr.) sau supervizor şi modul sclav (slave, eng., esclave, fr.) sau program.
Setul de instrucţiuni care pot fi executate în modul program este o submulţime a setului
corespunzător modului supervizor. Astfel instrucţiunile rezervate modului supervizor pot fi numite
privilegiate, aici pot fi găsite instrucţiunile de intrare-ieşire şi cele legate de întreruperi şi protecţie.
Pot exista şi alte moduri speciale.
(c) Masca întreruperilor.
2) Informaţii referitoare la contextul accesibil al memoriei şi drepturile de acces asociate: tabele de segmente,
indicatori de protecţie a memoriei, etc.
3) Informaţii referitoare la derularea activităţii curente: codul condiţiei, contorul operaţiei următoare (contor
ordinal). Pentru unele calculatoare contorul, care conţine adresa operaţiei următoare, poate fi un registru
distinct al cuvântului de stare, pentru altele el este un câmp al acestui cuvânt.

1.5. Întreruperi, devieri, apelarea supervizorului


2.2.1. Activităţi asincrone
Modelul descris mai sus poate fi folosit doar pentru un singur program, reprezentat printr-o secvenţă de activităţi,
executate pe un singur procesor. Unicele mecanisme necesare pentru organizarea comunicărilor între activităţi sunt
operaţiile de apelare şi retur pentru proceduri sau reluare pentru corutine.
Pentru cazuri mai generale sunt necesare mecanisme suplimentare, cum ar fi conceptele de asincronism sau de
protecţie reciprocă între activităţi.
Prin asincronism înţelegem efectul care îl pot avea asupra derulării unei activităţi anumite evenimente
exterioare.
Numim protecţie reciprocă între activităţi o modificare mai profundă a contextului, atunci când se trece de la
o activitate la alta, în comparaţie cu ceea ce are loc în cazul unei simple apelări de procedură.
Un caz tipic de asincronism este executarea intrărilor-ieşirilor simultan cu execuţia unui program. Trebuie să fie
asigurată posibilitatea informării programului despre terminarea unui transfer de informaţii. O primă posibilitate, care
nu necesită utilizarea unor dispozitive speciale, este cea a aşteptării active, prezentată mai jos.
Arhitectura calculatoarelor CDC Cyber 170 conţinea un procesor central PC şi 10 procesoare periferice identice
PP. Programele sistemului de operare erau executate pe PP: unul dintre acestea, numit PS, avea "grijă" de înlănţuirea
lucrărilor şi alocarea resurselor, celelalte, notate PES(i), erau specializate în operaţii de I/E. PC şi PP comunicau prin
intermediul unei memorii comune. Schematic, o operaţie de I/E se derula în felul următor:
1) Presupunem că un program al utilizatorului, executat pe PC, cere o operaţie de I/E. Procedura utilizată pentru
aceasta iniţializează un cuplu de indicatori D (cerere) şi F (terminare): D:=1, F:=0. Specificaţia precisă a
transferului necesar este plasată într-un descriptor, adresa căruia este cunoscută de către PS.
2) Un program de supraveghere, executat de PS, controlează periodic indicatorul D. În momentul în care îl
găseşte pus în 1, acest program alege unul din PES liber, fie PES(i), şi-i cere să execute operaţia de I/E.
Pentru aceasta sunt utilizaţi doi indicatori DEX(i) şi FEX(i), analogici cu D şi F, transmiţând lui PES(i) adresa
descriptorului cu informaţiile necesare operaţiei de I/E.
3) Programul de I/E, care este executat pe PES(i), testează periodic indicatorul său DEX(i). Atunci când acesta
este găsit pus în 1, programul execută transferul informaţiilor cerute, utilizând datele găsite în descriptor. La
terminarea transferului acest eveniment este semnalizat de către program prin punerea în 1 a indicatorului
FEX(i).
4) Supervizorul scrutează periodic FEX(i). Dacă acesta este găsit în 1, supervizorul face acelaşi lucru cu
indicatorul F, care este periodic consultat de către programul utilizatorului pentru a afla când se termină
transferul.
Algoritmic putem reprezenta toate acestea astfel:
programul utilizatorului programul supervizor programul pentru I/E
<cerere de I/E> ciclu ciclu
D:=1; if D=1 then if DEX(i)=1 then
F:=0; caută PES(i); execută I/E;
…… DEX(i):=1; <sincron>
FEX(i):=0; FEX(i):=1;
endif endif
<test terminare I/E> …… endciclu
if F=1 then <test terminare execuţie>
…… if FEX(i)=1 then
F:=1;
endif
endciclu
Pot fi făcute următoarele comentarii:
 relaţiile între PC şi PS, pe de o parte, şi PS şi PES, pe de alta, au aceeaşi schemă. Fiecare indicator poate fi
pus în 1 sau consultat de un singur procesor.
 procesorul central este în totalitate eliberat de operaţiile de I/E. El poate fi încărcat cu alte lucrări în timpul
acestor operaţii. În acelaşi timp, programul, care cere transferuri de informaţii verifică în mod explicit
terminarea operaţiilor de I/E.
 operaţiile de I/E sunt ele însăşi separate în două părţi: supervizarea (alegerea procesorului de intrare-ieşire în
funcţie de starea lui PES şi urgenţa operaţiei) şi transferul propriu-zis al informaţiilor, care poate fi realizat
într-un mod mai eficient de un procesor eliberat de funcţiile precedente.
Exemplele care urmează sunt chemate să ilustreze limitele metodei de aşteptare activă pentru organizarea
comunicărilor între activităţile asincrone.
Exemplul 2.3. Sistem multiprocesoral. Fie un calculator, arhitectura căruia presupune o memorie comună la
care sunt asociate mai multe procesoare identice (sistem multiprocesoral cu memorie comună),
aceste procesoare nefiind specializate cum a fost în cazul precedent, ci banalizate, adică o
activitate poate fi executată pe oricare procesor.
Comunicarea între activităţile executate pe diferite procesoare poate fi realizată prin
modificarea şi testarea periodică a indicatorilor memoriei comune. Însă aşteptarea activă conduce
în acest caz la eficienţă scăzută, deoarece consultarea periodică imobilizează procesorul. ◄
Exemplul 2.4. Măsurarea timpului. În cadrul derulării unui program adesea este necesar să se ia în consideraţie
timpul real (cazul proceselor tehnologice, raţionamente de securitate - oprirea unui program care
abuzează de procesor din cauza unei bucle infinite, etc.). Deoarece ceasul calculatorului este un
dispozitiv extern procesorului, el trebuie periodic consultat de către program, soluţie costisitoare
şi adesea imprecisă. Este necesar un mecanism care ar permite ceasului la momente fixe să
acţioneze asupra procesorului. ◄
Exemplul 2.5. Intervenţii externe. Din mai multe considerente (forţă majoră, securitate, etc.) poate fi necesar să
existe posibilitatea întreruperii derulării unui program pentru a impune procesorul să execute o
acţiune anume. Această întrerupere poate fi declanşată de către un operator uman, un dispozitiv
automat, etc. Este o situaţie analogică cu cea din exemplul precedent; din aceleaşi considerente
nu este binevenită consultarea periodică a unui indicator. Avem nevoie de un mecanism de
acţionare directă în acest caz. ◄
2.2.2. Mecanisme de comutare a contextului
Comutarea contextului unui procesor permite executarea într-o manieră indivizibilă a următoarelor două operaţii:
 trecerea cuvântului de stare într-un amplasament specificat al memoriei,
 încărcarea în cuvântul de stare a conţinutului unui alt amplasament specificat al memoriei.
Aceasta se poate întâmpla doar dacă procesorul se află într-o stare observabilă (de exemplu, între execuţia a două
instrucţiuni) sau de aşteptare. Din aceste considerente noţiunea de "punct de întrerupere" este sinonim al noţiunii
"punct observabil".
Comutarea contextului poate fi necesară din mai multe cauze distincte. Presupunem că fiecărei cauze i-a fost
asociat un număr de ordine. Pot fi întâlnite două scheme de comutare a contextului.
1) Salvare în amplasamente fixe. Fiecărei cauze (numărul unei cauze fiind i) îi sunt ataşate în mod constant o
pereche de amplasamente în memorie adresele cărora le vom nota prin csp_vechi[i] şi csp_nou[i] (csp
semnificând cuvânt de stare program). Comutarea se produce după cum urmează:
Mp[csp_vechi[i]]:=<cuvânt de stare program>
<cuvânt de stare program>:= Mp[csp_nou[i]]
În setul de instrucţiuni există o instrucţiune privilegiată schimbă_csp(adr) cu următorul efect:
<cuvânt de stare program>:= Mp[adr].
2) Salvare într-o stivă. Aici avem acelaşi procedeu în ceea ce constă cuvântul de stare nou. Însă cuvântul de
stare vechi nu este salvat într-un amplasament fix, ci într-o stivă specială:
ptr :=ptr+1
stiva[ptr] :=<cuvânt de stare program>
<cuvânt de stare program> := Mp[csp_nou[i]]
Instrucţiunea schimbă_csp(adr) este identică celei din 1); există însă şi o instrucţiune restabileşte_csp, care
restabileşte cuvântul de stare a procesorului folosind pentru acesta top-ul stivei:
<cuvânt de stare program> :=stiva[ptr]
ptr :=ptr-1
Comutarea contextului, descrisă mai sus, este declanşată sub influenţa stării unor indicatori din cuvântul de stare
program pe care procesorul îi consultă la interpretarea fiecărei instrucţiuni. Conform semnificaţiei acestor indicatori
şi maniera în care ei sunt modificaţi, pot fi evidenţiate trei mecanisme de comutare, descrise în tabelul de mai jos:
Tabelul 2.1. Mecanisme de comutare a contextului

Denumirea Cauza Utilizarea


mecanismului
Întrerupere Exterioară derulării Reacţionare la un eveniment
instrucţiunii curente asincron extern
Deviere (excepție) Legată de derularea Tratarea unei erori sau situaţii
instrucţiunii curente excepţionale
Apelarea supervizorului Comutare explicită Apelarea unei funcţii a S.O.
Adesea toate aceste trei denumiri sunt înlocuite cu una singură - întrerupere.
2.2.3. Întreruperi
O întrerupere este comutarea contextului procesorului declanşată de o cauză externă derulării instrucţiunii
curente. Fizic, întreruperea înseamnă trimiterea unui semnal procesorului, acest semnal provocând schimbarea stării
unuia dintre indicatorii, consultaţi în cursul executării fiecărei instrucţiuni. Semnalul poate proveni de la alt procesor,
de la un organ de I/E, de la un dispozitiv extern, şi în genere, de la orice proces fizic, extern procesorului întrerupt. O
întrerupere permite să cerem procesorului să suspende executarea programului curent, din primul punct întreruptibil,
şi să treacă la execuţia unui program predefinit. Acesta din urmă este numit program de tratare a întreruperii
(interrupt handler, eng., traitant de l'interruption, fr.). Programul de tratare a întreruperii este executat într-un context
diferit de cel al programului întrerupt, diferenţa fiind legată de modul de tratare, protecţie, informaţiile accesibile, etc.
Nivele de prioritate. Cauzele care duc la necesitatea întreruperii unui procesor sunt diferite, drept rezultat şi
programele predefinite, care tratează întreruperile diferă. Mecanismul întreruperilor trebuie să poată deosebi aceste
cauze. Două scheme de bază sunt utilizate în acest scop:
 Există un indicator unic pentru toate întreruperile şi acestui indicator îi este asociat un program unic de tratare
a întreruperilor. O informaţie suplimentară (codul întreruperii), care se conţine în cuvântul de stare sau într-un
amplasament de memorie, dă posibilitatea de a deosebi cauzele. Prima obligaţie a programului de tratare a
întreruperilor este de a consulta codul întreruperii pentru a stabili originea acesteia, doar apoi se va trece la
tratarea propriu-zisă.
 Fiecărei cauze este ataşat un indicator, se mai spune că fiecare cauză este ataşată unui nivel de întrerupere.
Fiecărui nivel îi corespunde un program distinct de tratare a întreruperii, activat în mod automat de către
mecanismul de comutare a contextului.

Armare Dezar
mat

Întrerupere Dezarmare

Declanşare Ar
programată mat

[ Punct de
Aştept
are

[Nivel nemascat]
[Nu există nivel activ cu o prioritate mai
mare]

Acti Achitare

Priorităţi şi mascarea întreruperilor.Fig.2.3.


AtunciStările
când unui
existănivel
maidemulte nivele de întrerupere este posibilă
întrerupere
necesitatea modificării simultane a doi indicatori, legaţi de două cauze diferite. Conflictul poate fi reglat stabilind o
ordine de prioritate în cadrul nivelelor de întrerupere. Această ordine poate fi fixată odată pentru totdeauna sau
modificabilă prin microprogramare.
Adesea este utilă protejarea, contra unor întreruperi anume, a execuţiei unei suite de instrucţiuni (de exemplu,
poate fi interzisă întreruperea unui program de tratare a întreruperii). Aceasta ar însemna întârzierea comutării
contextului, comutare, provocată de sosirea unui semnal asociat unui nivel oarecare de întrerupere. Se zice atunci, că
nivelul este mascat sau inhibat (întrerupere mascată). Ridicarea inhibării ("demascarea") va autoriza schimbarea
ulterioară a contextului. Informaţiile asociate mascării întreruperilor se află în cuvântul de stare a procesorului.
Pentru unele nivele de întrerupere efectul sosirii semnalului de întrerupere poate fi suprimat (nu doar întârziat).
Totul se va petrece în acest caz ca şi cum însăşi cauza întreruperii a fost suprimată: nivelul întreruperii este dezarmat.
Un nivel dezarmat poate fi rearmat, adică repus în serviciu.
Declanşare programată. Pentru unele calculatoare, îndeosebi cele utilizate în controlul proceselor, există o
instrucţiune de declanşare programată care permite modificarea indicatorului, asociat unui nivel de întrerupere, din
interiorul unui program. În acest fel este posibilă simularea apariţiei unei cauze externe de întrerupere.
Atunci când toate condiţiile necesare pentru ca o întrerupere să fie luată în consideraţie sunt prezente, nivelul se
numeşte activ. Această stare corespunde executării programului de tratare a întreruperii. Notăm, că această execuţie
poate fi suspendată pentru a permite tratarea unei întreruperi cu o prioritate mai înaltă. Ieşirea din starea activă se face
prin achitarea întreruperii. Achitarea este realizată folosind instrucţiunea schimbă_csp, care termină tratarea
schimbând contextul.
Operaţiile de armare, dezarmare, mascare, demascare şi declanşare a întreruperilor sunt totdeauna realizate prin
intermediul unor instrucţiuni privilegiate.
Figura 2.3 prezintă schematic tranziţiile de stare pentru un nivel de întrerupere.
Schema unui program de întrerupere. O întrerupere forţează procesorul să reacţioneze la un eveniment.
Executarea programului curent este suspendată, comanda fiind transmisă programului de tratare a întreruperii.
Programul reluat de către procesor după tratarea întreruperii nu este în mod obligator programul întrerupt (întreruperea
putea avea drept scop realocarea procesorului). Schema generală de tratare a unei întreruperi este prezentată în fig.2.4.
Programul întrerupt
Tratarea

Comutarea cuvântului de
Salvarea
contextului

Tratarea specifică întreruperii, care


presupune determinarea programului Q
pentru lansare (poate fi chiar P sau altul)

Restabilirea contextului programului Q


încărcarea cuvântului de stare a lui Q

Instrucţiunea schimbă_csp (achitare)

Programul nou Q

Fig.2.4. Schema generală a tratării unei întreruperi


2.2.4. Devieri şi apelarea supervizorului
Spre deosebire de o întrerupere, o deviere sau un apel al supervizorului sunt provocate de o cauză legată direct de
derularea instrucţiunii curente.
O deviere (trap, exeption, eng., dèroutement, exeption, fr.) semnalizează o anomalie în derularea unei instrucţiuni,
care prohibitează executarea instrucţiunii. Originile pot fi diverse:
 date incorecte, care conduc la imposibilitatea execuţiei corecte a instrucţiunii (împărţirea la zero, de
exemplu),
 tentativa executării unei operaţii interzise de mecanismul de protecţie (violarea protecţiei memoriei,
executarea unei instrucţiuni privilegiate în modul program, etc.),
 instrucţiune neexecutabilă (cod neautorizat de operaţie, adresă în afara memoriei existente, utilizarea unui
dispozitiv opţional, care este lipsă în configuraţia dată, ș.a.).
Devierile pot fi clasificate, ca şi întreruperile, conform cauzelor care le generează. Efectul unor cauze poate fi
suprimat (de exemplu devierile legate de operaţiile aritmetice, erorile fiind semnalate doar de valoarea codului
condiţiei). Natura unei devieri nu permite aplicarea noţiunii de mască. O deviere poate fi suprimată, dar nici
intr-un caz retardată.
Un apel al supervizorului (supervisor call, prescurtat SVC, eng., appel au superviseur, fr.) este o instrucţiune
chemată să provoace o comutare a contextului procesorului. Acest efect este analogic apelării unei proceduri, însă
modificarea contextului este mai profundă, fiindcă ea afectează întreg cuvântul de stare şi nu numai contorul ordinal.
Destinaţia unui apel al supervizorului este de a permite apelarea unei proceduri a sistemului de operare, pretinzând
la drepturi mai mari (modul master, întreruperi mascate, drepturi de acces, etc.), direct din programul utilizatorului.
Mecanismul comutării contextului permite asigurarea protecţiei, impunând condiţiile de intrare procedurii apelate. Ca
rezultat:
 contextul nou (modul, masca, etc.,) este specificat în noul cuvânt de stare, amplasat intr-o zonă de memorie
inaccesibilă utilizatorilor,
 programul de tratare a apelului supervizorului începe cu o secvenţă de verificare a drepturilor utilizatorului
de a executa acest apel şi stabilirea validităţii parametrilor transmişi.
Parametrii sunt transmişi ca şi în cazul apelului unei proceduri obişnuite. Returul în programul apelant se face
prin restabilirea cuvântului de stare salvat la apelare.
Alegerea procedurii accesibile în cazul unui apel al supervizorului este determinată de un parametru suplimentar,
aflat într-un registru sau în partea de adresă a instrucţiunii de apelare. Putem considera, că mulţimea procedurilor
accesibile utilizatorului în acest mod constituie o extensie a setului de instrucţiuni, formând astfel o "maşină" nouă.
Operaţiile de accesare a fişierelor şi de intrare-ieşire sunt disponibile utilizatorilor sub forma apelării supervizorului.
Figura 2.4, care descrie schematic întreruperile, este aplicabilă şi pentru cazul devierilor sau a apelării
supervizorului.
2.2.5. Exemple de sisteme de întreruperi
Funcţionarea sistemelor de întrerupere este ilustrată prin două exemple reprezentative, unul pentru un calculator
mare, altul pentru calculatoarele personale.
Exemplul 2.6. IBM 370.
Cuvântul de stare (64 biţi) conţine următoarele câmpuri:
<0-7> masca întreruperii <16-31> codul întreruperii
<8-11> cheie de protecţie <32-33> lungimea instrucţiunii
<12> codul caracterelor <34-35> codul condiţiei
<13> masca întreruperii <36-39> suprimarea devierii
<14> 1 - aşteptare, 0 - activ <40-63> contor ordinal
<15> 0 - stăpân, 1 - sclav.
Sistemul de întreruperi are 5 nivele (în ordinea de descreştere a priorităţilor): eroare hardware, deviere, apelare
supervizor, extern şi intrare-ieşire. Fiecărui nivel îi corespunde în memoria operativă un cuplu de amplasamente
rezervate cuvintelor de stare vechi şi nou. Fiecare nivel conţine mai multe cauze de întrerupere. Pentru a face o
deosebire a acestor cauze este utilizat un cod al întreruperii format din biţii 16 - 31 ai cuvântului de stare. Acest cod
este în mod automat pus la zi în cazul unei întreruperi. De exemplu, pentru o întrerupere de intrare-ieşire el va conţine
adresa canalului şi a perifericului care au provocat întreruperea; pentru o întrerupere externă acest cod permite să se
stabilească originea - ceasul, apel al operatorului, adresare a unui organ extern, etc. Biţii 0 - 7 şi 13 ai cuvântului de
stare permit introducerea măştii întreruperii. Ei permit mascarea nivelelor de întrerupere externă, eroare hardware şi,
selectiv, întreruperile de intrare-ieşire, care provin de la diferite canale. Biţii 36 - 39 permit inhibarea a 4 cauze de
deviere.
Observăm, că devierile şi apelările supervizorului sunt tratate ca şi întreruperi particulare.◄
Exemplul 2.7. IBM PC. Este propus ca exerciţiu.
1.6. Implementarea mecanismelor de comutare a contextului
Implementarea mecanismelor descrise mai sus este ilustrată printr-o serie de exemple, legate de utilizarea lor.
Vom preciza notaţia utilizată în schemele programelor:
 conţinutul unui cuvânt de stare este reprezentat prin notaţia <activitate, mod, mascare, contor ordinal>.
Primele trei câmpuri pot lua, respectiv, valorile: aşteptare/activ, master/slave, mascat/demascat. Dacă csp
desemnează un cuvânt de stare, câmpurile sale vor fi desemnate de csp.act, csp.mod, csp.masc şi csp.co.
 operatorul adr aplicat unui identificator de variabilă sau de procedură, desemnează adresa amplasamentului
unde se află variabila sau prima instrucţiune a procedurii,
 notaţia Mp[adr] desemnează conţinutul amplasamentului cu adresa adr.
 Notaţiile svc_vechi, dev_vechi, intr_x_vechi, svc_nou, dev_nou, intr_x_nou desemnează
amplasamentele unde sunt plasate şi de unde sunt încărcate cuvintele de stare.
Vom mai presupune că
 apelarea supervizorului este de forma SVC <cod>, unde <cod> reprezintă un număr care permite
identificarea funcţiei cerute,
 un indicator <cauză> permite stabilirea cauzei care provoacă o deviere.
Schemele prezentate conţin un program de iniţializare, executat la prima încărcare a sistemului. Funcţia acestui
program este de a iniţializa cuvântul de stare asociat tratării întreruperilor, devierilor şi apelurilor supervizorului.
2.3.1. Utilizarea devierilor şi apelării supervizorului
Simularea instrucţiunilor lipsă. Unele instrucţiuni din setul de bază de instrucţiuni ale procesorului pot fi
opţionale şi, deci, pot lipsi în unele configuraţii ale calculatorului. Tentativa executării unei instrucţiuni opţionale lipsă
generează o drivere de tipul instrucţiune inexistentă. Mecanismul devierilor poate fi utilizat pentru a realiza prin
program instrucţiunea inexistentă în setul de bază. De exemplu, pentru o configuraţie în care operaţiile aritmetice în
virgulă mobilă nu sunt disponibile ele pot fi simulate prin apelarea procedurilor corespunzătoare. Aceste proceduri
trebuie executate în mod slave pentru a permite tratarea explicită a erorilor: ele sunt apelate prin încărcarea cuvântului
de stare respectiv. O adresă de retur trebuie să fie pregătită de către programul de tratare a devierii în top-ul stivei
pentru a asigura returul la instrucţiunea care urmează instrucţiunii de operaţie aritmetică simulată.
Realizarea prin program:
procedura iniţializare;
dev_nou:=<activ,master,mascat, adr tratare_deviere>;
csp[ADD_FL]:= <activ,slave,demascat, adr ADD_FL>;
……
csp [DIV_FL]:= <activ,slave,demascat, adr DIV_FL>;
procedura tratare deviere;
begin
save(zonă);
case cauză of
……
instrucţiune inexistentă:
cod_op:=Mp[dev_vechi.co].cod_operaţie;
case cod_op of
……
ADD_FL: determinarea operanzilor;
introdu_în_stivă(stivă_utilizator,dev_vechi.co+1);
încarcă_csp(csp[ADD_FL])
……
else -- caz de eroare
<tratarea erorii>
end;
end;
restabilire(zonă);
încarcă_csp(dev_vechi)
end
Operanzii vor fi determinaţi prin analiza conţinutului instrucţiunii, care conţine adresele lor; returul rezultatelor
utilizează convenţiile proprii instrucţiunii în cauză (plasarea într-un registru, etc.).
Măsurarea capacităţii memoriei. Un sistem de operare presupune a fi utilizat pentru diverse configuraţii ale
calculatorului. Capacitatea memoriei, numărul şi natura dispozitivelor periferice, etc., pot fi diferite. Sistemul de
operare trebuie să se adapteze configuraţiei concrete, ca şi condiţiilor particulare de utilizare (număr de utilizatori,
priorităţi, etc.). Crearea unei asemenea versiuni specifice se numeşte generarea sistemului de operare. Pentru a reduce
frecvenţa generărilor S.O. unii parametri de configurare pot fi determinaţi în mod automat la iniţializare.
Astfel, poate fi determinată capacitatea memoriei operative utilizând o deviere pentru adresa amplasamentului de
memorie inexistent. Memoria constă dintr-un număr nbloc de blocuri (care trebuie determinat), fiecare bloc conţinând
p cuvinte. Un program executat la iniţializarea sistemului încearcă să acceseze ciclic primul cuvânt al fiecărui bloc;
tentativa de accesare a primului bloc inexistent în configuraţia curentă provoacă, prin deviere, părăsirea buclei.
Numărul de blocuri este egal cu numărul primului bloc neimplantat (blocurile fiind numerotate începând cu 0).
Realizarea prin program:
program principal:
begin
dev_nou:= <activ,master,mascat, adr măsurare>;
i:=1;
ciclu
<accesare cuvânt cu adresa i.p>;
i:=i+1;
endciclu;
ieşire ciclu: nbloc:=i;
dev_nou := <activ,master,mascat, adr tratare_deviere>;
end
procedura măsurare; -- apelare prin deviere
if cauză = adresă în afara memoriei then
csplucrare:= <activ,master,mascat, adr ieşire ciclu>;
încarcă_csp(csplucrare)
endif
La ieşirea din programul de măsurare a capacităţii memoriei se va restabili tratarea normală a devierilor
(procedura tratare_deviere).
Gestionarea devierilor de către utilizatorul sistemului. Reacţia standard la o deviere care are loc în timpul
execuţiei unui program utilizator este apelarea unei proceduri de sistem, care provoacă emiterea unui mesaj de eroare,
oprirea programului curent şi trecerea la programul următor.
Este de dorit ca fiecare utilizator să aibă posibilitatea să asocieze fiecărei cauze distincte de deviere i o procedură
proprie, tratarea căreia ar înlocui reacţia standard. Pentru a evita o buclă infinită, o deviere din aceeaşi cauză i în
interiorul acestei proceduri trebuie să apeleze procedura standard.
Asocierea unei proceduri proc de reluare a cauzei i de deviere se face cu ajutorul unui apel al supervizorului (SVC
asociere_deviere) cu parametrii i şi proc.
Pentru trecerea la programul următor se va apela o procedură a sistemului de operare, apel realizat prin încărcarea
unui cuvânt de stare, numit schimbare.
Realizarea prin program:
const ndev=<numărul de cauze de deviere>;
var C: array[0..ndev-1] of boolean;
adrtratare: array[0..ndev-1] of adresă;
procedura iniţializare;
begin
svc_nou:=<activ,master,mascat, adr tratare_svc >;
for i:=0 to ndev-1 do -- tratare standard
C[i]:=false;
endfor;
dev_nou:=<activ,master,mascat, adr tratare_dev >;
end
procedura tratare_svc;
begin
save(zonă);
case cod of
……
asociere_deviere: -- parametrii i, proc
C[i]:=true;
adrtratare[i]:=adr proc;
restabilire(zonă);
încarcă_csp(svc_vechi)
……
endcase
end
procedura tratare_dev(i);
begin
save(zonă);
if C[i] then
C[i]:=false; -- restabilire tratare standard
introdu_în_stivă(stivă_utilizator,dev_vechi.co); -- pentru retur
dev_vechi.co:= adrtratare[i];
restabilire(zonă);
încarcă_csp(dev_vechi);
else
<tratare standard[i]>;
încarcă_csp(schimbare); -- programul următor
endif
end
Programul de tratare a devierii, specificat de către utilizator se va executa în modul slave, iar returul se va face cu
ajutorul mecanismului standard de retur dintr-o procedură, deoarece adresa de retur a fost pusă în vârful stivei de
execuţie.
2.3.2. Exemple de utilizare a întreruperilor
Principala utilizare a întreruperilor este măsurarea timpului şi administrarea operaţiilor de intrare-ieşire.
Prezentăm mai jos câteva exemple de utilizare a ceasului calculatorului.
Un ceas de calculator este realizat cu ajutorul unui oscilator cu cuarţ, care emite impulsuri periodice. Perioada de
emitere, de obicei poate fi aleasă dintr-o plajă de valori prestabilită, de exemplu de la 0.1 până la 100 microsecunde.
Aceste impulsuri sunt utilizate pentru a decrementa valoarea unui contor, conţinut într-un registru special sau într-un
cuvânt de memorie, rezervat acestui scop. Când acest contor trece prin zero este declanşată o întrerupere, numită
întrerupere de ceas, care corespunde unui nivel rezervat acestui efect.
Pentru aplicaţiile care urmează vom presupune, că fiecare impuls decrementează cu 1 conţinutul unei locaţiuni
de memorie, notat ceas.
Limitarea timpului de execuţie a unei proceduri. Este necesar să fie executată o procedură p astfel, încât dacă
după un interval de timp prestabilit q, numit timeout, executarea nu se va termina, ea va fi întreruptă şi o procedură
specificată tratare_eroare este apelată.
Realizarea prin program:
procedura iniţializare;
intr_ceas_nou:=<activ,master,mascat,adr intr_ceas>;
svc_nou :=<activ,master,mascat,adr tratare_svc >;
dezarmare(intr_ceas);
procedura tratare_svc;
save(zonă);
case cod of

apel_lim_timp_ex: -- parametrii p, q, tratare_eroare
ceas:=q;
cspretur:=svc_vechi; -- salvare pentru retur
csplucrare:= <activ,slave,demascat,adr p>;
csperoare:= <activ,slave,demascat,adr tratare_eroare>;
armare(intr_ceas);
restabileşte(zonă);
încarcă_csp(csplucrare);

retur: -- datorat terminării procedurii p
dezarmare(intr_ceas);
încarcă_csp(cspretur);

endcase
procedura intr_ceas; -- expirarea timpului limită
dezarmare(intr_ceas);
încarcă_csp(csperoare);
Primul eveniment, care va avea loc (expirarea timpului q, rezervat executării, sau returul din p), dezarmează
ceasul şi activează procedura adecvată pasându-i contextul curent. Returul din p trebuie să se facă prin apelarea
supervizorului şi nu printr-o instrucţiune obişnuită de retur. La fel se va proceda şi cu returul din tratare_eroare, dacă
se cere revenirea la contextul apelării iniţiale a procedurii p.
Culegerea unor date de măsurare. Un calculator are obligaţiunea să preia periodic datele unor măsurări legate
de o instalaţie industrială. Perioada ceasului este de 5 μs. Datele trebuie culese fiecare 100 ms (perioada de
eşantionare). Deoarece timpul necesar culegerii datelor este cu mult inferior perioadei de eşantionare, calculatorul
poate executa alte lucrări în regim de fond.
Realizarea prin program:
const q = 20000 -- 100ms/5μs
procedura iniţializare;
begin
intr_ceas_nou:=<activ,master,mascat,adr intr_ceas>;
csplucrare:= <activ,slave,demascat,adr fond>;
cspmăsurare:= <activ,slave,demascat,adr măsurare>;
ceas:=q;
încarcă_csp(cspdev)
end
procedura intr_ceas;
begin
save(zonă); salvarea contextului de fond
cspsave:=intr_ceas_vechi; salvarea cuvântului de stare pentru fundal
ceas:=q;
încarcă_csp(cspmăsurare) programul de culegere a datelor
end
Programul de măsurare trebuie să se termine printr-o apelare a supervizorului SVC retur efectul căreia este de a
restabili contextul lucrului în fond. Acest apel execută secvenţa:
restabileşte (zonă);
încarcă_csp(cspsave);
Schema de mai jos ilustrează funcţionarea sistemului descris:

Măsurar Măsurar Măsurar Măsurar


F F F F

Iniţiali
100 100 100 100 100
ms
Remarcă. Putem include procedura măsurare direct în programul de tratare a întreruperii, care ar fi avut în
forma
save(zonă);
ceas:=q;
măsurare;
restabileşte(zonă);
încarcă_csp(intr_ceas_vechi)
Dar în acest caz programul de măsurare va trebui să fie executat în modul master şi întreruperile să fie mascate,
ceea ce nu ar permite modificarea sau înlocuirea lui de către un utilizator neprivilegiat. Această remarcă ilustrează un
principiu general, care cere îndeplinirea independentă (decuplată) a programelor de tratare a întreruperilor (secvenţe
de scurtă durată, executate cu privilegii înalte) şi programele activate de către procedurile de tratare a întreruperilor şi
apelate prin încărcarea unui cuvânt de stare (tratare primară şi secundară a întreruperii).
Administrarea lucrărilor în timp partajat. O mulţime de programe sau lucrări trebuiesc executate pe un
calculator, utilizând procesorul în regim de partajare a timpului: procesorul este alocat succesiv lucrărilor într-o ordine
prestabilită, pentru tranşe de timp de durată fixă q (cuanta de timp). Dacă o lucrare este încheiată înainte de expirarea
cuantei q, procesorul este imediat alocat lucrării următoare.
Lucrările sunt numerotate de la 0 la n-1. Este definită o zonă de amplasare a cuvântului de stare a procesorului
csp[i] pentru fiecare lucrare i şi o altă zonă reg[i] pentru registrele acesteia. Aceste zone permit salvarea contextului
procesorului de fiecare dată când o lucrare este întreruptă pentru a permite reluarea ulterioară a lucrării întrerupte.
Realizarea prin program:
Pentru început, presupunem că lucrările nu se vor termina niciodată. Vom utiliza un fir circular de lucrări, notat
fir_lucr şi gestionat de trei proceduri intrare, ieşire şi urm.
procedura iniţializare;
begin
for i:=0 to n-1 do
csp[i] := <activ,slave,demascat,adr lucrare[i]>;
intrare(i, fir_lucr);
endfor
intr_ceas_nou:=<activ,master,mascat,adr intr_ceas>;
svc_nou:=<activ,master,mascat,adr trattare_svc>;
lucr_ales:=0; -- începem cu lucrarea 0
încarcă_csp(csp[lucr_ales]);
end
procedura intr_ceas;
begin
csp[lucr_ales]:=intr_ceas_vechi;
save_registre(reg[lucr_ales]);
lucr_ales:=urm(lucr_ales);
ceas:=q;
încarcă_registre(reg[lucr_ales]);
încarcă_csp(csp[lucr_ales]);
end
Pentru a lua în consideraţie terminarea lucrărilor, fiecare lucrare trebuie să înştiinţeze sistemul atunci când
execuţia sa a luat sfârşit printr-un apel al supervizorului SVC end după cum este prezentat mai jos.
procedura tratare_svc;

case cod of

term:
suc:=urm(lucr_ales);
ieşire(lucr_ales, fir_lucr);
lucr_ales:=suc;
ceas:=q;
încarcă_registre(reg[lucr_ales]);
încarcă_csp(csp[lucr_ales]);
endcase
Am presupus că firul de lucrări nu va deveni niciodată vid. În practică aceasta poate fi obţinut prin trecerea la o
lucrare de fond, care poate eventual fi redusă la o buclă.
2.3.3. Concluzii
Exemplele precedente ilustrează modurile elementare de comunicare dintre programele unui sistem de operare şi
mediul exterior. Programele sistemului sunt activate:
1) fie de către programul utilizatorului, care semnalează o anomalie (deviere) sau cere achitarea unui serviciu
(apelarea supervizorului),
2) fie de către un dispozitiv extern (ceas, proces comandat, organ periferic sau linie de comunicaţii), care
semnalează producerea unui eveniment şi care cere o reacţie imediată a sistemului.
Ca şi rezultat putem afirma că un sistem de operare este pilotat de evenimente (event-driven, eng.). Notăm de
asemenea, că pentru comunicarea internă între programele unui sistem se foloseşte apelarea supervizorului dacă un
anumit tip de protecţie trebuie să fie activat (program executat în modul slave, de exemplu) şi apelarea unei proceduri
obişnuite în caz contrar. O secvenţă de execuţie a unui program de sistem se termină prin încărcarea unui cuvânt de
stare a procesorului care permite controlul total al contextului restabilit.

1.7. Programarea operaţiilor de intrare-ieşire


Prin noţiunea de intrare-ieşire (prescurtat I/E; eng. input/output, I/O; fr. entrée-sortie, E/S) numim orice transfer
de informaţii din sau spre nucleul calculatorului. Operaţiile de I/E semnifică:
 transferurile de informaţii dintre diferite nivele ierarhice ale memoriei,
 transferurile de informaţii din sau spre mediul exterior (organe periferice locale sau la distanţă, captoare sau
dispozitive de acţionare, alte calculatoare, etc.).
2.4.1. Organizarea generală
2.4.1.1. Periferice, controlere, canale
Un organ de intrare-ieşire este un dispozitiv capabil să transfere informaţii între procesorul sau memoria
calculatorului şi un suport extern de informaţie. Acest transfer este comandat de către procesorul central. În cel mai
simplu caz, o instrucţiune specială a procesorului permite transferarea informaţiei între suportul extern şi un registru
al procesorului, care va fi deci ocupat pe toată perioadă transferului informaţiei. Odată cu evoluţia calculatoarelor, în
scopul unei mai bune utilizări a procesorului s-a ajuns la necesitatea acordării unei autonomii organelor de intrare-
ieşire încredinţându-le funcţii tot mai complicate de înlănţuire şi comandă, procesorului central lăsându-i-se doar
iniţiativa de lansare şi de control a operaţiilor. Din considerente economice mai apoi s-a trecut la separarea
dispozitivelor de comandă a perifericelor de perifericele propriu-zise, pentru ca dispozitivele de comandă să poată fi
partajate între mai multe periferice.
Câteva scheme de organizare a perifericelor sunt prezentate în fig.2.5. Schema (c) este proprie unui calculator de
putere mare, (a) şi (b) corespund configuraţiilor calculatoarelor de putere mai mică. Precizăm funcţiile organelor
reprezentate în această figură.
1) Un canal (sau unitate de schimb) este un procesor specializat în operaţiile de intrare-ieşire. El poate fi lansat
doar de un procesor central, nu posedă întreruperi, dar poate întrerupe un procesor central. Setul de
instrucţiuni ale canalului îi permite să acţioneze controlerele şi perifericele, care-i sunt conectate. Mini- şi
microcalculatoarele pot poseda organe, numite Unităţi de acces direct la memorie (ADM), care sunt nişte
canale simplificate.
2) Un contróler este un dispozitiv de comandă adaptat la un tip concret de echipament periferic. Autonomia sa
este limitată de operaţii foarte elementare. Destinaţia principală a unui controler este de a permite conectarea
a mai multor periferice de acelaşi tip la un singur controler. Un singur dispozitiv periferic poate transmite
informaţii prin intermediul controlerului la un moment de timp dat. În acelaşi timp, este posibilă executarea
simultană a unor operaţii pe alte periferice, operaţii care nu implică transferul de informaţii (rebobinarea unei
bande magnetice, deplasarea braţului unui disc, etc.). Partajarea funcţiilor între periferic şi controler depinde
de tipul perifericului. De obicei, funcţiile logice (înlănţuirea şi sincronizarea operaţiilor, emiterea unor
semnale în caz de accident sau terminarea normală a execuţiei) sunt încredinţate controlerului, iar funcţiile
fizice (transferul propriu-zis) - perifericului. În programarea intrărilor-ieşirilor nu este necesar, de obicei, să
se facă o deosebire între un controler şi un periferic.
3) Un periferic este un organ capabil să transfere informaţii din sau spre un suport extern. Controlerul este legat
de periferic printr-o interfaţă, care conţine un set de funcţii (intrare, ieşire, semnalizări de comandă şi de
accident) şi o linie de comunicaţie, care serveşte la transferul informaţiilor.

Magistrală Magistrală
UC UC

 Cp Cp A
 M M DM


p p C C
 p p
 p p

 ( (


 UC UC

Magistrală












 Un canal poate comanda un singur dispozitiv periferic cu debit ridicat (disc, de ex.) sau poate fi multiplexat
pentru mai multe periferice cu debitul mai mic. Încercarea mai multor procesoare (unităţi centrale sau canale) de a
accesa simultan memoria operativă poate genera conflicte. Conflictele sunt reglate de către dispozitivul hardware de
accesare, care impune o ordine de acces conform unor priorităţi prestabilite. Canalele au prioritate faţă de unităţile
centrale, deoarece ele trebuie să reacţioneze cât se poate de rapid la evenimentele externe.
 2.4.1.2. Adresarea dispozitivelor periferice
 Diferite organe sunt desemnate, la fiecare nivel, printr-o adresă care permite să evidenţiem:
 canale legate de memorie,
 controlere ataşate fiecărui canal,
 dispozitive periferice ataşate fiecărui controler.
 La un nivel anume adresa este un simplu număr de ordine. Un periferic este desemnat printr-o adresă compusă
 <numărul canalului, numărul controlerului, numărul dispozitivului periferic>.
 Un controler poate fi conectat la mai multe canale, iar un periferic la mai multe controlere, care vor fi,
obligator, de acelaşi tip. Astfel, un periferic are mai multe căi de acces la memorie (doar una singură poate fi activă la
un moment de timp dat) şi poate fi desemnat prin mai multe adrese (fig.2.6).

p 0
 0 00
Controle
 r
0 1 p 0
 0
Canal
0 01
0
 1 0 01
p
Controle 0

0 r 1 10
 1 01, 01
Canal p
10 1
 1 0
10
 Controle p 1
r 1
 1 11
 1
Fig.2.6. Adresarea perifericelor

 Această organizare permite ameliorarea performanţelor şi disponibilităţii sistemului. Existenţa mai multor căi
de acces la un periferic diminuează riscul unor indisponibilităţi din cauza saturaţiei sau ieşirii din funcţiune a unui
canal sau controler.
 În cazul unor calculatoare, cum ar fi de exemplu DEC, adresarea perifericelor utilizează amplasamente de
memorie rezervate, care sunt asociate cuvântului de stare sau registrului de date a unui controler specificat. Accesarea
cuvântului de stare sau a registrului de date este realizată prin intermediul instrucţiunilor obişnuite de accesare a
memoriei. Această schemă simplifică desemnarea perifericelor şi programarea intrărilor-ieşirilor.
2.4.2. Metode de comandă a perifericelor
 Programul, care controlează funcţionarea elementară a unui dispozitiv periferic se numeşte driver. Driverul
gestionează în mod direct interfaţa controlerului perifericului, tratează întreruperile generate de acesta, detectează şi
tratează cazurile de eroare. El este, de obicei, invizibil unui utilizator obişnuit al sistemului, care apelează funcţii de
intrare-ieşire prin intermediul unor servicii de înalt nivel, realizate prin apelări ale supervizorului. Vom prezenta mai
jos modurile principale de control ale perifericelor şi primitivele elementare de intrare-ieşire.
 2.4.2.1. Intrări-ieşiri sincrone
 În acest caz nu există nici un fel de paralelism între procesare şi transferul informaţiilor. Procesorul este ocupat
pentru toată durata transferului.
 Schema care urmează nu descrie doar un caz particular, ci este reprezentativă pentru organizarea intrărilor-
ieşirilor sincrone pentru mai multe tipuri de calculatoare.
1) Specificarea mecanismului
 Operaţia de bază constă în transferarea unor informaţii elementare de volum fix între un amplasament de
memorie şi un dispozitiv periferic. Presupunem că volumul informaţiei transmise, care depinde de perifericul
considerat, este un octet. Starea ansamblului controler-periferic este definită printr-un cuvânt de stare, aflat în
controler. Acest cuvânt de stare conţine o mulţime de indicatori booleeni, dintre care doar 3 ne vor interesa aici:
 preg : perifericul este pregătit (gata) de funcţionare,
 term : transferul este terminat; perifericul este gata să transfere un nou caracter,
 err : în cursul transferului a avut loc o eroare; natura ei este precizată de un cod care face parte din
cuvântul de stare.
 Controlerul este comandat de unitatea centrală cu ajutorul a trei instrucţiuni de intrare-ieşire, efectul cărora
este descris mai jos:
 IN(adr_mem, adr_perif) : cerere de transfer tip intoducere a unui octet,
 OUT(adr_mem, adr_perif) : cerere de transfer tip extragere a unui octet,
 TEST(adr_perif) : copie cuvântul de stare într-un registru cu scopul de a consulta indicatorii.
 Prin adr_mem şi adr_perif sunt desemnate adresele amplasamentului memoriei şi perifericului.
2) Programul driver-ului
 Fie că vrem să extragem o secvenţă de n caractere care se află într-un masiv T[0..n-1]. Caracterul T[i], dacă
i>0, poate fi extras doar dacă a fost transferat T[i-1]. Pentru a verifica această condiţie se testează iterativ (aşteptare
activă) indicatorul term.
 iniţializare : term:=false;
 TEST(adr_perif);
 if preg = false then // perifiericul nu este pregătit
 <tratare eroare>
 endif;
 for i:=0 to n-1 do
 OUT(adr T[i], adr_perif);
 ciclu : TEST(adr_perif);
 if err = true then
 <tratare eroare>
 endif;
 if term = false then goto ciclu
 endif;
 endfor
 Intrările-ieşirile sincrone sunt folosite la microprocesoarele cele mai simple sau atunci când procesorul nu
poate fi exploatat în mod util în timpul transferului (înregistrări de măsurare, încărcarea iniţială a unui sistem).
 2.4.2.2. Intrări-ieşiri asincrone cu întreruperi
1) Specificarea mecanismului.
 Terminarea procesului de transferare a caracterului T[i] este semnalizată printr-o întrerupere. Dacă acest
caracter nu este ultimul (i<n-1), următorul caracter poate fi transmis. Procesul este declanşat de transferul primului
caracter. Acest transfer este cerut de către programul principal prin intermediul unui apel a supervizorului (SVC intrare
sau ieşire), care are drept parametri:
 adresa perifericului,
 numărul de caractere care vor fi transferate,
 adresa de origine sau de destinaţie a primului caracter,
 booleanul term, valoarea TRUE a căruia semnalizează sfârşitul transferului.
 Programul principal programul de tratare a întreruperii
 term:=FALSE (la părăsirea lui T[i])
 SVC i:=0;
 ieşire OUT(adr T[i], adr_perif);
 save context;
 TEST(adr_perif);
 … if err then
 <tratare eroare>
 else
 if i<n-1 then
 <tratare paralelă> i:=i+1;
 OUT(adr T[i], adr_perif)
 … else
 term:=TRUE
 if term then endif
 … endif;
 <restabilire context>

 2.4.2.3. Intrări-ieşiri programate pe canal sau acces direct la memorie


 Un canal este un procesor capabil să execute o secvenţă de operaţii de intrare-ieşire, definite de un program
din memorie, pentru un periferic conectat la acest canal. El conţine un cuvânt de stare în care este inclus un contor
ordinal ca şi un set de indicatori despre starea operaţiei curente. Programul unui canal este compus dintr-o suită de
instrucţiuni, fiecare specificând o operaţie elementară de intrare-ieşire. Acest program trebuie să fie pregătit anticipat
de procesorul central. Principiul de funcţionare a unui canal este următorul:
1) Un canal poate fi lansat doar de un procesor central cu ajutorul unei instrucţiuni SIO (Start Input-Output,
eng.). Aceasta iniţializează cuvântul de stare a canalului, specificând adresa primei instrucţiuni care trebuie
executată şi adresa perifericului origine sau destinaţie a transferului. După aceasta, activitatea canalului şi a
procesorului central decurg în mod paralel.
2) Odată lansat, canalul execută programul său. Această execuţie se poate termina în trei moduri:
a) terminarea execuţiei programului,
b) terminare cauzată de o eroare (pană hardware, program incorect, etc.),
c) la comanda unui procesor central, care poate în orice moment stopa execuţia programului canalului cu
ajutorul unei instrucţiuni speciale stop_canal (HIO, Halt Input-Output, eng.).
 Cazul a) este cel al funcţionării normale. Procesorul central este informat despre terminarea executării
programului canalului prin intermediul unei întreruperi. Toate datele necesare derulării intrării-ieşirii sunt prezente în
cuvântul de stare a canalului, care poate fi consultat de către procesorul central cu ajutorul unei instrucţiuni
testare_canal (adesea notată prin TIO, eng. Test Input Output). În cazul b) o întrerupere oricum are loc, cu excepţia,
evidentă, a cazului în care însuşi dispozitivul întreruperii a fost afectat. Cuvântul de stare a canalului conţine toate
informaţiile necesare despre cauza opririi. Pentru o oprire de urgenţă, dacă procesorul central detectează o situaţie
anormală, este folosită instrucţiunea stop_canal (cazul c).
3) Procesorul central, la orice moment de timp, poate consulta cu ajutorul instrucţiunii testare_canal starea
canalului. Această consultare nu împiedică executarea programului procesorului central. Starea obţinută este starea
corespunzătoare ultimului punct observabil.
 Programul unui canal reflectă specializarea acestuia în operaţii de intrare-ieşire. O instrucţiune a canalului
specifică următoarele:
 natura operaţiei care va fi executată (citire, scriere, deplasarea braţului unui disc, etc.),
 adresa de început a zonei de memorie origine sau destinaţie,
 numărul caracterelor care urmează a fi transmise.
 Există o instrucţiune, care permite un salt condiţionat în cadrul programului. Pot fi de asemenea programate
iteraţii.
 Exemplul 2.8. Canalele calculatoarelor IBM 370. Instrucţiunile canalului conţin informaţiile menţionate
mai sus la care sunt adăugaţi trei indicatori:
 un bit de înlănţuire a instrucţiunilor. Dacă acest bit este pus în 1, la terminarea instrucţiunii
curente se va executa instrucţiunea următoare, în caz contrar execuţia se opreşte,
 un bit de înlănţuire a datelor. Dacă acest bit este pus în 1, canalul execută, la terminarea
instrucţiunii curente, o instrucţiune, care are acelaşi cod al operaţiei şi este ataşată aceluiaşi
periferic, ca şi instrucţiunea precedentă, dar care utilizează o zonă de memorie, specificată
în instrucţiunea următoare. Aceasta permite executarea unui transfer spre sau dintr-o zonă
non-contiguă de memorie,
 un bit de cerere a unei întreruperi. Dacă valoarea acestui indicator este 1, o întrerupere are
loc atunci când canalul începe execuţia instrucţiunii date. Aceasta permite culegerea unor
date statistice.
 Un canal este lansat cu ajutorul unei instrucţiuni SIO care specifică adresa perifericului. În
prealabil adresa primei instrucţiuni, care va fi executată şi cheia de protecţie utilizată în timpul
transferului, trebuiesc plasate într-o locaţiune rezervată de memorie.◄
 Mai sus au fost prezentate doar aspectele esenţiale ale funcţionării unui canal. Există însă câteva mecanisme,
care complică în mod substanţial descrierea unui sistem real de intrare-ieşire:
 multiplexarea canalelor şi a contolerelor. Suntem impuşi să ştim a determina imediat momentul de timp
în care canalul sau controlerul devin disponibile, pentru a le utiliza în mod eficient. Pentru aceasta sunt folosiţi
indicatori şi întreruperi speciale,
 necesitatea de a ţine cont de caracteristicile diverselor periferice, ceea ce complică în mod substanţial
relaţiile de ordonanţare,
 detectarea şi tratarea erorilor, cărora sunt consacrate majoritatea programelor unui administrator al
intrărilor-ieşirilor.
 Un ADM este o formă simplificată a unui canal: "programul" unui ADM conţine o instrucţiune unică, care
specifică o adresă în memorie, o adresă de periferic, numărul octeţilor care vor fi transferaţi, sensul transferului şi
semnalul terminării transferului (să se ceară sau nu o întrerupere). Acest program nu este plasat în memorie, ci într-
un registru al ADM, utilizat ca şi cuvânt de stare, care poate fi consultat în timpul transferului pentru a duce evidenţa
octeţilor rămaşi pentru a fi transmişi.
2.4.3. Intrări-ieşiri buferizate în memorie
 2.4.3.1. Specificarea problemei
 Diferenţa considerabilă dintre viteza de lucru a unităţii centrale şi cea a organelor periferice impune
"buferizarea" intrărilor-ieşirilor, adică introducerea unei zone de memorie-tampon între periferic şi programul
utilizatorului. Scopul este de a reduce timpul de inactivitate a unităţii centrale, dezlegând funcţionarea acesteia de
periferice. Programul utilizatorului va transfera informaţiile din sau spre zona-tampon, iar această zonă-tampon va
servi, în mod paralel, drept sursă sau destinaţie la schimbul de date cu perifericul. Dezlegarea unităţii centrale de
periferic este cu atât mai bine realizată cu cât este mai mare capacitatea zonei-tampon. Adesea, pentru a nu
supraîncărca memoria principală, zona-tampon este situată pe discul fix.
 Pentru descrierea modelului sunt necesare următoarele specificaţii:
 schimburile de informaţii cu perifericele se va face prin înregistrări de lungime fixă,
 zona-tampon este de volum fix egal cu N înregistrări,
 pentru un program utilizator, schimbul de informaţii cu zona-tampon trebuie să simuleze schimbul cu
perifericul, drept consecinţă, ordinea de depozitare sau de extragere a informaţiilor trebuie să fie bine stabilită
şi nici o înregistrare să nu fie pierdută,
 aşteptările inutile trebuie reduse la minimum, atât în cazul unităţii centrale, cât şi a perifericului,
 capacitatea suporturilor nu este limitată: în cazul unei operaţii de citire organul de intrare va fi totdeauna
alimentat; în cazul unei operaţii de scriere, organul de ieşire poate accepta întotdeauna o înregistrare,
 transferul se va efectua fără eroare.
 2.4.3.2. Descrierea algoritmilor
1) Citire
 Un program de citire este o procedură citire(înregistrare), care prezintă drept rezultat valoarea înregistrării
citite. Această procedură, realizată printr-o apelare a supervizorului, are ca efect citirea din zona-tampon a înregistrării
următoare. Citirea este posibilă doar dacă zona-tampon conţine cel puţin o înregistrare, care nu a fost încă citită. În
caz contrar, activitatea apelantă trece în starea de aşteptare.
 În ambele cazuri, continuitatea alimentării zonei-tampon este asigurată de introducerea unei înregistrări de la
perifericul de intrare, dacă un transfer nu are deja loc. Terminarea transferului unei înregistrări în zona-tampon
provoacă o întrerupere. Pentru a menţine perifericul în stare activă, programul de tratare asociat acestei întreruperi
trebuie să relanseze transferul următor, dacă mai există loc în zona-tampon. Plus la aceasta, dacă activitatea apelantă
era în aşteptarea sosirii unei înregistrări, ea trebuie relansată.
 Acest mod de funcţionare poate fi schematic prezentat conform schemei programului de mai jos.

programul
 periferic
de intrare citire



 Zonă-tampon cu N

 procedura citire(înreg) tratare întrerupere
 (apelare supervizor)
 if tamponul_nu este_vid then if tamponul_nu este_plin then
 extragere(înreg, tampon); lansare_transfer;
 lansare_transfer în caz de necesitate; if aşteptare then // aşteptare pentru citire
 else reluare citire
 lansare_transfer în caz de necesitate; endif
 aşteptare
 endif
 În acest program "în caz de necesitate" semnifică "dacă un transfer nu este deja în curs". Sistemul este
iniţializat de citirea primei înregistrări, care provoacă transferuri succesive până la umplerea zonei-tampon.
2) Scriere
 Schema care corespunde unei scrieri se obţine din precedenta, înlocuind după cum urmează:
 citire prin scriere
 extragere prin introducere
 tampon_vid prin tampon_plin
 tampon_plin prin tampon_vid
 programul
periferic
 de ieşire scriere



 Zonă-tampon cu N

 procedura scriere(înreg) tratare întrerupere


 (apelare supervizor)
 if tamponul_nu este_plin then if tamponul_nu este_vid then
 introducere(înreg, tampon); lansare_transfer;
 lansare_transfer în caz de necesitate; if aşteptare then
 else reluare scriere
 lansare_transfer în caz de necesitate; endif
 aşteptare
 endif
3) Programe
 Programele sunt o transcriere a schemelor de mai sus. Zona-tampon este un fir de aşteptare, gestionat circular
cu ajutorul a doi pointeri: tcitire (top-ul FA) - indică următoarea înregistrare care urmează a fi preluată, qcitire (coada
FA) - indică amplasamentul unde se va introduce următoarea înregistrare. Funcţia suc realizează înlănţuirea
amplasamentelor; o realizare simplă pentru o zonă-tampon secvenţială, care are N înregistrări a câte L amplasamente
este de forma:
 suc(ptr) = (ptr + L) mod (L*N)
 Vom presupune, că activitatea care cere operaţia citire este unica care va utiliza procesorul; trecerea acesteia
în starea de aşteptare se va realiza trecând în starea de aşteptare procesorul. Această soluţie este impracticabilă în cazul
unor activităţi multiple (este necesar de a relansa o altă activitate), dar o vom accepta din motive de simplitate.
 Mai subliniem că transferul este comandat de un canal sau un ADM, fără a detalia programul respectiv,
pregătit de obicei anticipat; numai valorile parametrilor se vor schimba de la o execuţie la alta. Procedurile
lansare_citire şi lansare_imprimare realizează pregătirea programului şi lansarea transferului.
 programul
periferic utilizator (SVC)
de intrare citire

Zonă-






 Variabile  Semnificaţie  Valoare iniţială
 L  lungimea unei casete (înregistrări)  const.
 Ncitire  numărul de casete a zonei-tampon  const
 nvcitire  numărul de casete vide  Ncitire
 citire_activ  indicator al activităţii perifericului de intrare  false
 tcitire, qcitire  pointeri top şi coadă  0, 0
 suc()  pointer spre caseta următoare  …
  
 citire(înreg)   tratare_întrerupere_perif_de_intrare
 save(zonă_svc); save(zonă_întrerupere);
 început: qcitire:=suc(qcitire);
 if nvcitire<Ncitire then nvcitire:=nvcitire-1;
 înreg:=tampon[tcitire]; if nvcitire>0 then
 tcitire:=suc(tcitire); lansare_perif_de_intrare(qcitire)
 nvcitire:=nvcitire+1; else
 if non citire_activ then citire_activ:=false
 citire_activ:=true; endif;
 lansare_perif_de_intrare(qcitire) if aşteptare_citire then
 endif întrerupere_veche.act:=activ;
 else aşteptare_citire:=false
 if non citire_activ then endif;
 citire_activ:=true; restabilire(zonă_întrerupere);
 lansare_perif_de_intrare(qcitire) încărcare_csp(întrerupere_veche);
 endif;
csplucrare:=<aşteptare,slave,demascat, adr început>; lansare_perif_de_intrare(qcitire)
aşteptare_citire:=true; elaborare prog.canal:
încărcare_csp(csplucrare); <sens=citire
endif; adr.memorie=adr tampon[qcitire]
restabilire(zonă_svc); contor_octeţi=L>
încărcare_csp(svc_vechi); SIO
<prog=prog.canal
perif= perif_de_intrare>
 Execuţia programelor de mai sus ne impun să facem o remarcă foarte importantă. Trebuie să notăm, că
executarea tratării întreruperii este declanşată de sfârşitul transferului fizic al unei înregistrări, activitate, care are loc
paralel cu executarea programului primitivei citire(înreg) pe procesor. Drept rezultat, este posibil ca întreruperea să
survină într-un punct oarecare (observabil) al executării acestei primitive, ceea ce va însemna intercalarea executării
programului de tratare a întreruperii între două puncte oarecare ale programului citire(înreg). Consecinţele pot fi
catastrofale, cum ne arată exemplul următor.
 Să considerăm instrucţiunea a doua
 if non citire_activ then
 citire_activ:=true;
 lansare_perif_de_intrare(qcitire)
 endif
 din programul citire(înreg). Ea se descompune în următoarele instrucţiuni elementare:
(a) load R, citire_activ
(b) jifz R, urm -- ramificare dacă true
(c) …
 urm: …
 <aşteptare>
 Presupunem că întreruperea soseşte între instrucţiunile (a) şi (b), valoarea citită în R de (a) este true; programul
de tratare a întreruperii pune citire_activ în false. După prelucrarea întreruperii, instrucţiunea (b) este executată cu R
având valoarea true şi activitatea apelantă trece în starea de aşteptare în care rămâne un timp indefinit, deoarece
perifericul nu a fost relansat. Pot exista şi alte cazuri de funcţionare incorectă, legată de momentul sosirii unei
întreruperi.
 Putem afirma, că validitatea variabilelor comune (de ex. citire_activ, nvcitire etc.,) nu poate fi garantată dacă
autorizăm sosirea întreruperilor de terminare a operaţiilor de intrare-ieşire în orice moment de timp. Apelarea
supervizorului pentru a realiza secvenţa de cod citire_înreg trebuie executată cu întreruperile de intrare-ieşire
mascate. Situaţia dată este cunoscută sub numele de excludere mutuală şi este o consecinţă a execuţiei activităţilor
asincrone.
 Programele pentru un periferic de ieşire (imprimare, de ex.) sunt analogice. Remarca despre mascarea
întreruperilor rămâne valabilă.


programul
 utilizator (SVC)
imprima
scriere
 ntă

 Zonă-

 tim qi

 Variabile  Semnificaţie  Valoare iniţială
 L  lungimea unei casete (înregistrări)  const.
 Nimp  numărul de casete a zonei-tampon  const
 nvimp  numărul de casete vide  Nimp
 imp_activ  indicator al activităţii perifericului de intrare  false
 timp, qimp  pointeri top şi coadă  0, 0
 suc()  pointer spre caseta următoare  …
  
 scriere(înreg)   tratare_întrerupere_imp
 save(zonă_svc); save(zonă_întrerupere);
 început: qimp:=suc(qimp);
 if nvimp>0 then nvimp:=nvimp+1;
 tampon[timp]:= înreg; if nvimp<Nimp then
 timp:=suc(timp); lansare_imp(qimp)
 nvimp:=nvimp-1; else
 if non imp_activ then imp_activ:=false
 imp_activ:=true; endif;
 lansare_imp(qimp) if aşteptare_imp then
 endif întrerupere_veche.act:=activ;
 else aşteptare_imp:=false
 if non imp_activ then endif;
 imp_activ:=true; restabilire(zonă_întrerupere);
 lansare_imp(qimp) încărcare_csp(întrerupere_veche);
 endif;
csplucrare:= :=<aşteptare,slave,demascat, adr început>; lansare_imp(qimp)
aşteptare_imp:=true; elaborare prog.canal:
încărcare_csp(csplucrare); <sens=ieşire
endif; adr.memorie=adr tampon[qimp]
restabilire(zonă_svc); contor_octeţi=L>
încărcare_csp(svc_vechi); SIO
<prog=prog.canal
perif= imprimantă>
2.4.4. Încărcarea iniţială
 Pentru a începe lucrul maşina "goală" trebuie să aibă încărcat în memoria operativă programul sistemului de
operare şi să execute un salt la prima instrucţiune a acestui program. Aceasta presupune prezenţa în memorie a unui
program de citire, care el însuşi trebuie încărcat în prealabil. La primele calculatoare, un program de câteva
instrucţiuni, introdus manual în memorie cu ajutorul unor comutatoare ale pupitrului de comandă la o adresă fixă,
permitea încărcarea unui program de citire mai elevat capabil să citească versiunea completă a sistemului de operare.
Acest proces era numit încărcare (bootstrapping, eng.).
 În calculatoarele contemporane, programul de iniţializare se află permanent într-o memorie constantă (BIOS).
Execuţia acestui program este declanşată odată cu punerea sub tensiune a calculatorului (autoîncărcare).

Exerciţii
 Exerciţiul 2.1.
 Elaboraţi schema unui program de intrare-ieşire cu tratare a întreruperilor conform schemei date în 2.4.2.2. cu
modificările următoare:
a) în cazul detectării unei erori de transfer, se va încerca repetarea operaţiei până la un număr maxim de încercări
nmax, înainte de a anula intrarea-ieşirea,
b) dacă transferul nu a fost realizat într-o perioadă de timp tmax specificată, să se anuleze operaţiile de intrare-
ieşire.
 Pentru ambele cazuri să se apeleze o procedură specificată de tratare a erorii.
 Exerciţiul 2.2.
 Un calculator este utilizat pentru culegerea unor date de măsurare. El execută periodic (cu perioada T) un ciclu
de activităţi:
 culegerea rezultatelor de măsurare de la captoare (durata tm)
 procesarea rezultatelor de măsurare (durata tp)
 vidajul pe disc al rezultatelor (durata tv)
 Toate cele trei operaţii de mai sus trebuie să fie executate consecutiv pentru fiecare ciclu. Există un ceas, care
provoacă o întrerupere la trecerea unui contor prin zero. Să se elaboreze programele acestui sistem (programul
principal, programul de tratare a întreruperilor) pentru următoarele trei cazuri (formulând pentru fiecare caz, dacă va
avea loc, condiţia posibilităţii tratării în funcţie de durata diferitor operaţii).
a) Captoarele şi controlerul discului sunt comandate de unitatea centrală. Ciclurile succesive de măsurare
trebuie executate consecutiv şi un task de fond este executat pentru perioada timpului mort.
b) Presupunem că tm+tp < T < tm+tp+tv. Vidajul ciclului i trebuie efectuat paralel cu preluarea datelor
ciclului i+1. Se presupune că discul este comandat de ADM.
c) Presupunem că tm < T < tm+tp. Trebuie executat în paralel şi preluarea datelor şi vidajul pentru ciclurile
consecutive.
 Exerciţiul 2.3.
 O instrucţiune citire_poziţie(x,y) permite citirea poziţiei curente pe ecran a mouse-ului, definită prin
coordonatele sale carteziene x şi y. O altă instrucţiune init_mouse permite să definim poziţia curentă a mouse-ului
drept origine a sistemului de coordonate.
 Să se elaboreze un program, care va permite urmărirea deplasării mouse-ului pe ecran.
a) Admitem că viteza maximă de deplasare a mouse-ului este de 10 cm/s. Care este frecvenţa de preluare a
coordonatelor dacă se cere să localizăm poziţia mouse-ului definită printr-un pixel pe un ecran de 30x30 cm
şi care conţine 1024x1024 pixeli?
b) Mouse-ul este gestionat de un program activat periodic, cu perioada T aleasă în funcţie de rezultatul obţinut.
 a) Să se elaboreze acest program presupunând disponibile două proceduri:
 afişare_cursor(x,y), care afişează un cursor centrat pe x, y,
 deplsare_cursor(dx,dy), care deplasează cursorul, presupus afişat, la distanţa dictată de vectorul (dx,dy).
 Avem la dispoziţie un ceas cu întrerupere la trecerea prin zero.
3. GESTIONAREA ACTIVITĂŢILOR PARALELE ........................................................................................... 50
3.1. EXEMPLE INTRODUCTIVE ................................................................................................................................... 50
3.1.1. Administrarea tamponată a intrărilor-ieşirelor ........................................................................................ 50
3.1.2. Comanda unui proces industrial ................................................................................................................ 51
3.2. NOŢIUNE DE PROCES SECVENŢIAL ..................................................................................................................... 52
3.2.1. Proces unic. Context .................................................................................................................................. 52
3.2.2. Relaţii între procese ................................................................................................................................... 53
3.2.2.1. Mulţimi de procese. Paralelism............................................................................................................................. 53
3.2.2.2. Concurenţa proceselor. Resurse virtuale ............................................................................................................... 54
3.2.2.3. Excludere mutuală ................................................................................................................................................ 55
3.3. SINCRONIZAREA PROCESELOR ........................................................................................................................... 55
3.3.1. Exprimarea şi implementarea restricţiilor de precedare ........................................................................... 55
3.3.2. Probleme de realizare a sincronizării ....................................................................................................... 56
3.3.3. Monitorul – mecanism de sincronizare...................................................................................................... 57
3.3.3.1. Definiţii................................................................................................................................................................. 57
3.3.3.2. Exemple de utilizare ............................................................................................................................................. 58
3.4. IMPLEMENTAREA SINCRONIZĂRII....................................................................................................................... 59
3.4.1. Probleme-tip .............................................................................................................................................. 59
3.4.2. Administrarea unei resurse partajate ........................................................................................................ 59
3.4.2.1. Alocarea resurselor banalizate .............................................................................................................................. 60
3.4.2.2. Modelul cititorului şi redactorului ........................................................................................................................ 62
3.4.3. Comunicarea între procese ........................................................................................................................ 64
3.4.3.1. Modelul producătorului şi consumatorului ........................................................................................................... 64
3.4.3.2. Primitive de comunicare ....................................................................................................................................... 65
3.4.3.3. Aplicaţii : relaţia client-server............................................................................................................................... 67
3.4.4. Administrarea intrărilor-ieşirilor .............................................................................................................. 68
3.4.4.1. Administrarea unui periferic ................................................................................................................................. 68
3.4.4.2. Buferizarea imprimării .......................................................................................................................................... 69
3.4.5. Sincronizare temporală ............................................................................................................................. 70
3.5. GESTIONAREA DINAMICĂ A PROCESELOR .......................................................................................................... 72
3.6. SINCRONIZAREA ÎN WINDOWS ........................................................................................................................... 73
3.6.1. Procese şi fire ............................................................................................................................................ 73
3.6.2. Necesitatea sincronizării ........................................................................................................................... 74
3.6.3. Structura mecanismului de sincronizare în Windows ................................................................................ 74
3.6.4. Administrarea obiectelor de sincronizare în Windows .............................................................................. 75
3.6.4.1. Excluderea mutuală............................................................................................................................................... 75
3.6.4.2. Evenimentele ........................................................................................................................................................ 75
3.6.4.3. Semafoarele .......................................................................................................................................................... 75
3.6.4.4. Secţiunile critice ................................................................................................................................................... 75
3.6.4.5. Protejarea accesării variabilelor ............................................................................................................................ 76
3.6.4.6. Sincronizarea în MFC ........................................................................................................................................... 76
3.6.5. Exemplu de sincronizare în Windows ........................................................................................................ 76
3.6.6. Utilizarea secţiunilor critice în Windows .................................................................................................. 77
3.6.6.1. Structura RTL_CRITICAL_SECTION ................................................................................................................ 78
3.6.6.2. Funcţii API pentru secţiunile critice ..................................................................................................................... 80
3.6.6.3. Clase de secţiuni critice ........................................................................................................................................ 83
3.6.6.4. Depanarea secţiunilor critice ................................................................................................................................. 84
3.7. EXERCIŢII LA CAPITOLUL 3 ................................................................................................................................ 89
3. GESTIONAREA ACTIVITĂŢILOR PARALELE
Una din caracteristicile principale ale sistemelor de calcul este existenţa activităţilor simultane. Capitolul 3 prezintă
conceptele şi instrumentele, care permit descrierea şi coordonarea acestor activităţi.

1.8. 3.1. Exemple de activităţi paralele


1.8.1. 3.1.1. Administrarea intrărilor-ieşirelor buferizate
Reluăm exemplul imprimării cu gestionarea unor zone tampon pe disc, descris în capitolul 2. Analiza acestui mod
de funcţionare (fig. 3.1) pune în evidenţă patru activităţi, care pot avea loc simultan :
1) primitiva scriere_linie SL (unitatea centrală)
2) scriere pe disc SD (canal 1)
3) citire de pe disc CD (canal 2)
4) imprimare fizică IF (canal 3)

scriere_lin
S S C I

tm1 tm2
(memorie) td (memorie)

(di
Fig. 3.1. Gestionarea buferizată a unei
Aceste patru activităţi sunt în mare măsură autonome, or ele sunt executate pe procesoare distincte cu programe
diferite. Ele nu sunt totuşi independente, deoarece accesează obiecte comune: două zone tampon în memoria operativă,
tm1 şi tm2 şi una pe disc, td. Pot fi evidenţiate două tipuri de condiţii, care trebuie respectate:
1) Condiţii, care stabilesc posibilitatea existenţei activităţilor
O înregistrare nu poate fi preluată dintr-o zonă tampon înainte de a fi depozitată aici. Zonele tampon au
capacităţi limitate. Dacă o zonă tampon este total ocupată cu înregistrări, care nu au fost încă preluate, este
imposibilă depozitarea. Astfel, acţiunile de depozitare şi preluare sunt supuse unor condiţii de existenţă, formulate
mai jos.
Activitate Acţiune Condiţie
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 activităţilor modifică valorile de adevăr ale acestor condiții. De exemplu, imprimarea unei linii pune în
TRUE condiţia “tm2 nu este plin”.
O activitate, care nu poate executa o acţiune, deoarece condiţia asociată are valoare FALSE, trebuie să aştepte,
adică execuţia unei acţiuni trebuie întârziată până când valoarea logică a condiţiei devine TRUE. În capitolul 2 a fost
discutată realizarea acestui mecanism de aşteptare şi continuare cu ajutorul întreruperilor.


În realitate, citirea şi scrierea pe disc sunt executate pe acelaşi canal, ceea ce poate impune unele restricții suplimentare privind simultaneitatea
executării lor.
2) Condiţii de validitate a informaţiilor partajate
Dacă vom examina procesul de accesare a zonelor tampon vom descoperi o altă formă de interacţiune, cauzată
de posibilitatea de accesare simultană de către două activităţi a unui amplsament de memorie. Astfel, dacă SD
citeşte conţinutul unei înregistrări din tm1 pe care SL este în curs de a o modifica, rezultatul acestei citiri riscă să fie
incoerent, dacă nu vor fi luate măsuri speciale de precauţie. Problema poate fi rezolvată impunând una din activităţi,
aflate în conflict, să “aştepte” până când cealaltă va termina accesarea.
Concluziile acestei prime analize:
Lucrarea “imprimare buferizată” este realizată prin intermediul a patru activităţi simultane, în mare măsură
autonome, care cooperează pentru atingerea scopului final.
Executarea corectă a lucrării impune respectarea anumitor restricții logice în derularea activităților. Aceste
restricţii pot conduce la întârzierea execuţiei unei activităţi, care este obligată să aştepte producerea unui eveniment,
provocat de o altă activitate.
1.8.2. 3.1.2. Comanda unui proces industrial
Să reluăm exemplul din capitolul 1, în care un calculator comanda un reactor chimic. Considerăm două reactoare
identice, R1 şi R2, care funcţionează independent. Vom examina două soluţii posibile:
1) utilizarea a două calculatoare (câte unul pentru fiecare reactor),
2) folosirea unui singur calculator pentru comanda ambelor reactoare.
Prima variantă nu prezintă nimic nou în raport cu descrierea din capitolul 1. Soluţia a doua solicită identificarea
unor condiții suplimentare pentru realizarea sa. Fie P1, P2, D1, D2 segmentele procedurilor (programele) şi datelor
pentru comanda celor două reactoare R1 şi R2, memoria principală având capacitatea necesară pentru păstrarea acestor
segmente. Programele P1 şi P2 sunt executate pe rând de procesor. Raţionând ca şi în capitolul 1, concluzionăm că
relaţia 2t<T trebuie să fie respectată. Dacă acesta va fi cazul, funcţionarea reactoarelor pentru un observator extern
pare identică pentru ambele soluţii. Trebuie, totuşi să subliniem, că soluţia doi impune restricţii mai severe în ceea ce
priveşte performanţele calculatorului (capacitatea memoriei şi viteza de procesare). Să vedem acuma care sunt
modalitățile de implementare a soluţiei doi.
1) Partajarea procesorului
Între două execuţii succesive ale lui P1, procesorul este utilizat de P2, ca rezultat starea sa internă (conţinutul
registrelor, cuvântul de stare, contorul ordinal) va fi modificată. Pentru a permite reluarea lui P1, informaţiile sale
trebuiesc salvate la terminarea fiecărei secvenţe de execuţie şi restabilite la începerea execuţiei următoare. Aceleaşi
afirmaţii sunt valabile şi în cazul executării lui P2.
2) Partajarea programului
Programele P1 şi P2, fiind identice, putem păstra doar o singură copie (notată prin P) pentru economisirea memoriei.
Să examinăm condiţiile necesare pentru partajarea programului P:
 nu este permisă modificarea codului programului de execuţia sa,
 atunci când P este executat pentru reactorul Ri (i=1 sau 2), el va accesa segmentul de date Di; această adresare
selectivă a datelor va fi realizată de un mecanism care nu modifică textul programului.
Un program, existent în exemplar unic, dar care poate fi utilizat pentru executarea mai multor activităţi
independente, eventual simultane, se numeşte program reentrant (reenterabil). Acest mod de utilizare implică:
 invarianţa textului programului în cursul executării sale,
 desemnarea uniformă, de către program, a datelor proprii fiecărei activităţi.
Printre altele, dacă activităţile partajează un procesor unic, informaţiile despre starea acestuia (în particular, cele
care servesc la desemnarea datelor) trebuiesc salvate la fiecare comutare.
Să rezumăm rezultatele celui de-al doilea exemplu.
1. Au fost evidenţiate două activităţi logic independente: comanda reactoarelor R1 şi R2. Aceste două activităţi
pot fi puse pe două calculatoare distincte fără să existe vre-o legătură între ele.
2. Considerente de economie pot impune ca aceste activităţi să partajeze resurse fizice şi resurse program
comune. Buna funcţionare a acestei partajări restricţionează execuţia activităţilor (utilizarea alternativă a
procesorului) şi modul de utilizare a obiectelor partajate (salvarea contextului de executare, reenteranţa
programelor).
Ca şi concluzie pentru ambele exemple putem evidenţia două momente:
 existenţa unor activităţi evolutive, care pot derula simultan,
 existenţa unor relaţii între aceste activităţi: cooperare pentru executarea unor sarcini comune, concurenţă
în utilizarea resurselor partajate, relații determinate de specificaţiile de “bună funcţionare”. Ele se pot
traduce în restricţii de execuţie, care conduc la întârzieri temporare a progresării unei activităţi.
Continuarea acestui capitol are drept scop elaborarea unui suport formal pentru aceste noţiuni, introducând
conceptele de proces şi sincronizare şi descriind modalităţile lor de utilizare în cadrul unui sistem informatic.

1.9. 3.2. Noţiune de proces secvenţial


Noţiunea de proces este mai întâi introdusă într-un mod concret pentru cazul unui calculator care conţine un
procesor unic şi o memorie adresabilă. Vom trece mai apoi la definiţia abstractă a unui proces independent de
suportul său fizic, ceea ce ne va permite introducerea problemei sincronizării.
1.9.1. 3.2.1. Proces unic. Context
Noţiunea de proces pune la dispoziţie un model pentru reprezentarea unei activităţi, care rezultă din executarea
unui program pe calculator. Starea calculatorului este definită de starea procesorului (conţinutul registrelor) şi starea
memoriei (conţinutul amplasamentelor). Această stare este modificată de către procesor prin executarea instrucţiunilor
programului. Fiecare executare a unei instrucţiuni constituie o acţiune: o acţiune are ca efect trecerea în timp finit a
calculatorului dintr-o stare iniţială într-o stare finală. Acest efect este descris în specificaţiile instrucţiunii.
Executarea unei instrucţiuni va fi considerată indivizibilă sau atomară, adică este interzisă observarea sau
definirea stării maşinii în timpul acestei execuţii. Timpul utilizat pentru descrierea evoluţiei stării este un parametru t,
care poate lua o serie de valori discrete crescătoare, corespunzătoare începutului şi sfârşitului instrucţiunilor. Aceste
instanţe sunt numite puncte de observare, iar stările corespunzătoare ale maşinii sunt numite puncte observabile. Prin
abuz de limbaj, acest ultim termen desemnează în egală măsură şi punctele de observare. Acestea permit să se asocieze
data numită valoarea curentă a lui t unei stări a mașinii – stare datată. O stare datată definește un eveniment.
Evenimentele permit reperarea modificărilor stării maşinii. Începutul şi sfârşitul unei acţiuni a sunt evenimente datele
cărora vor fi notate prin început(a) şi sfârşit(a); vom avea, evident, relația început(a) precede (<) sfârşit(a).
Astfel, executarea unui program se traduce într-o suită de acţiuni a1, a2,..., ai,..., cu început(ai) < sfârşit(ai).
O astfel de suită este numită proces secvenţial (sau simplu proces).
Un proces poate, deci, fi descris cu ajutorul succesiunii evenimentelor început(a1), sfârşit(a1), început(a2),
sfârşit(a2),... Această suită de stări datate ale maşinii se numeşte traiectorie temporală (sau istorie) a procesului.
Putem de asemenea defini un proces ca o suită de activităţi în sensul lui 2.1.
Mulţimea informaţiilor pe care acţiunile unui proces le pot consulta sau modifica se numeşte contextul procesului.
Reluând modelul de execuție secvențială din 2.1, contextul unui proces rezultant din executarea unui program conţine:
1) Contextul procesorului (cuvântul de stare şi registrele),
2) Un context în memorie - segmente procedură, date, stivă de execuție,
3) O mulţime de atribute ataşate procesului şi care specifică diferite proprietăţi:
a) Nume. Numele unui proces, care serveşte pentru identificarea lui, este de regulă, un număr intern atribuit
la crearea procesului şi care permite să se ajungă la reprezentarea contextului său (v. 4.2).
b) Prioritate. Prioritatea proceselor permite ordonarea lor pentru alocarea procesorului (v. 4.2, 4.3). Dacă
toate procesele au aceeaşi prioritate, alocarea se face conform ordinii “prim sosit, prim servit”.
c) Drepturi. Drepturile unui proces specifică operaţiile care îi sunt permise, în scopul asigurării protecţiei
informaţiei (v. 5.1.4).
Traiectoria temporală a unui proces este definită de şirul stărilor contextului său (procesor şi memorie) preluate
după execuţia fiecărei instrucţiuni. Prin definiţie, starea restului maşinii nu este modificată de execuţia procesului.


Într-un mod mai riguros ar trebui să adăugăm aici “punctele întreruptible” care pot fi prezente în unele instrucţiuni de lungă durată; o executare
a unei asemenea instrucţiuni poate fi considerată o suită de mai multe acţiuni.

Pentru comoditatea expunerii considerăm, că sfârşitul unei acţiuni şi începutul acţiunii următoare sunt două evenimente distincte, datele cărora
sunt diferite, deşi stările corespunzătoare sunt identice.
1.9.2. 3.2.2. Relaţii între procese
Vom cerceta în continuare execuţia unei mulţimi de procese şi interacţiunea reciprocă a acestora. Noţiunile,
introduse în 3.2.1 pot fi extinse pentru o mulţime de procese:
 traiectoria temporală a unei mulţimi de procese este şirul evenimentelor formate de începuturile şi sfârşiturile
acţiunilor rezultante din executarea programelor acestor procese.
 contextele unor procese diferite pot avea părţi comune. Două procese, contextele cărora sunt disjuncte, se
numesc independente; ele nu pot avea interacţiuni reciproce. Partea contextului, care aparţine unui singur
proces, se numeşte context privat al procesului dat.
Exemplul 3.1. Fie două procese care partajează o procedură reentrantă. Segmentul executabil, care conţine această procedură aparţine contextului
comun. Segmentul de date şi stiva fiecărui proces formează contextul lor privat. ◄

1.9.2.1. 3.2.2.1. Mulţimi de procese. Paralelism


Să considerăm două programe distincte P şi Q, fiecare având în memorie un segment de cod şi un segment de date.
Numim p şi q procesele rezultante din executarea acestor două programe. Executarea setului (p, q) poate să se producă
în diferite moduri, caracterizate de forma particulară a traiectoriei temporale. Trei posibile traiectorii sunt reprezentate
în figura 3.2.
p q
(

p q p q p q
(

p q
(

Fig. 3.2. Executarea unei mulţimi de


Schemele de mai sus pot fi comentate astfel:
 schema 1: este executat mai întâi procesul p în întregime, apoi procesul q la fel în totalitate,
 schema 2: sunt executate şiruri de instrucţiuni ale procesului p în mod alternativ cu şiruri de instrucţiuni ale
procesului q, şi tot aşa până 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 execuţie introducem noţiunea nivel de observare. Putem considera o suită
de acţiuni ale unui proces ca o acţiune unică, adică să observăm derularea unui proces considerând o unitate de execuţie
mai puţin fină decât instrucţiunea. De exemplu, dacă vom redefini noţiunea de acţiune elementară ca execuţie a
unei proceduri, traiectoria procesului va conţine doar stările definite de fiecare apel şi retur de procedură. Nivelul de
observare cel mai fin (cel al instrucţiunilor) este numit nivel de bază.
Să ne situăm mai întâi la nivelul de observare la care, prin convenţie, executarea completă a fiecărei dintre
programele P şi Q reprezintă o acţiune unică. Definiţiile care urmează sunt pentru acest nivel.
a) schema de tip 1 este a unei execuţii secvenţiale a lui p şi q. Ea este caracterizată de relaţiile:
sfârşit(q) < început(p) sau sfârşit(p) < început(q)
b) schemele de tip 2 sau 3 sunt scheme de execuţie paralelă. Ele sunt caracterizate de
sfârşit(p) > început(q) sau sfârşit(q) > început(p).
Revenim la nivelul de bază. Putem face o distincţie între schemele 2 şi 3. Într-adevăr, în schema 2, din considerente
de existenţă a unui singur procesor, la un moment de timp dat doar o singură acţiune 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ă observaţii importante sunt necesare:
1) Diferenţa acestor scheme de execuţie este legată de alegerea nivelului de observare. Astfel, la nivelul de bază,
diferenţa dintre schemele 1 şi 2 dispare: ambele sunt secvenţiale.
Exemplu 3.2. Utilizatorii unui sistem de operare, care funcţionează î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
instrucțiuni. La nivelul de bază, însă, aceste instrucţiuni sunt atomare şi executate de procesor în mod secvenţial. Exemplul celor
două reactoare chimice, prezentate în 3.1.2, conduce la observaţii analogice. ◄
2) Alegerea nivelului de observare depinde de finețea fenomenelor, care dorim să le considerăm elementare.
Dacă trebuie să studiem executarea instrucţiunilor în “pipe-line” pe un procesor microprogramat, în calitate
de nivel de observare va fi ales nivelul microinstrucţiunilor, iar contextul va fi completat cu memoria
microprogramelor și registrele interne.
Vom examina realizarea, la nivelul de bază, a unei scheme de execuţie de tipul 2. La fiecare realocare a
procesorului, contextul procesului curent trebuie salvat, pentru a permite reluarea ulterioară a acestei execuţii. Dacă
memoria are o capacitate suficientă pentru a păstra toate segmentele, doar contextul procesorului trebuie salvat.
Dacă memoria principală poate conţine, 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ă definiţia operaţională,
adesea întâlnită, a contextului unui proces ca mulţime a informaţiilor care trebuie salvată pentru a permite
reluarea ulterioară a procesului, dacă execuţia acestuia a fost întreruptă.
1.9.2.2. 3.2.2.2. Concurenţa proceselor. Resurse virtuale
Situaţia descrisă de schemele 1 şi 2 nu rezultă dintr-o legătură logică între p şi q, ci doar din existenţa unui singur
procesor. Ea poate fi caracterizată astfel: fie o mulţime de procese contextele cărora 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 situaţia descrisă, procesorul este o resursă critică pentru procesele p şi q.
Observăm că excluderea mutuală a unei resurse conduce la “serializarea” execuţiei proceselor concurente, în cazul
unor acţiuni, care cer această resursă (în exemplul dat, toate acţiunile). Schemele 1 şi 2 diferă doar prin nivelul de
fineţe la care este executată serializarea.
Funcţionarea corectă a unei mulţimi de procese, care participă la îndeplinirea unei lucrări comune, implică relaţii
logice de cooperare (v.3.1.1). Este comod să se separe această cooperare de concurenţa pentru resursele fizice cu
scopul de a simplifica înţelegerea şi aplicarea celor două tipuri de relaţii. Pentru aceasta este folosită noţiunea de
resurse virtuale: fiecărei resurse fizice critice i se asociază tot atâtea copii imaginare (sau virtuale) ale acestei resurse
câte procese concurente solicită utilizarea ei. Suntem nevoiţi să tratăm două probleme distincte:
1) respectarea relaţiilor de cooperare între procesele, care, fiecare posedă (conceptual) resursele fizice solicitate
şi pentru care paralelismul în execuţie nu este restricţionat de competiţia pentru resurse,
2) reglarea problemei de concurenţă pentru resursele fizice printr-o serializare convenabilă a execuţiei proceselor
în cauză. Se va spune în acest caz, că realizăm 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ă ataşăm fiecărui 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 menţionat, că această schemă logică reprezintă doar o notaţie compactă pentru mulţimea
schemelor reale posibile şi că ele sunt obligatoriu de forma 1 sau 2 din considerentele unicităţii procesorului. Pentru
o schemă reală şi una virtuală a unui proces dat este păstrată doar ordinea de succesiune a evenimentelor (începutul şi
sfârşitul acţiunii) şi nu sunt păstrate valorile absolute ale intervalelor de timp, care le separă. În absenţa altor informaţii,
nu putem spune nimic apriori despre ordinea evenimentelor, asociate unor procese distincte. Timpul folosit la
reperarea evenimentelor în schema logică este numit timp logic; relaţiile sale cu timpul real sunt prezentate în fig.3.3.
a1 a2
+------+-----------------+------+ procesul p
b1 b2 (timp logic)
+--+-------+---------+ procesul q
a1 a2
 +------+---+ +----------+ +----+------+ p (timp real, execuţia 1)
+--+--+ +-----+---------+ q
b1 b2
a1 a2
+---+ +---+-----------------+------+ p (timp real, execuţia 2)
+--+----+ +---+---------+ q
b1 b2
În toate cazurile a1 precede a2, b1 precede b2.
Fig.3.3. Timpul logic şi timpul real
Vom considera, că evoluţia proceselor are loc în timp logic, adică vor fi interzise orice ipoteze despre viteza relativă
a proceselor în cauză: aceasta este consecinţa simplificării considerabile, introduse de noţiunea de resurse virtuale,
care permite ignorarea mecanismelor de alocare. O singură excepţie, totuşi va fi făcută pentru studierea sincronizării
temporale, în care timpul fizic intervine în mod precis ca măsură a duratei şi nu numai ca mod de reperare relativă.
1.9.2.3. 3.2.2.3. Excludere mutuală
Introducerea resurselor virtuale ne permite să tratăm problema excluderii mutuale la accesarea resurselor fizice
corespunzătoare în cadrul mecanismelor de alocare. Această problemă poate fi pusă într-un alt mod cum o
demonstrează exemplul următor.
Exemplu 3.3. 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). Acţiunile respective se vor scrie în programele lui p şi q:
procesul p: Ap : n=n+np procesul q: Aq : n=n+nq
Să realizăm decompoziţia acestor acţiuni în instrucţii, notând 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 acţiuni 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 actualizări a fost pierdută şi valoarea finală a variabilei n este incorectă. Pentru evitarea acestei incoerenţe acţiunile Ap şi
Aq trebuie să fie executate în excludere reciprocă; se zice de asemenea că ele constituie pentru p şi q secţiuni critice. Este necesar
să se respecte condiţia
sfârşit(Ap) < început(Aq) sau sfârşit(Aq) < început(Ap).
Această condiţie de serializare, care are efectul de a face Ap şi Aq să devină atomare, este identică condiţiei deja întâlnite în
3.2.2.2 la accesarea unei resurse fizice critice. ◄
 Excluderea mutuală constă în extinderea pentru secvenţa de acţiuni a proprietăţii de indivizibilitate a acţiunilor
nivelului de bază (acţiuni atomare). Posibilitatea executării unor acţiuni atomare se află la baza mecanismelor
care realizează sincronizarea.

1.10. 3.3. Sincronizarea proceselor


1.10.1. 3.3.1. Exprimarea şi implementarea restricţiilor de succesiune
Introducem două exemple, care ne vor ajuta la formularea restricţiilor logice impuse de cooperare.
Exemplul 3.4. Procesul p transmite informaţii 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 condiţia:
sfârşit(scriere(a)) < început(citire(a))
Această relaţie exprimă restricţia, că citirea lui a de către q nu poate începe înainte de terminarea scrierii lui a de către p. ◄
Exemplul 3.5. Rendez-vous. Fie N procese p1,..., pN. Definim în programul fiecărui 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
<debut_i>;
<rendez-vous>
<continuare_i>;
atunci restricţiile de sincronizare se vor exprima după cum urmează:
 i, j  [1..N]: sfârşit(debut_i) < început(continuare_j) ◄
Restricţiile de sincronizare pot fi exprimate prin următoarele două forme echivalente:
1. Se va impune o ordine de succesiune în timp logic pentru unele puncte ale traiectoriei temporale ale
procesului,
2. Se va impune unor procese o condiţie de autorizare a depăşirii acestor puncte ale traiectoriei lor temporale.
Punctele privilegiate astfel se vor numi puncte de sincronizare.
Expresia (2) arată, că restricţiile de sincronizare pot fi satisfăcute impunând un proces “să aştepte” să execute o
acţiune până când o oarecare condiţie va fi satisfăcută. Această noţiune de aşteptare nu poate fi exprimată cu ajutorul
instrumentelor introduse până acum. Pentru aceasta vom introduce o nouă stare pentru un proces, stare în care procesul
se zice în aşteptare sau blocat, prin opoziţie stării activ, considerate până acum în mod implicit. Un proces, care intră
în starea de aşteptare, plecând de la un punct observabil t, opreşte progresarea începând cu acest punct şi stopează
executarea acţiunilor. În momentul în care procesul revine în starea activ, el reia execuţia sa şi contextul său privat
restabileşte starea pe care procesul o avea în punctul t (partea neprivată a contextului poate fi modificată de execuţia
altor procese).
Se numeşte blocare tranziţia activaşteptare şi deblocare tranziţia inversă.
 Vom utiliza noţiunea de aşteptare pentru specificarea sincronizării proceselor. Această specificare se va
produce în două etape:
1) definirea punctelor de sincronizare pentru fiecare proces,
2) asocierea unei condiţii de depăşire fiecărui punct de sincronizare, condiţie exprimată prin intermediul
variabilelor de stare a sistemului.
Vom ilustra acest mod de specificare pentru cele două exemple precedente. Notăm un punct de sincronizare prin
ps, iar condiţia de depăşire asociată prin aut(ps). Doar dacă această condiţie are valoarea TRUE, procesul în cauză
este autorizat să execute instrucţiunea etichetată cu ps.
Exemplul 3.6: var f : boolean init false
procesul p procesul q
scriere(a); <debut_q>;
f:=true;
<continuare_p> ps : citire(a)

aut(ps) : f:=true
A fost introdusă variabila de stare f pentru a exprima condiţia “procesul p a terminat acţiunea scriere(a)”.◄
Exemplul 3.7: var n : integer init 0;
procesul pi
<debut_i>;
n:=n+1
ps: <continuare_i>
aut(psi): n=N (i=1,...,N);
Variabila de stare n este în acest caz numărul procesului sosit în punctul de rendez-vous.◄

1.10.2. 3.3.2. Probleme de realizare a sincronizării


Vom încerca să implementăm sincronizarea specificată de condiţiile de depăşire. Pentru aceasta trebuie să definim
un mecanism de aşteptare prin introducerea noţiunii de eveniment memorizat. Un eveniment memorizat (e) este o
variabilă, care poate lua două valori: TRUE şi FALSE, valoarea iniţială FALSE. Asupra evenimentului memorizat
sunt posibile două operaţii, care sunt acţiuni indivizibile:
e:=<valoare> -- atribuirea imediată a unei valori
aşteptare(e).
Operaţia aşteptare(e), executată de un proces p, are următoarea specificaţie:
if e = FALSE then
starea(p) := blocat -- p este trecut în “aşteptarea lui e”
endif
Când e ia valoarea TRUE, toate procesele care aşteptau e trec în starea activ.
Vom încerca acum să realizăm cu ajutorul acestui mecanism sincronizarea pentru cele două exemple.
Exemplul 3.8. var e : eveniment memorizat;
procesul p procesul q
scriere(a); <debut_q>;
e:=TRUE; aşteptare(e);
<continuare_p> citire(a) ◄

Analiza acestui sistem (care nu conţine alte variabile de stare, decât evenimentul memorizat e) poate fi făcută
enumerând traiectoriile temporale posibile. Această analiză arată, că sincronizarea este corectă atunci când operaţiile
asupra evenimentului memorizat sunt indivizibile.
Exemplul 3.9. var e : eveniment memorizat;
n : integer init 0;
procesul pi
<debuti>;
(A) n:=n+1;
(B) if n<N then
aşteptare(e)
else
e:=TRUE
endif
<continuarei> ◄

O analiză mai atentă ne arată că acest program nu este tocmai corect. Notând un registru local al procesului i prin
Ri, acţiunile (A) şi (B) pot fi descompuse după cum urmează:
(1) load Ri, n
(2) ai Ri, 1 -- adunare imediată
(3) ci Ri, N --comparare imediată
(4) br () etichetă -- salt dacă Ri  N
<aşteptare (e)>
...
etichetă : ...
Presupunem, că toate proceselor sunt în aşteptare în punctele lor de rendez-vous, cu excepţia 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.
Reieşind din analiza de mai sus putem concluziona, că implementarea condiţiilor de sincronizare nu poate fi corect
realizată numai cu ajutorul operaţiilor de aşteptare. Consultarea şi modificarea variabilelor de stare, care intervin în
aceste condiţii, trebuie să fie executate în regim de excludere reciprocă. Observaţia dată ne impune să introducem un
mecanism de sincronizare, care în mod automat ar asigura acest regim de funcţionare.
1.10.3. 3.3.3. Monitorul – mecanism de sincronizare
1.10.3.1. 3.3.3.1. Definiţii
Un monitor este constituit dintr-o mulţime de variabile de stare şi o mulţime de proceduri, care utilizează aceste
variabile. Unele dintre aceste proceduri, numite externe, sunt accesibile utilizatorilor monitorului. Numele acestor
proceduri sunt numite puncte de intrare ale monitorului. Procesele, care utilizează monitorul pentru a se sincroniza,
nu au acces direct la variabilele de stare. Monitorul poate fi utilizat doar prin apelarea procedurilor sale externe.
Acestea permit blocarea sau deblocarea proceselor conform specificaţiilor problemei. Condiţiile de blocare sau
deblocare sunt exprimate ca funcţie ale variabilelor de stare, iar mecanismul de execuţie al monitorului garantează
manipularea acestor variabile în regim de excludere mutuală. În fine, un monitor conţine un fragment de cod de
iniţializare, executat o singură dată la crearea monitorului.
Blocarea şi deblocarea proceselor se exprimă, în procedurile monitorului, prin intermediul unor condiţii. O
condiţie este declarată ca şi o variabilă, dar nu are “valoare”: o condiţie c poate fi utilizată doar prin intermediul a trei
operaţii sau primitive, efectul cărora este descris mai jos (prin p am notat procesul, care le execută)
c.aşteptare : blochează procesul p şi îl plasează în “aşteptarea lui c”
c.vid : funcţie cu valori booleene (true, dacă nu există nici un proces în aşteptarea lui c, altfel false)
c.semnalizare : if non c.vid then <deblocarea proceselor care sunt în aşteptarea lui c> endif
Procesele, care sunt în aşteptarea unei condiţii c, sunt grupate într-un fir de aşteptare asociat lui c. Putem spune,
că o condiţie furnizează proceselor un instrument de desemnare a unui astfel fir de aşteptare.
Un proces deblocat de c.semnalizare îşi reia execuţia de la instrucţiunea, care urmează imediat primitivei
c.aşteptare, care-l blocase. Necesitatea asigurării excluderii reciproce pentru variabilele de stare impune o restricţie
suplimentară mecanismelor de deblocare: atunci când un proces p deblochează un proces q printr-o operaţie de
semnalizare, p şi q nu pot fi menţinute simultan active. Se va impune blocarea lui p până în momentul în care q, la
rândul său, se va bloca sau va părăsi monitorul. Pentru evitarea unei blocări indefinite a lui p este necesar ca operaţia
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 operaţia semnalizare înainte ca un nou proces să poată executa o procedură a monitorului.
Observaţie. Problema excluderii mutuale , legată de primitiva semnalizare nu se va pune, dacă aceasta este ultima operaţie
într-o procedură a monitorului. Acest caz este foarte frecvent în practică, unele monitoare (de exemplu, Concurrent Pascal) respectă
în mod obligator restricţia dată.
1.10.3.2. 3.3.3.2. Exemple de utilizare
Exemplul 3.10. sinc: monitor;
var fact: boolean;
term: condiţie;
procedura terminare_scriere;
begin
fact:=true;
term.semnalizare
end
procedura debut_citire;
if non fact then
term.aşteptare
endif
begin -- iniţializare
fact := false
end
end sinc
Monitorul este utilizat după cum urmează:
procesul p procesul q
scriere(a); <debut_q>
sinc.terminare_scriere; sinc.debut_citire;
<continuare_p> citire(a);
Exemplul 3.11. rendez-vous : monitor;
var n : integer;
toţi_sosiţi : condiţie;
procedura sosire;
begin
n := n+1;
if n < N then
toţi_sosiţi.aşteptare --nu au sosit toate procesele
endif
toţi_sosiţi.semnalizare -- a sosit şi ultimul
end
begin -- iniţializare
n:=0
end
end rendez_vous
Programul procesului pi va fi de forma:
procesul pi
<debut i>
rendez_vous.sosire;
<continuare i>
Este foarte important să înţelegem funcţionarea sincronizării: procesul, care soseşte ultimul la rendez_vous,
execută toţi_sosiţi.semnalizare şi deblochează unul dintre procesele, care sunt în starea aşteptare. Acesta, la rândul
său, execută toţi_sosiţi.semnalizare, deblocând următorul proces şi aşa mai departe.

1.11. 3.4. Implementarea sincronizării


1.11.1. 3.4.1. Probleme-tip
Experienţa demonstrează, că problemele de sincronizare logică întâlnite în practică pot fi reduse, în marea lor
majoritate, la combinaţia unui număr mic de situaţii elementare, schemele de soluţionare ale cărora sunt cunoscute.
Secţiunile 3.4.2 – 3.4.5 sunt consacrate studierii acestor probleme-tip, utilizând instrumentarul de bază, pus la
dispoziţie de monitoare. Problemele-tip sunt următoarele:
 accesarea de către o mulţime de procese a unei resurse partajate comune,
 comunicarea între procese,
 gestionarea perifericelor şi intrărilor-ieşirilor tamponate,
 sincronizare temporală.
1.11.2. 3.4.2. Administrarea unei resurse partajate
Considerăm o resursă (fizică sau logică) partajată de o mulţime de procese. Utilizarea acestei resurse trebuie să
respecte nişte reguli de utilizare, destinaţia cărora constă în garantarea unor proprietăţi specificate sau restricţii de
integritate. Aceste restricţii sunt specificate pentru fiecare resursă. O modalitate de garantare a respectării regulilor
de utilizare a unei resurse constă în adoptarea următoarei scheme:
 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 căruia impune respectarea
restricţiilor de integritate.
Cel mai simplu caz este acela al unei resurse pentru care singura restricţie de integritate este de a fi utilizată în
excludere reciprocă. Simpla grupare a procedurilor sale de acces într-un monitor unic garantează respectarea acestor
restricţii.
1.11.2.1. 3.4.2.1. Alocarea resurselor banalizate
Considerăm o resursă pentru care există un număr fix de N exemplare. Un proces poate accesa la cerere n unităţi
din cele N, le poate utiliza şi apoi elibera. O unitate, folosită de un proces, se numeşte alocată procesului, care o
utilizează, pentru toată perioada de la accesare până la eliberare. Toate unităţile sunt echivalente din punctul de vedere
al proceselor utilizatoare, vom mai zice că resursa este banalizată. Zonele-tampon din memoria principală sau pe
disc, unităţile de bandă magnetică, etc. sunt exemple de resurse banalizate.
Vom admite următoarele 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 operaţie de eliberare este aplicată totdeauna ultimelor resurse, achiziţionate de procesul care execută
eliberarea,
 o cerere de alocare este blocantă în caz de eşec (număr insuficient de unităţi libere).
Definim două proceduri cerere şi eliberare ale unui monitor. Utilizarea resursei are loc conform schemei de mai
jos.
ps:resurse.cerere(n); -- cerere pentru n unităţi
-- aşteptare în caz de eşec
<utilizarea unităţilor primite>
resurse.eliberare(n) -- eliberarea resurselor
Condiţia de sincronizare se va scrie pentru orice proces:
aut(ps) : n  nlibere
Prima formă a unui monitor resurse se va obţine utilizând direct condiţia de sincronizare:
resurse: monitor;
var nlibere : integer;
disp : condiţie;
procedura cerere(n);
begin
while n>nlibere do
disp.aşteptare;
endwhile;
nlibere:=nlibere-n
end;
procedura eliberare(n);
begin
nlibere:=nlibere+n;
disp.semnalizare -- deblocare în lanţ
end;
begin -- iniţializare
nlibere:=N
end
end resurse
Nu a fost făcută nici o ipoteză despre ordinea proceselor în firul de aşteptare al condiţiei disp. Drept consecinţă, la
fiecare eliberare vor fi deblocate toate procesele. Este o soluţie greoaie şi poate fi foarte costisitoare, dacă există multe
procese. Este preferabil să se programeze în mod explicit administrarea firului de aşteptare a cererilor, ceea ce obligă
să avem câte o condiţie distinctă pentru fiecare proces.
Pentru elaborarea programelor vom introduce un tip discret proces, variabilele căruia permit desemnarea
proceselor.
resurse : monitor
type element : struct
lungime : integer
proc : proces
end;
var nlibere : integer;
disp :array[proces] of condiţie;
<declaraţia firului de aşteptare 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; -- p este procesul apelant
introducere(e,f); -- în ordinea de creştere a lungimii
disp[p].aşteptare
endif;
nlibere:=nlibere-n
end;
procedura eliberare(n);
var e: element;
begin
nlibere:=nlibere+n;
while primul(f).lungime  nlibere do
extragere(e,f); -- elementul extras = primul (f)
disp[e.proc].semnalizare -- e.proc = procesul deblocat
endwhile;
end;
begin -- iniţializare
nlibere:=N;
f:=<vid>
end
end resurse
Această soluţie este mult mai compactă şi mai generală, deoarece permite o mai bună separare a expresiei
sincronizării (ceea ce rezultă din structura monitorului) de politica de alocare (care este definită de procedurile de
gestionare a firului de aşteptare introducere şi extragere).
1.11.2.2. 3.4.2.2. Modelul cititorului şi scriitorului
Să considerăm un fişier manipulat de procese din două clase diferite: cititori, care consultă fişierul fără a modifica
conţinutul lui şi scriitori, care pot modifica acest conţinut. Fie pentru un moment arbitrar de timp ncit şi nscr numărul
de cititori şi de scriitori, respectiv, care folosesc o procedură de acces la fişier. Cererea de asigurare a coerenţei
fişierului ne impune să respectăm următoarele restricţii:
(nscr=0) şi (ncit0) -- fişier în citire
sau (nscr =1) şi (ncit=0) -- fişier în scriere
Fie fich un monitor care asigură respectarea acestor restricţii. Vom impune următoarea formă a acceselor la fişier:
proces cititor proces scriitor
fich.debut_citire; fich.debut_scriere;
<acces citire> <acces scriere>
fich.terminare_citire; fich.terminare_scriere;
Procedurile debut_citire, terminare_citire, debut_scriere, terminare_scriere trebuie să asigure respectarea
restricţiilor de mai sus. Vom implementa aceste restricţii autorizând depăşirile; pentru aceasta este necesar să fie
precizate priorităţile între cititori şi scriitori.
 Cu titlu de exemplu, presupunem că cititorii au prioritate în faţa redactorilor (o scriere nu va fi autorizată, dacă
există cititori în aşteptare). Definim următoarele variabile de stare:
scr = o scriere este în curs (valoare booleană)
nc = numărul de cititori în aşteptare sau în curs de citire
În acest caz, condiţiile de depăşire se vor exprima după cum urmează:
aut(citire) : scr=false (nu există scrieri curente)
aut(scriere): scr=false şi nc=0 (nici scrieri nici citiri în curs, nici cititori în aşteptarea)
Monitorul, care urmează traduce direct aceste condiţii.
fich : monitor;
var scr : boolean;
nc : integer;
c_scr, c_cit : condiţie;
procedura debut_citire;
begin
nc:=nc+1;
if scr then
c_cit.aşteptare;
endif
end
procedura terminare_citire;
begin
nc:=nc-1;
if nc=0 then -- ultimul cititor a terminat
c_scr.semnalizare
endif
end
procedura debut_scriere;
begin
if scr or nc>0 then -- scriere sau citire în curs
c_scr.aşteptare
endif;
scr:=true
end
procedura terminare_scriere;
begin
scr:=false;
if nc>0 then -- prioritate cititorilor care aşteaptă
c_cit.semnalizare
else
c_scr.semnalizare
endif
end
begin -- iniţializare
scr:=false;
nc:=0
end
end fich
Pot fi definite şi programate şi alte reguli de prioritate.
1.11.3. 3.4.3. Comunicarea între procese
Procesele pot comunica prin accesarea unei mulţimi de variabile comune. Acest mod de comunicare este slab
structurat şi ineficace, deoarece el trebuie să asigure excluderea reciprocă a variabilelor. Este utilizat doar în cazuri
speciale, cum ar fi un nucleu de sincronizare, unde excluderea mutuală globală este redusă la secvenţe scurte şi bine
protejate. Pentru cazuri generale sunt utilizate alte moduri de comunicare. Vom prezenta mai întâi o schemă de bază
pentru comunicarea prin mesaje - modelul producătorului şi consumatorului, realizat cu ajutorul monitoarelor
(3.4.3.1). O altă posibilitate, descrisă în 3.4.3.2, constă în a considera operaţiile de comunicare ca un fel de mecanisme
primitive de sincronizare. În 3.4.3.3 prezentăm o aplicaţie frecventă de comunicare – modelul client-server.
1.11.3.1. 3.4.3.1. Modelul producătorului şi consumatorului
O schemă uzuală de comunicare este cea în care un proces (producătorul) trimite mesaje unui alt proces
(consumatorul), utilizând un tampon în memoria comună. Mesajele sunt de lungime fixă şi capacitatea tamponului
este de N mesaje.
Specificaţiile comunicaţiei sunt următoarele:
 un mesaj dat poate fi preluat doar o singură dată după ce a fost depozitat în tampon,
 un mesaj nu trebuie să poată fi pierdut; dacă tamponul conţine N mesaje nepreluate, nu pot fi depozitate aici
mesaje suplimentare,
 o operaţie “imposibilă” (depozitare într-un tampon plin sau preluare dintr-un tampon vid) blochează procesul,
care încearcă să o execute.
Condiţiile de depăşire pot fi exprimate după cum urmează, notând prin n numărul de mesaje din tampon, care nu
au fost încă preluate:
aut(depozitare) : n < N -- tamponul nu este plin
aut(preluare) : n > 0 -- tamponul nu este vid
Respectarea acestor restricţii este asigurată de un monitor tampon, utilizat după cum urmează:
proces producător proces consumator
... ...
produce(mesaj_emis); tampon.preluare(mesaj_recepţionat);
tampon.depozitare(mesaj_emis); consumator(mesaj_recepţionat);
Monitorul tampon poate fi elaborat, transcriind în mod direct autorizările de depăşire:
tampon : monitor;
var n : 0..N;
non_plin, non_vid : condiţie;
<declaraţii ale procedurilor depozitare şi preluare>
procedura depozitare(m:mesaj);
begin
if n=N then
non_plin.aşteptare
endif;
n:=n+1;
introducere(m);
non_vid.semnalizare
end
procedura preluare(var m:mesaj);
begin
if n=0 then
non_vid.aşteptare
endif;
preluare(m);
n:=n-1;
non_plin.semnalizare
end;
begin -- iniţializare
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 depozitării. 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 ieşire(var m:mesaj);
begin
m:= fa[top];
top:=top+1 mod N
end;
<iniţializarea se va completa cu top:=0; coadă:=0;>
Această schemă poate fi extinsă pentru mai mulţi producători şi consumatori. Drept efect, procedurile monitorului
asigură accesarea exclusivă la mesaje între producători şi consumători. Totuşi, în cazul a mai mulţi consumatori
schema nu permite direcţionarea unui mesaj către un consumator anumit: putem doar garanta, că un mesaj va fi preluat
de un consumator (şi numai de unul singur) fără a specifica concret de care.
1.11.3.2. 3.4.3.2. Primitive de comunicare
Schimbul de mesaje între procese, în afară de funcţia de transmitere a informaţiei, poate fi utilizat şi pentru
ordonarea evenimentelor în cadrul unor procese distincte, deoarece emiterea unui mesaj precede întotdeauna recepţia
sa. Operaţiile de schimb de mesaje pot fi definite ca nişte mecanisme primitive şi să le utilizăm pentru sincronizarea
proceselor.
Primitivele de bază în comunicarea prin mesaje sunt:
emitere(mesaj,destinaţie)
recepţie(mesaj,origine)
Specificările acestor primitive trebuie să precizeze:
 natura şi forma mesajelor,
 modul de adresare a proceselor emiţătoare ş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ţă, pasând adresa fizică sau identificatorul informaţiei transmise.
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 poştală (desemnare indirectă). Aceste nume sunt folosite ca parametri origine
şi destinaţie. Schema a doua facilitează modificarea dinamică a interconexiunilor proceselor sau chiar componentele
unei mulţimi de procese, care comunică.
În cazul desemnării directe parametrul origine a primitivei recepţie poate fi interpretat în două moduri:
 fie ca dată: receptorul specifică explicit că aşteaptă un mesaj de la un destinatar special (recepţie selectivă),
 fie ca rezultat: receptorul primeşte un mesaj care i-a fost adresat împreună cu identitatea emiţătorului.
În cazul desemnării indirecte asocierea proceselor cutiilor poştale poate fi statică sau dinamică. În ultimul caz, sunt
utilizate două primitive conectare şi deconectare pentru ataşarea procesului la o cutie poştală (în calitate de receptor)
şi de abrogare a acestei ataşări, respectiv. În unele sisteme un receptor sau mai multe pot fi ataşate unei cutii poştale
date; cutiile poştale supuse unor asemenea restricţii sunt adesea numite porţi. Este posibilă şi situaţia inversă când un
proces poate fi asociat la mai multe porţi distincte. Dacă asocierea între procesul receptor şi poartă este statică, un
nume de poartă specifică fără ambiguitate un proces receptor ca şi în metoda desemnării directe.
3) Moduri de sincronizare
Pentru primitivele de comunicare pot fi specificate mai multe moduri de sincronizare. În caz general, operaţia de
recepţie blochează, în absenţa mesajului, receptorul. Unele sisteme pun la dispoziţie primitive care dau posibilitatea
să se determine dacă o cutie poştală este vidă, ceea ce permite evitarea blocării. Pentru emitere sunt utilizate două
moduri de sincronizare:
 Schema producător-consumator, în care cutia poştală este realizată printr-o zonă tampon. Emiterea nu este
blocantă, cu excepţia cazului în care tamponul este plin. Folosirea tampoanelor de lungime variabilă cu
alocarea dinamică a locaţiunilor reduce probabilitatea blocării emiţătorului.
 Schema rendez-vous, în care emiţătorul este blocat până la preluarea mesajului de către receptor. Această
schemă poate fi considerată caz limită a precedentei cu lungimea nulă a tamponului.
În fine, atunci când un proces este asociat în recepţie la mai multe porţi, poate fi definit un mod de sincronizare,
zis aşteptare multiplă, în care sosirea unui mesaj la o oarecare din aceste porţi deblochează receptorul.
4) Tratarea erorilor
Scopul tratării erorilor este de a evita blocările infinite ale proceselor, care se pot produce în diverse circumstanţe:
 Emiterea unui mesaj cu o destinaţie (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 aşteaptă un mesaj sau un răspuns: procesele în aşteptare sunt
blocate şi recepţionează un cod de eroare.
Ultima situaţie nu este totdeauna detectabilă. O tehnică uzuală constă în stabilirea unui interval de timp maxim de
aşteptare a unui mesaj şi deblocarea procesului care aşteaptă la expirarea acestui interval (v. 3.4.5).
Vom ilustra prin câteva exemple reprezentative utilizarea acestor mecanisme de comunicare.
Exemplul 3.12. Sistemul de operare Thoth [8]. În acest sistem comunicarea foloseşte 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ă emiţătorul până la primirea unui răspuns, transmis în message. Această
primitivă indică identitatea procesului care a transmis răspunsul (sau nil, dacă destinatarul nu există).
id:=receive(message, id_orig)
recepţionează un mesaj; procesul origine poate să nu fie specificat. Valoarea transmisă este identitatea emiţătorului.
reply(message, id_orig, id_dest)
trimite un răspuns destinatarului specificat (care trebuie să-l aştepte); nu este blocantă; fără consecinţe, dacă răspunsul nu
era aşteptat.
forward(message, id_orig, id_dest)
această operaţie non blocantă este utilizată de un proces după recepţionarea unui mesaj trimis de către id_orig, pentru ca să
impună mesajul să ajungă la id_dest, care are acum obligaţia de a răspunde lui id_orig. ◄
Exemplul 3.13. Sistemul de operare Unix [9]. În sistemul Unix comunicarea între procese utilizează tampoane, numite pipes (tuburi), administrate
conform schemei producător-consumator. Mesajele transmise sunt caractere. Un pipe (tub) leagă un emiţător ş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
comunicărilor între procese este apelarea procedurii, însă transmiterea parametrilor şi a rezultatelor are loc conform principiului
de transmitere a mesajelor cu rendez-vous. Recepţionarea poate fi condiţionată (un apel este acceptat doar dacă o condiţie
specificată este satisfăcută) şi există posibilitatea de aşteptare multiplă. ◄

1.11.3.3. 3.4.3.3. Aplicaţii : relaţia client-server


O aplicaţie curentă a comunicărilor între procese este relaţia client-server. Un proces server are în şarjă
îndeplinirea unor servicii (executarea unui program predefinit) proceselor client. Pentru aceasta poate fi utilizată
următoarea schemă:
procesul server procesul client
ciclu poartă_serviciu.emitere(cerere)
poartă_serviciu.recepţionare(cerere)
<executare serviciu> ...
[poartă_client.emitere(rezultat)] ...
endciclu [poartă_client.recepţionarere(rezultat)]
 Secvenţele din parantezele pătrate sunt facultative.
Procesul server este asociat unei porţi, unde clienţii îşi depun cererile, trimiţând cereri; el este blocat atâta timp cât
nu există cereri de servicii în aşteptare.
Serviciul cerut poate conţine trimiterea la client a rezultatului. În acest caz clientul trebuie să trimită serverului în
cererea sa numărul unei porţi la care el se va bloca în aşteptarea rezultatului.
Fără a modifica schema de mai sus putem introduce mai multe procese server echivalente, oricare dintre ele fiind
în stare să satisfacă o cerere a unui serviciu. Aceste servere în recepţie vor fi asociate la una şi aceeaşi cutie poştală.
 Modelul din 3.4.2.1 (alocarea resurselor banalizate) şi modelul client-server de mai sus sunt reprezentative
pentru două scheme de obţinere a unui serviciu cu ajutorul proceselor într-un sistem de operare: apelarea de
procedură într-un monitor sau activarea uni proces server ciclic prin emiterea mesajelor. Alegerea între aceste
două scheme este dictată de considerente de eficacitate (schema serverului este preferată, atunci când există
paralelism real între client şi server) sau de uniformitate a structurii.
1.11.4. 3.4.4. Administrarea intrărilor-ieşirilor
Vom arăta cum poate fi integrată administrarea intrărilor-ieşirilor în mecanismul monitoarelor. Prezentăm mai
întâi gestionarea unui periferic izolat, apoi, ca exemplu de aplicaţie, principiul unei gestionări tamponate a intrărilor-
ieşirilor.
1.11.4.1. 3.4.4.1. Administrarea unui periferic
Fiecărui periferic îi este asociat un monitor procedurile externe ale căruia permit executarea intrărilor-ieşirilor la
acest periferic. Acest monitor are următoarea formă generală (pentru un sistem mono-utilizator):
perif: monitor;
var ..., sfr_schimb_i,...: condiţie;
<declaraţiile 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.aşteptare; -- întrerupere demascată
if starea ≠ ok then -- în timpul aşteptării
<tratare eroare(incident de transfer)>
endif;
<demascare întreruperi>
end;
...
begin
<iniţializare>
end
end perif
Procedura lansare_transfer_i pregăteşte programul pentru schimbul cerut (construirea programului canalului sau
al ADM - Acces Direct la Memorie, ţinând cont de parametrii schimbului) şi lansează execuţia sa (instrucţiunea SIO
– Start Input Output). Procesele apelante aşteaptă sfârşitul transferului datorită condiţiei sfr_schimb_i. Sosirea unei
întreruperi, care marchează sfârşitul schimbului de tip i provoacă în mod automat executarea următoarei secvenţe:
if sfr_schimb_i.vid then <tratarea eroare întrerupere care nu este aşteptată>
else sfr_schimb_i.semnalizare
endif
Pentru un proces care execută o intrare-ieşire apelând o procedură de schimb a acestui monitor, totul se petrece ca
şi cum schimbul este sincron: la returul din procedură, informaţia a fost efectiv transferată (sau o eroare a fost detectată
şi semnalizată). Mecanismul de blocare evită aşteptarea activă şi procesorul poate fi utilizat în timpul transferului de
un alt proces.
1.11.4.2. 3.4.4.2. Buferizarea imprimării
Putem elabora acum programul administrării tamponate a imprimării, 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 aceeaşi structură care are rolul de a asigura excluderea mutuală şi sincronizarea condiţiilor tampon plin şi tampon
vid.
Aceste tampoane, numite tampon1, tampon2 şi tampon_disc au aceeaşi 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 fiecărui monitor, procedurile de depozitare şi preluare pot fi:
<pentru tamponul 1> <pentru tamponul 2>
procedura intrare(l:linie); procedura intrare(l:linie);
tm1[coadă] := l; tm2[coadă] := l;
coadă := coadă+1 mod N1 coadă := coadă+1 mod N2
procedura ieşire(var l:linie); procedura ieşire(var l:linie);
l := tm1[top]; l := tm2[top];
top := top+1 mod N1 top := top+1 mod N2
În monitorul tampon_disc operaţiile de depozitare şi preluare sunt intrări-ieşiri, care utilizează monitorul de
gestionare a discului:
procedura intrare(l:linie);
disc.scriere(l,td[coadă]);
coadă := coadă+1 mod Ndisc
procedura ieşire(var l:linie);
disc.scriere(l,td[top]);
top := top+1 mod Ndisc
Programul de intrare-ieşire este realizat prin cooperarea a patru procese programul cărora este prezentat schematic
mai jos (trei procese ale sistemului de operare şi procesul utilizator). Pentru a simplifica expunerea au fost omise
secvenţele de tratare a erorilor şi am admis, că sistemul funcţionează în regim permanent fără limitarea numărului de
linii la imprimare. Programele folosesc trei monitoare de gestionare a perifericelor (tampon1, tampon2 şi tampon_disc)
şi două monitoare de gestionare a perifericelor (impr şi disc), construite în baza modelului perif, prezentat în 3.4.4.1
proces imprimare linie proces scriere_disc proces citire_disc
ciclu ciclu ciclu
tampon2.preluare(l); tampon1.preluare(l); tampon_disc.citire(l);
impr.scriere(l); tampon_disc.scriere(l); tampon2.depozitare(l);
endciclu endciclu endciclu
Imprimarea unei linii este cerută de procedura:
procedura scriere_linie(l:linie);
tampon1.depozitare(l)
Putem constata, că programele de mai sus sunt mult mai simple decât cele care folosesc direct întreruperile.
Structura modulară, introdusă de monitoare permite separarea totală a gestionării tampoanelor de cea a perifericelor.
Schimbarea capacităţii unui tampon modifică doar monitorul care comandă acest tampon; înlocuirea unui periferic cu
un altul implică rescrierea doar a monitorului, care comandă acest periferic.
1.11.5. 3.4.5. Sincronizare temporală
Sincronizarea temporală face ca timpul să intervină nu numai ca mijloc de ordonare a evenimentelor, dar şi ca
măsură de durată absolută. Acest mod de sincronizare este utilizat în aplicaţiile de timp real, care conţin interacţiuni
cu organe externe (comanda proceselor industriale, de exemplu). Sincronizarea temporală solicită folosirea unui ceas,
realizat prin intermediul unui oscilator cu quartz, care emite impulsuri la intervale regulate. Aceste impulsuri pot fi
utilizate pentru a declanşa o întrerupere la fiecare impuls sau pentru a decrementa în mod automat conţinutul unui
registru contor, o întrerupere este declanşată atunci când conţinutul acestui registru atinge valoare 0.
Vom utiliza a doua metodă de funcţionare. Unitatea de timp folosită este perioada ceasului.
Pentru rezolvarea problemelor principale de sincronizare temporală poate fi folosită primitiva suspendare(t), care
are ca efect blocarea procesului apelant pentru o durată (fizică) egală cu t unităţi de timp. Prezentăm principiul de
realizare a acestei primitive cu ajutorul unui ceas.
Pentru rezolvarea acestei probleme vom fi nevoiţi să întreţinem:
 valoarea absolută a timpului (ora absolută), care măsoară la orice moment timpul trecut de la o instanţă
iniţială,
 un registru, adică o listă a proceselor care aşteaptă deblocarea, ordonat conform timpului absolut de
deblocare.
Toate procesele, care apelează primitiva suspendare(t) sunt inserate în registru în poziţia, care corespunde orei sale
absolute de deblocare.
Numim ora_de_bază ora absolută a ultimei înnoiri a ceasului, adică a ultimei iniţializări a contorului; fie t_aşt
ultima valoare încărcată. Ora absolută exactă este dată la fiecare moment de timp de relaţia
ora_exactă = ora_de_bază + t_aşt - contor
(t_aşt - 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_aşt; ora_de_bază poate, deci, fi iniţializată conform
relaţiei
ora_de_bază := ora_de_bază + t_aşt
Variabila ora_de_bază, odată iniţializată, va fi corect întreţinută cu condiţia ca registrul să nu fie nicicând vid; în
caz general această condiţie va fi asigurată introducând un proces, numit paznic:
procesul paznic
ciclu
suspendare(tmax)
endciclu
în care tmax este un interval foarte mare de timp, paznicul rămânând pentru toată perioada de lucru în coada
registrului.
Mecanismele descrise sunt realizate într-un monitor numit ceas, care are două intrări: 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 aşteptare, care conţine descriptorii proceselor. Un
descriptor este format din numele procesului şi timpul absolut de deblocare; firul de aşteptare este ordonat în ordinea
creşterii timpului deblocării.
Presupunem, că întreruperea de ceas activează un proces, unica activitate a căruia constă în apelarea procedurii
ceas.tratare_întrerupere.
ceas: monitor;


Problema ar fi uşor de rezolvat, dacă fiecare proces ar dispune de un ceas propriu: ar fi suficient să se introducă în contor valoarea t şi să se
aştepte întreruperea la trecerea prin zero. Realizarea primitivei suspendare cu ajutorul unui ceas unic comun presupune ataşarea fiecărui proces a
unui ceas virtual.
type descriptor: struct
i : proces;
ora_debl : integer
end;
var contor, t_aşt;
ora_de_bază, ora_exactă: integer
deblocare : array[proces] of condiţie;
<declaraţiile firului de aşteptare şi procedurilor sale de acces>
procedura suspendare(durata:integer);
var proc: descriptor;
begin
ora_exactă:=ora_de_bază+t_aşt-contor;
proc.ora_deblocării:=ora_exactă+durata -- ora absolută a deblocării
proc.i := p; -- p este procesul apelant
intrare(proc,fir) -- ordonare de ora_deblocării
if proc=primul(fir) then
t_aşt:=contor:=durata;
ora_de_bază:=ora_exactă;
endif;
deblocare[p].aşteptare
end;
procedura tratare_întrerupere; -- contor în zero
var proc: descriptor;
delta_t:integer;
begin
ieşire(proc,fir); -- primul din firul de aşteptare
ora_de_bază := ora_exactă+t_aşt;
if vid(fir) then
delta_t := tmax
else
delta_t := primul(fir).ora_deblocării – ora_de_bază;
endif;
t_aşt := contor := delta_t; -- deblocarea viitoare
deblocare[proc.i].semnalizare
end;
begin
ora_de_bază := <ora_iniţială>;
contor := t_aşt := tmax;
intrare(paznic,fir)
end
end ceas
Metoda de mai sus (întreţinerea 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ă activităţi care se derulează în paralelism real. Timpul utilizat este un timp simulat, adică o variabilă, care
reprezintă trecerea timpului fizic, respectând proporţionalitatea intervalelor timpului simulat cu intervalele
corespunzătoare ale timpului fizic.

1.12. 3.5. Gestionarea dinamică a proceselor


Doar în sistemele cele mai simple procesele sunt în număr constant şi create odată pentru totdeauna la iniţializarea
sistemului. În sistemele concurente, mai ales în cele interactive, procesele sunt comandate dinamic. Astfel, în Multics,
un proces nou este creat odată cu admiterea unui nou utilizator; în Unix, la executarea fiecărei comenzi. Primitivele
de creare şi distrugere a proceselor pot fi puse în şarja sistemului de operare sau la dispoziţia utilizatorilor. Crearea
unui proces presupune alocarea resurselor şi iniţializarea contextului, elementele căruia au fost specificate în 3.2.1.
Distrugerea unui proces eliberează toate resursele care i-au fost alocate.
Primele primitive, propuse pentru gestionarea dinamică a proceselor, au fost fork şi join. Istoric şi cronologic,
aceste operaţii au fost introduse pentru organizarea executării paralele a programelor pe un sistem multiprocesoral,
noţiunea de proces nefiind încă clară. Vom descrie câteva variante ale acestor primitive.
Fie P o procedură. Instrucţiunea
id := fork(p),
executată de un proces p (părinte), 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 iniţial al lui q este o copie a
lui p, mai puţin contorul ordinal, care este fixat la prima instrucţiune a lui p. Procesul fiu se termină cu o primitivă,
numită exit sau quit, care provoacă dispariţia sa. După ce fork crează un proces fiu q, primitiva join q permite
procesului părinte să fixeze un punct de rendez-vous cu acest fiu. Executarea lui join q blochează procesul părinte
până când q nu va executa exit. Primitivele fork şi join au avantajele şi dezavantajele instrucţiunii go to din
programarea secvenţială.
Exemplul 3.15. În sistemul de operare Unix crearea unui proces poate fi realizată de către interpretorul limbajului de comandă (shell) sau cu
ajutorul instrucţiunii fork() de un program. Ultima situaţie este prezentată schematic în fig.3.4.

procesul 1 procesul 2

copie

date date

stivă stivă

procesul fiu
procesul părinte
Fig.4.4. Crearea proceselor cu ajutorul instrucţiunii fork

Efectul instrucţiunii fork():


 duplicarea procesului părinte;
 returnarea valorii pid (numărului procesului fiu) în procesul părinte;
 returnarea valorii 0 în procesul fiu:
procesul părinte procesul fiu

if (fork() == 0) if (fork() == 0)
codul procesului fiu codul procesului fiu
else else
codul procesului părinte codul procesului părinte

returnarea pid al procesului fiu ( 0) returnare 0


Altfel spus, în Unix primitiva fork (fără parametri) creează un proces al cărui spaţiu de lucru este o copie a spaţiului de lucru
a creatorului, inclusiv şi contorul ordinal. Diferenţa poate fi determinată consultând valoarea returnată de primitivă (0 pentru fiu;
identificatorul fiului sau nil pentru părinte). O primitivă wait permite părintelui să aştepte terminarea execuţiei unuia dintre
programele fiu (fără a putea alege care anume, dacă există mai multe). Un proces termină execuţia sa cu primitiva exit. Primitiva
exec(p) permite unui proces să schimbe contextul, apelând 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.
Ilustrăm folosirea primitivelor fork şi exec:
...
id := fork();
if id = 0 then -- eu sunt fiul
exec(p) -- programul fiului
else -- eu sunt părintele
if id = -1 then -- nil : creare imposibilă
<tratare eroare>
else
<programul părintelui>
endif
endif
Primitiva wait este utilizată după cum urmează:
id := wait(cod) -- blocare până la terminarea programului unuia dintre fii
... -- id = numărul programului fiu terminat, cod = cauza terminării ◄

1.13. 3.6. Sincronizarea în Windows


Platforma pe 32 de biţi pune la dispoziţia programatorului instrumente evoluate pentru multiprogramare, atât la
nivelul unei mulţimi de lucrări, cât şi a unei lucrări singulare. Poate să apară întrebarea CÂND să fie utilizată
multiprogramarea în cadrul unei singure aplicaţii. Răspunsul este foarte simplu: atunci când dorim ca mai multe
fragmente de cod să fie executate simultan (pseudosimultan, dacă există mai multe fragmente decât procesoare). De
exemplu, dacă dorim ca unele activităţi să fie îndeplinite în regim de fond sau programul să continue să reacţioneze
la unele evenimente exterioare în timpul îndeplinirii unor calcule foarte „costisitoare”. Pot fi aduse şi alte exemple.
1.13.1. 3.6.1. Procese şi fire
Numim proces în Windows o instanţă (un exemplar) a programului, încărcat în memoria operativă. Această
instanţă poate crea fire (thread) - secvenţe de instrucţiuni, care urmează a fi executate. Este important să se înţeleagă
că în Windows anume firele sunt executate (nu procesele!), fiecărui proces fiindu-i asociat minimum un fir, numit
firul principal al aplicaţiei.
Deoarece în realitate există mult mai multe fire decât procesoare fizice, firele vor fi executate secvenţial, timpul
de procesor repartizându-se între fire. Dar viteza mare de execuţie şi frecvenţa mare de comutare a firelor lasă
impresia unei execuţii paralele a acestora.
Fără comentarii suplimentare subliniem, că stările elementare ale unui fir sunt aceleaşi ca şi în cazul proceselor:
ales (exe), eligibil (ready) şi blocat (wait). Starea blocat este asociată aşteptării unui anume eveniment. Când
evenimentul se produce firul este trecut în mod automat în starea eligibil. De exemplu, dacă un fir execută anumite
calcule, iar un alt fir este obligat să aştepte rezultatele pentru a le salva pe disc, al doilea fir ar putea utiliza un ciclu
de tipul "while(!isCalcFinished ) continue;". Este însă simplu să ne convingem, că în timpul execuţiei acestui ciclu
procesorul este ocupat 100% - este cazul aşteptării active. Astfel de cicluri trebuie evitate prin utilizarea
mecanismelor de sincronizare.
În cadrul sistemului de operare Windows există două tipuri de fire – fire interactive, care execută un ciclu
propriu de prelucrare a mesajelor (de exemplu, firul principal al unei aplicaţii) şi fire de lucru, care sunt funcţii
simple. În ultimul caz execuţia firului se încheie atunci când calculele, generate de funcţia respectivă, iau sfârşit.
Merită atenţie şi modalitatea organizării ordinii de execuţie a firelor. Algoritmul FIFO este departe de a fi cel
mai optimal. În Windows toate firele sunt ordonate conform priorităţilor. Prioritatea unui fir este un număr întreg de
la 0 la 31 şi este determinată de prioritatea procesului, care a generat firul şi prioritatea relativă a firului. În acest
mod se ajunge la o flexibilitate maximă, fiecărui fir punându-i-se la dispoziţie – în caz ideal – exact atâta timp de
procesor, cât are nevoie.
Prioritatea firului poate fi modificată dinamic. Firele interactive, care au prioritatea Normal, sunt executate în
mod deosebit de către sistem, prioritatea acestor fire fiind majorată, atunci când procesul, care le-a generat, se află în
planul central (foreground). În rezultat, aplicaţia curentă reacţionează mai repede la cererile utilizatorului.
1.13.2. 3.6.2. Necesitatea sincronizării
Când un proces este creat în mod automat este creat firul principal al acestuia. Acest fir poate crea în timpul
execuţiei alte fire, care la fel pot crea fire noi şi aşa mai departe. Timpul de procesor fiind repartizat între fire,
fiecare fir „lucrează” în mod independent.
Toate firele unui proces împart resursele comune, de exemplu, spaţiul de adrese al memoriei operative sau
fişierele deschise. Aceste resurse aparţin întregului proces, deci şi fiecărui fir. Fiecare fir poate să utilizeze aceste
resurse fără nici un fel de restricţii. În realitate, din cauza multitaskingului controlat (preemptive multitasking - la
orice moment de timp sistemul poate întrerupe execuţia unui fir şi transmite controlul unui alt fir), se poate întâmpla
ca un fir să nu fi terminat încă lucrul cu o resursă comună oarecare, iar sistemul să treacă la un alt fir, care utilizează
aceeaşi resursă. Rezultatele pot fi imprevizibile. Asemenea conflicte se pot produce şi în cazul unor fire, care aparţin
chiar unor procese diferite. Problema poate să apară întotdeauna când două sau mai multe fire folosesc o resursă
comună. Este necesar un mecanism de coordonare a lucrului firelor cu resurse comune. În Windows acest mecanism
se numeşte sincronizarea firelor (thread synchronization).
1.13.3. 3.6.3. Structura mecanismului de sincronizare în Windows
Mecanismul de sincronizare este un set de obiecte ale sistemului de operare Windows, create şi gestionate
program, comune pentru toate firele sistemului (unele pentru firele unui singur proces) şi utilizate pentru
coordonarea accesului la resurse. În calitate de resurse pot fi toate obiectele, care pot fi accesate de două şi mai
multe fire – un fişier pe disc, un port, un articol al unei baze de date, o variabilă globală a unui program, accesibilă
firelor unui singur procesor, un obiect al dispozitivului interfeţei grafice (Graphic Device Interface), etc.
De obicei, sunt utilizate mecanismele (obiectele) de sincronizare, introduse mai sus: excluderea mutuală (mutex),
secţia critică (critical section), eveniment memorizat (event) şi semaforul (semaphore), fiecare realizând metoda
proprie de sincronizare. În calitate de obiecte sincronizate pot fi chiar procesele sau firele (când un fir aşteaptă
terminarea execuţiei unui proces sau a unui alt fir), fişierele, dispozitivele de comunicaţie, etc.
Sensul mecanismelor de sincronizare constă în faptul, că fiecare poate să fie în starea set. Pentru fiecare
mecanism de sincronizare această stare poate să aibă sens propriu. Firele pot să testeze starea curentă a
mecanismului de sincronizare şi/sau să aştepte modificarea acestei stări, coordonându-şi în acest fel acţiunile proprii.
Este foarte important să se sublinieze, că atunci când un fir lucrează cu mecanismele de sincronizare (le creează, le
modifică starea) sistemul nu întrerupe execuţia firului, până nu va fi terminată această acţiune, adică toate operaţiile
finite din mecanismele de sincronizare sunt atomare (nu pot fi întrerupte).
Menţionăm de asemenea, că nu există nici o legătură reală între mecanismele de sincronizare şi resurse.
Mecanismele de sincronizare nu pot interzice accesul nedorit la o resursă, ele doar indică firului momentul când
acesta poate accesa resursa, sau când acesta trebuie să aştepte (de exemplu, un semafor la o intersecţie doar indică
când este permisă trecerea. Cineva poate trece „pe roşu”, dar consecinţele pot fi grave).
1.13.4. 3.6.4. Administrarea obiectelor de sincronizare în Windows
Crearea unui obiect de sincronizare se produce prin apelarea unei funcţii speciale din WinAPI de tipul Create…
(de exemplu, CreateMutex). Acest apel returnează descriptorul obiectului (handle), care poate fi folosit de toate
firele procesului dat. Un obiect de sincronizare poate fi accesat şi dintr-un alt proces, dacă acest proces a moştenit
descriptorul obiectului dat, sau folosind funcţia de deschidere a unui obiect (Open…). Obiectului, dacă el nu este
destinat doar pentru uz intern (în interiorul unui singur proces), în mod obligator i se acordă un nume unic. Nu poate
fi creat un eveniment memorizat şi un semafor cu acelaşi nume.
Folosind descriptorul poate fi determinată starea curentă a obiectului cu ajutorul funcţiilor de aşteptare. De
exemplu, funcţia WaitForSingleObject(x, y) cu doi parametri (primul este descriptorul obiectului, iar al doilea –
timpul de aşteptare în ms) returnează WAIT_OBJECT_0, dacă obiectul se află în starea set (adică nu aparţine nici
unui fir şi poate fi utilizat pentru sincronizare), WAIT_TIMEOUT – dacă a expirat timpul de aşteptare şi
WAIT_ABANDONED, dacă obiectul de sincronizare nu a fost eliberat înainte ca firul, care-l comanda, să se fi
terminat. Dacă timpul de aşteptare este egal cu 0, atunci funcţia returnează rezultatul imediat, în caz contrar, aşteaptă
intervalul de timp indicat. În cazul în care starea obiectului de sincronizare va deveni set până la expirarea acestui
timp, funcţia returnează WAIT_OBJECT_0, altfel - WAIT_TIMEOUT.
Dacă în parametrul timp este indicată constanta simbolică INFINITE, funcţia va aştepta până când starea
obiectului va deveni set, fără vre-o restricţie.
Starea mai multor obiecte poate fi aflată cu ajutorul funcţiei WaitForMultipleObjects. Pentru încheierea lucrului
cu un obiect de sincronizare şi eliberarea descriptorului se apelează funcţia CloseHandle. Este important de ştiut,
că apelarea unei funcţii de aşteptarea blochează firul curent, adică atâta timp cât un fir se află în starea de aşteptare
el nu are acces la procesor.
1.13.4.1. 3.6.4.1. Excluderea mutuală
Cum a fost menţionat deja, mecanismele de excludere mutuală (mutex-ele, de la MUTual EXclusion) permit
coordonarea accesului la o resursă partajată. Starea set a obiectului corespunde momentului de timp în care obiectul
nu aparţine nici unui fir şi poate fi „utilizat”, iar starea reset – momentului când un fir oarecare controlează deja
mutex-ul. Accesarea va fi permisă doar după eliberare.
Pentru a lega mutex-ul de firul curent trebuie apelată una din funcţiile de aşteptare. Firul, căruia îi aparţine
mutex-ul, îl poate „ocupa” de mai multe ori, fără autoblocare, însă mai apoi acesta va trebui eliberat tot de atâtea ori
cu ajutorul funcţiei ReleaseMutex.
1.13.4.2. 3.6.4.2. Evenimentele
Obiectele-evenimente sunt utilizate pentru a informa firele, care sunt în aşteptare, despre producerea unui
eveniment. În Windows există două tipuri de evenimente – cu resetare manuală şi automată. Resetarea manuală se
execută cu funcţia ResetEvent. Aceste evenimente sunt folosite pentru informarea mai multor fire, iar
evenimentele cu resetare automată sunt utilizate pentru informarea unui anumit fir, celelalte rămânând în aşteptare.
Funcţia CreateEvent crează un obiect-eveniment, funcţia SetEvent setează evenimentul în starea set, iar
funcţia ResetEvent resetează evenimentul. Funcţia PulseEvent setează evenimentul, iar după semnalizarea firelor,
care erau în aşteptare (toate în cazul resetării manuale şi doar unul la resetarea automată), resetează obiectul. Dacă
nu există fire în aşteptare, PulseEvent doar resetează obiectul, fără semnalizare.
1.13.4.3. 3.6.4.3. Semafoarele
Un obiect-semafor este în ultimă instanţă un mutex cu contor. Acest obiect permite să fie „ocupat” de un număr
anume de fire, după care „ocuparea” va fi posibilă numai dacă unul din fire va „elibera” semaforul. Semafoarele
sunt utilizate pentru a limita numărul de fire, care lucrează simultan cu resursa. La iniţializare se specifică numărul
maxim de fire, la fiecare „ocupare” valoarea contorului semaforului scade.
1.13.4.4. 3.6.4.4. Secţiunile critice
Obiectul-secţiune critică permite programatorului să evidenţieze un fragment de cod în care firul obţine acces la
o resursă partajată, preîntâmpinând utilizarea resursei de mai mulţi utilizatori. Pentru a utiliza resursa firul va intra
mai întâi în secţiunea critică (apelarea funcţiei EnterCriticalSection). Dacă intrarea a avut loc, nici un alt fir nu va
avea acces la aceeaşi secţiune critică, execuţia acestuia fiind suspendată. Reluarea se va produce în momentul în care
primul fir părăseşte secţiunea critică (funcţia LeaveCriticalSection). Diferenţa de mutex constă în faptul că
secţiunea critică este utilizată numai pentru firele unui singur proces.
Cu ajutorul funcţiei TryEnterCriticalSection se poate stabili, dacă secţiunea critică este liberă. Utilizând această
funcţie, un proces, fiind în aşteptarea resursei, poate să nu se blocheze, îndeplinind operaţii utile.
1.13.4.5. 3.6.4.5. Protejarea accesării variabilelor
Există o serie de funcţii, care permit lucrul cu variabilele globale ale tuturor firelor, fără a ne preocupa de
sincronizare, deoarece aceste funcţii singure rezolvă problema sincronizării. Aceste funcţii sunt
InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd şi
InterlockedCompareExchange. De exemplu, funcţia InterlockedIncrement incrementează valoarea unei
variabile pe 32 biţi cu o unitate.
1.13.4.6. 3.6.4.6. Sincronizarea în Microsoft Fundation Classes
Biblioteca MFC conţine 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 consultaţi 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 aşa 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
conţine toate metodele necesare. Clasa este proiectată în aşa mod, ca metodele ei să rezolve problema sincronizării,
adică în cadrul aplicaţiei să apară ca o simplă clasă. Obiectul de sincronizare MFC este inclus în această clasă în
calitate de membru privat şi toate funcţiile clasei, care realizează accesarea resursei, îşi coordonează lucrul cu acest
membru.
Utilizând funcţiile Lock şi Unlock clasele de sincronizare MFC pot fi utilizate direct, iar în mod indirect – prin
funcţiile CSingleLock şi CmultiLock.
1.13.5. 3.6.5. Exemplu de sincronizare în Windows
Prezentăm un exemplu simplu de lucru cu obiectul de sincronizare mutex [22]. Pentru simplitate a fost utilizată
o aplicaţie Win32 de consolă, deşi nu este obligator.

#include <windows.h>
#include <iostream.h>

void main()
{
DWORD res;

// creăm obiectul excludere mutuală


HANDLE mutex = CreateMutex(NULL, FALSE, "NUME_APLICATIE-MUTEX01");
// dacă obiectul există deja, CreateMutex va returna descriptorul obiectul existent,
// iar GetLastError va returna ERROR_ALREADY_EXISTS

// timp de 20 s încercăm să ocupăm obiectul


cout<<"Încerc să ocup obiectul...\n"; cout.flush();
res = WaitForSingleObject(mutex,20000);
if (res == WAIT_OBJECT_0) // dacă ocupare s-a terminat cu succes
{
// aşteptăm 10 s
cout<<"L-am prins! Aşteptare 10 secunde...\n"; cout.flush();
Sleep(10000);

// eliberăm obiectul
cout<<"Acum eliberăm obiectul\n"; cout.flush();
ReleaseMutex(mutex);
}

// închidem descriptorul
CloseHandle(mutex);
}
Pentru a controla modul de funcţionare a mecanismului de excludere mutuală se vor lansa două instanţe ale
acestei aplicaţii. Prima instanţă va ocupa obiectul imediat şi-l va elibera doar peste 10 secunde. Numai după aceasta
instanţa a doua va reuşi să ocupe obiectul. În acest exemplu obiectul de sincronizare este folosit pentru sincronizarea
proceselor, din care cauză în mod obligatoriu trebuie să aibă nume.
1.13.6. 3.6.6. Utilizarea secţiunilor critice în Windows
În acest caz secţiunile critice sunt utilizate pentru a permite la un moment de timp dat accesul la unele date
importante unui singur fir al aplicaţiei, celelalte fire fiind blocate. De exemplu, fie variabila m_pObject şi câteva
fire, care apelează metodele obiectului referit de m_pObject. Presupunem că această variabilă din timp în timp îşi
poate schimba valoarea, valoarea 0 nu este interzisă. Fie următorul fragment de cod:

// Firul #1
void Proc1()
{
if (m_pObject)
m_pObject->SomeMethod();
}

// Firul #2
void Proc2(IObject *pNewObject)
{
if (m_pObject)
delete m_pObject;
m_pObject = pNewObject;
}

În acest exemplu există pericolul potenţial de apelare m_pObject->SomeMethod() după ce obiectul a fost
distrus cu ajutorul delete m_pObject, deoarece în sistemele de operare cu multitasking controlat execuţia oricărui
fir poate fi întreruptă în cel mai neconvenabil (pentru firul dat) moment şi să înceapă execuţia unui alt fir. Pentru
exemplul nostru momentul nedorit este atunci când firul #1 a testat deja m_pObject, dar nu a reuşit să apeleze
SomeMethod(). Execuţia firului #1 a fost întreruptă şi a început execuţia firului #2. Iar firul #2 reuşise deja să
apeleze destructorul obiectului. Ce se va întâmpla atunci când firului #1 i se va acorda din nou timp de procesor şi
va fi apelat SomeMethod() al unui obiect deja inexistent? Greu de presupus.
Aici ne vin în ajutor secţiunile critice. Să modificăm exemplul de mai sus.

// Firul #1
void Proc1()
{
::EnterCriticalSection(&m_lockObject);
if (m_pObject)
m_pObject->SomeMethod();
::LeaveCriticalSection(&m_lockObject);
}

// Firul #2
void Proc2(IObject *pNewObject)
{
::EnterCriticalSection(&m_lockObject);
if (m_pObject)
delete m_pObject;
m_pObject = pNewObject;
::LeaveCriticalSection(&m_lockObject);
}

Fragmentul de cod inclus între ::EnterCriticalSection() şi ::LeaveCriticalSection() cu una şi aceeaşi


secţiune critică în calitate de parametru nu va fi executat nici o dată în mod paralel. Aceasta înseamnă, că dacă firul
#1 a reuşit să „acapareze” secţiunea critică m_lockObject, încercarea firului #2 să intre în aceeaşi secţiune critică
va conduce la blocarea acestuia până în momentul când firul #1 va elibera m_lockObject prin apelul
::LeaveCriticalSection(). Invers, dacă firul #2 a accesat secţiunea critică înaintea firului #1, acesta din urmă va
fi nevoit “să aştepte”, înainte de a începe lucrul cu m_pObject.
Menţionăm, că secţiunile critice nu sunt obiecte ale nucleului sistemului de operare. Practic, tot lucrul cu
secţiunile critice are loc in procesul care le-a creat. Din aceasta rezultă, că secţiunile critice pot fi utilizate numai
pentru sincronizare în cadrul unui proces. Să cercetăm mai aprofundat o secţiune critică.
1.13.6.1. 3.6.6.1. Structura RTL_CRITICAL_SECTION
Este definită după cum urmează:

typedef struct _RTL_CRITICAL_SECTION


{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // Folosit de sistemul de operare
LONG LockCount; // Contorul de utilizări
LONG RecursionCount; // Contorul accesării repetate din firul utilizatorului
HANDLE OwningThread; // ID firului utilizatorului (unic)
HANDLE LockSemaphore; // Obiectul nucleului folosit pentru aşteptare
ULONG_PTR SpinCount; // Numărul de cicluri goale înaintea apelării nucleului
}
RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

Câmpul LockCount este incrementat cu o unitate la fiecare apelare ::EnterCriticalSection() şi decrementat


cu unu la fiecare apel ::LeaveCriticalSection(). Acesta este primul (adesea şi unicul) control pentru testarea
secţiunii critice. Dacă după incrementare în acest câmp avem 0, aceasta înseamnă că până la acest moment nu au
avut loc apeluri impare ::EnterCriticalSection() din alte fire. În acest caz datele “păzite” de această secţiune
critică pot fi utilizate în regim monopol. Adică, dacă secţiunea critică este folosită intensiv de un singur fir, atunci
::EnterCriticalSection() se transformă practic în ++LockCount, iar ::LeaveCriticalSection() în--LockCount.
Aceasta înseamnă, că folosirea a mai multor mii de secţiuni critice într-un singur proces nu va conduce la creşterea
substanţială a utilizării nici a resurselor de sistem, nici a timpului de procesor. Ca şi concluzie: nu se va face
economie pe contul secţiunilor critice. Mult totuna nu vom putea economisi.
În câmpul RecursionCount este păstrat numărul de apeluri repetate ::EnterCriticalSection() din unul şi
acelaşi fir. Dacă se va apela ::EnterCriticalSection() din unul şi acelaşi fir de mai multe ori, toate apelurile vor
avea succes. Adică următorul cod nu se va opri pentru totdeauna în cel de-al doilea apel
::EnterCriticalSection(), ci va merge până la capăt.
// Firul #1
void Proc1()
{
::EnterCriticalSection(&m_lock);
// ...
Proc2()
// ...
::LeaveCriticalSection(&m_lock);
}

// Încă Firul #1
void Proc2()
{
::EnterCriticalSection(&m_lock);
// ...
::LeaveCriticalSection(&m_lock);
}
Într-adevăr, secţiunile critice sunt destinate să protejeze datele la accesarea din câteva fire. Utilizarea multiplă a
uneia şi aceeaşi secţiuni critice de un singur fir nu va genera eroare, ceea ce este normal. Trebuie doar să avem grijă
ca numărul de apeluri ::EnterCriticalSection() şi ::LeaveCriticalSection() să coincidă şi totul va fi în
regulă.
Câmpul OwningThread conţine 0 în cazul secţiunilor critice libere sau identificatorul unic al firului-posesor.
Acest câmp este testat, dacă la apelul ::EnterCriticalSection() câmpul LockCount, după incrementarea cu o
unitate, a devenit mai mare ca 0. Dacă OwningThread coiincide cu identificatorul unic al firului curent, atunci
valoarea lui RecursionCount creşte cu o unitate şi ::EnterCriticalSection() este returnat imediat. În caz contrar
::EnterCriticalSection() va aştepta până firul, care posedă secţiunea critică, va apela ::LeaveCriticalSection() de
un număr necesar de ori. Câmpul LockSemaphore este folosit, dacă este necesar să se aştepte până secţiunea
critică este eliberată. Dacă LockCount este mai mare ca 0 şi OwningThread nu coiincide cu identificatorul unic
al firului curent, atunci firul blocat crează un obiect al nucleului – eveniment – şi apelează
::WaitForSingleObject(LockSemaphore). Firul posesor, după decrementarea câmpului RecursionCount,
testează acest câmp şi dacă valoarea lui este 0, iar LockCount este mai mare ca 0, constată că există minimum un fir
în aşteptarea momentului când LockSemaphore se va afla în starea “sosit”. Pentru aceasta firul-posesor apelează
::SetEvent() şi un fir oarecare (doar unul) dintre cele blocate este deblocat şi are acces la datele critice.
Windows NT/2000 generează o excepţie, dacă încercarea de a crea un eveniment a eşuat. Aceasta este just atât
pentru funcţiile ::Enter/LeaveCriticalSection(), cât ş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, funcţiile ::Enter/LeaveCriticalSection() în loc să genereze o excepţie,
dacă nu pot crea un eveniment propriu, vor folosi un obiect global, unic pentru toţi, creat în avans. În aşa mod, în caz
de deficienţă catastrofală a resurselor de sistem, programul sub comanda lui WindowsXP “şchiopătează” un timp
oarecare mai departe. Într-adevăr, este foarte complicat să scrii programe, care ar fi în stare să continue lucrul după
ce ::EnterCriticalSection() a generat o excepţie. De obicei, chiar dacă programatorul a prezis o astfel de
situaţie, nu se ajunge mai departe de generarea unui mesaj de eroare şi stoparea forţată a execuţiei programului.
Drept rezultat, WindowsXP ignorează bitul cel mai semnificativ al câmpului LockCount.
În sfârşit, câmpul LockCount. Acest câmp este folosit doar în sistemele multiprocesorale. În sistemele
monoprocesorale, dacă secţiunea critică este ocupată de un fir oarecare, putem doar comuta comanda la această
secţiune şi trece în aşteptarea evenimentului nostru. În sistemele multiprocesorale există o alternativă: să executăm
de câteva ori un ciclu vid, testând de fiecare dată dacă secţiunea critică nu a fost eliberată (aşteptare activă). Dacă
după un număr de SpinCount ori secţiunea critică nu a fost eliberată, trecem în starea blocat. Această modalitate
este mult mai eficientă decât comutarea la planificatorul nucleului şi înapoi. În WindowsNT/2000 bitul cel mai
semnificativ al acestui câmp indică dacă obiectul nucleului, variabila handle a căruia se află în câmpul
LockSemaphore, trebuie creat anticipat. Dacă pentru aceasta nu dispunem de resurse de sistem suficiente, sistemul
generează o excepţie şi programul îşi poate „micşora” posibilităţile funcţionale sau chiar termina execuţia.
1.13.6.2. 3.6.6.2. Funcţii API pentru secţiunile critice
Descriem mai jos câteva funcţii din API pentru lucrul cu secţiunile critice.
Mai întâi funcţiile BOOL InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) şi BOOL
InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount).
Completează câmpurile structurii lpCriticalSection adresate. După apelare secţiunea critică este gata de lucru.
Iată pseudocodul funcţiei 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);
}

Funcţia DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD


dwSpinCount) setează valoarea câmpului SpinCount şi returnează valoarea precedentă a acestuia. Amintim, că
bitul cel mai semnificativ este responsabil de “legarea” evenimentului, folosit pentru aşteptarea accesului la
secţiunea critică dată. Pseudocodul funcţiei 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


secţiunea critică. Are următorul pseudocod:
VOID RtlDeleteCriticalSection(LPRTL_CRITICAL_SECTION pcs)
{
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 secţiunea critică. Dacă
secţiunea critică este ocupată de un alt fir, atunci ::EnterCriticalSection() va aştepta până 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ă secţiunea critică.
Pseudocodul este următorul:
VOID RtlLeaveCriticalSectionDbg(LPRTL_CRITICAL_SECTION pcs)
{
if (--pcs->RecursionCount)
::InterlockedDecrement(&pcs->LockCount);
else if (::InterlockedDecrement(&pcs->LockCount) >= 0)
RtlpUnWaitCriticalSection(pcs);
}
1.13.6.3. 3.6.6.3. Clase de secţiuni critice
Pentru o utilizare corectă a secţiunilor critice prezentăm mai jos codul claselor secţiunilor critice:
class CLock
{
friend class CScopeLock;
CRITICAL_SECTION m_CS;
public:
void Init() { ::InitializeCriticalSection(&m_CS); }
void Term() { ::DeleteCriticalSection(&m_CS); }

void Lock() { ::EnterCriticalSection(&m_CS); }


BOOL TryLock() { return ::TryEnterCriticalSection(&m_CS); }
void Unlock() { ::LeaveCriticalSection(&m_CS); }
};

class CAutoLock : public CLock


{
public:
CAutoLock() { Init(); }
~CAutoLock() { Term(); }
};

class CScopeLock
{
LPCRITICAL_SECTION m_pCS;
public:
CScopeLock(LPCRITICAL_SECTION pCS) : m_pCS(pCS) { Lock(); }
CScopeLock(CLock& lock) : m_pCS(&lock.m_CS) { Lock(); }
~CScopeLock() { Unlock(); }
void Lock() { ::EnterCriticalSection(m_pCS); }
void Unlock() { ::LeaveCriticalSection(m_pCS); }
};

Clasele CLock şi CAutoLock sunt utilizate, de obicei, pentru sincronizarea accesării variabilelor clasei, iar
CScopeLock este destinat, în special, pentru a fi utilizată în proceduri. Compilatorul singur va avea grijă să apeleze
::LeaveCriticalSection() prin intermediul destructorului. Urmează un exemplu de folosire a CScopeLock.

CAutoLock m_lockObject;
CObject *m_pObject;
void Proc1()
{
CScopeLock lock(m_ lockObject); // apelarea lock.Lock();
if (!m_pObject)
return; // apelarea lock.Unlock();
m_pObject->SomeMethod();

// apelarea lock.Unlock();
}
1.13.6.4. 3.6.6.4. Depanarea secţiunilor critice
Depanarea secţiunilor critice este o ocupaţie foarte interesantă, dar şi dificilă. Poţi căuta ore şi chiar zile în şir
cauza apariţiei unei probleme. Erorile, legate de secţiunile critice sunt de două tipuri: de realizare şi de arhitectură.
Erorile de realizare pot fi depistate relativ uşor şi, de regulă, sunt generate de utilizarea incorectă (lipsa perechii) a
apelurilor ::EnterCriticalSection() şi ::LeaveCriticalSection(). Urmează un fragment de cod în care este omis
apelul ::EnterCriticalSection().
// În procedură se presupune, că m_lockObject.Lock(); a fost deja apelat
void Pool()
{
for (int i = 0; i < m_vectSinks.size(); i++)
{
m_lockObject.Unlock();
m_vectSinks[i]->DoSomething();
m_lockObject.Lock();
}
}
Apelul ::LeaveCriticalSection() fără ::EnterCriticalSection() va conduce la faptul că chiar primul
apel ::EnterCriticalSection() va stopa execuţia firului pentru totdeauna.
În fragmentul de cod de mai jos lipseşte 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 întâmpla ca
::EnterCriticalSection() să fie apelată fără iniţializarea secţiunii 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 aşa-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 când 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 fără 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 îmbrăţişarea fatală (deadlock, v.5.1.3.3), când două fire
încearcă să acceseze două şi mai multe secţiuni critice. Prezentăm 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 secţiuni critice. Este greu de presupus că codul de mai jos a fost
scris de un programator sănătos:

CRITICAL_SECTION sec1;
CRITICAL_SECTION sec2;
// ...
sec1 = sec2;
Din atribuirea de mai sus este dificil să obţii foloase. Dar fragmentul următor poate fi adesea întâlnit:

struct SData
{
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 câmpurilor şi,
în rezultat, variabila m_lock a fost copiată, iar anume în acest moment ea fusese accesată dintr-un alt fir şi valoarea
câmpului LockCount este ≥ 0. După apelul ::LeaveCriticalSection() din acel fir valoarea câmpului LockCount
pentru variabila iniţială m_lock a fost decrementată cu o unitate, pentru variabila copiată rămânând fără schimbare.
Ca rezultat, orice apel ::EnterCriticalSection() nu se va întoarce niciodată în acest fir. Va rămâne pentru totdeauna
în aşteptare. Pot exista situaţii mult mai complicate.
Fie un obiect care apelează metodele unui alt obiect, obiectele aflându-se în fire diferite. Apelurile se fac în mod
sincron, adică obiectul #1 transmite execuţia firului obiectului #2, apelează metoda şi se va comuta înapoi la firul
său. Execuţia firului #1 va fi suspendată pentru toată perioada de execuţie a firului obiectului #2. Presupunem acum,
că obiectul #2 apelează o metodă a obiectului #1 din firul său. Controlul va fi întors obiectului #1, dar din firul
obiectului #2. Dacă obiectul #1 apelase metoda obiectului #2, intrând într-o secţiune critică oarecare, atunci la
apelarea metodei obiectului #1 acesta se va bloca pe sine însuşi la intrarea repetată în aceeaşi secţiune critică.
Fragmentul de cod care urmează vine să exemplifice această situaţie.
// Firul #1
void IObject1::Proc1()
{
// Intrăm în secţiunea critică a obiectului #1
m_lockObject.Lock();
// Apelăm 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()
{
// Apelăm metoda obiectului #1 din firul obiectului #2
m_pObject1->Proc2();
}

// Firul #2
void IObject1::Proc2()
{
// Încercăm să intrăm în secţiunea 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 intrări în secţiunile critice. Primul exemplu din acest subparagraf
va fi rescris astfel:
// Firul #1
void Proc1()
{
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 rămas ca şi mai înainte sincronizat, dar apelul SomeMethod() are loc în afara secţiunii
critice. Situaţia a fost aproape rezolvată. Mai există o problemă mică. Să cercetăm 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 secţiunii


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 preîntâmpina aceasta vom
folosi aceeaşi 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();
}
Potenţial, acum ultimul apel IObject2::Release() va fi executat după părăsirea secţiunii critice. Iar atribuirea
unei valori noi este sincronizată ca şi mai înainte cu apelul IObject2::SomeMethod() din firul #1.
Concluzii:
 secţiunile critice sunt executate relativ repede şi nu cer multe resurse de sistem;
 pentru sincronizarea accesării a mai multor variabile independente este mai bine să fie utilizate câteva
secţiuni critice (nu una pentru toate variabilele);
 codul unei secţiuni critice va fi redus la minimum;
 nu este recomandat să fie apelate metode ale unor obiecte “străine” dintr-o secţiune critică.

1.14. 3.7. Exerciţii la capitolul 3


Exerciţiul 3.1. Să se exprime în termeni de procese executarea programelor în corutine (v.2.1). Precizaţi contextul
şi modul de sincronizare a acestor procese.
Exerciţiul 3.2. În programele cititorului şi redactorului, prezentate în 3.4.2.2, cititorii pot ocupa resursa permanent,
blocând pentru un timp redactorii.
1) Propuneţi o soluţie cu prioritate pentru redactori în care accesarea resursei ar fi interzisă unor noi cititori, dacă
există un redactor în aşteptare.
2) Soluţia de mai sus prezintă un risc de aşteptare pentru un timp nedefinit a cititorilor. Specificaţi un mod de
funcţionare care ar trata într-o manieră echitabilă cititorii şi redactorii şi elaboraţi programul monitorului
respectiv.
Exerciţiul 3.3. Buticul unui bărbier este organizat conform schemei de mai jos:

 Uşa de intrare şi cea de comunicare dintre sala de aşteptare şi salonul bărbierului nu permit intrarea simultană
a mai multor clienţi (doar unul singur poate trece prin fiecare uşa la un moment de timp dat). Aceste uşi au un
mecanism culisant, care face ca una din ele să fie închisă, atunci când cealaltă este deschisă. Bărbierul invită
următorul client, atunci când termină deservirea clientului curent. Dacă sala de aşteptare este goală el doarme.
Dacă un client găseşte bărbierul dormind, îl trezeşte, altfel îşi aşteaptă rândul în sala de aşteptare.
Reprezentând bărbierul şi clienţii prin procese, programaţi funcţionarea acestui sistem cu ajutorul unui monitor.
Exerciţiul 3.4. O linie de cale ferată, care leagă două oraşe A şi B, conţine o porţiune cu un singur drum. Vom
reprezenta trenurile prin procese conform schemei de mai jos:
trenurile A → B trenurile B → A
... ...
sens_unic.intrare_vest sens_unic.intrare_est
<traversare sens unic> <traversare sens unic>
sens_unic.ieşire_est sens_unic.ieşire_vest
... ...

B
A
Rolul monitorului sens_unic este să garanteze că toate trenurile, angajate la un moment de timp dat pe porţiunea
cu un singur drum, circulă în aceeaşi direcţie.
1) Să se elaboreze programul monitorului sens_unic, presupunând că numărul trenurilor prezente pe porţiunea
cu un singur drum, nu este limitat.
2) Aceeaşi problema, dar pentru un număr limitat N de trenuri.
3) Examinaţi în ambele cazuri riscul de blocare şi mijloacele de evitare ale acestora.
Exerciţiul 3.5. Cinci filozofi stau la o masă conform schemei de mai jos.

Filosof

Filosof Filosof

Filosof Filosof

Fiecare, schematic prezentat printr-un proces pi, (i=0..4), se comportă conform programului:
ciclu
gândire -- nu necesită vre-o resursă
mâncare -- sunt necesare două (ambele) beţişoare (pentru orez)
endciclu
Fiecare filozof poate utiliza doar cele două furculiţe, plasate de o parte şi alta a farfuriei sale, la terminare
întorcându-le în poziţia iniţială. Durata celor două faze este arbitrară, dar finită.
1) Să se arate, că alocarea furculiţelor poate conduce la blocarea sistemului (blocarea a două sau mai multe
procese).
2) Să se elaboreze un monitor furculiţe, care ar realiza alocarea furculiţelor fără blocare. Apelarea procedurii
mâncare va fi înlocuită în programul procesului pi prin secvenţa
furculiţe.luare(i)
mâncare
furculiţe.eliberare(i)
Apelarea procedurii luare îl blochează pe apelant pentru perioada de timp în care cele două furculiţe adiacente
farfuriei sale sunt utilizate de către vecinii săi. Apelarea procedurii eliberare eliberează două furculiţe.
3) Examinaţi posibilitatea de blocare indefinită a unui proces şi propuneţi soluţii pentru a o evita.
Rezolvarea punctului 1. Blocare reciprocă
Filozoful i

gândire();
P(furculiţa i);
P(furculiţa (i+1) mod 5);
mâncare();
V(furculiţa i);
V(furculiţa (i+1) mod 5);
Dacă fiecare filozof va lua în acelaşi moment de timp furculiţa din partea sa dreaptă va avea loc o blocare reciprocă.
Rezolvarea punctului 2.
Filozoful i luare_furculiţă(i) întoarcer_furculiţă(i)
gândire(); P(mutex); P(mutex);
luare_furculiţă(i); stare[i] = FOAME; stare[i] = GÂNDIRE;
mâncare(); test(i); test(STÂNGA);
întoarcere_furculiţă(i); V(mutex); test(DREAPTA);
P(s[i]); V(mutex);
test(i)
if(stare[i] = FOAME && stare[DREAPTA] != MÂNCARE && stare[STÂNGA] != MÂNCARE) then
stare[i] = MÂNCARE;
V(s[i]);
Exerciţiul 3.6. Specificaţi şi elaboraţi cu ajutorul unui monitor două primitive emitere şi recepţie, care realizează
comunicarea între două procese via un tampon de capacitatea unui singur mesaj cu sincronizare prin metoda rendez-
vous.
Exerciţiul 3.7. Programul de mai jos reprezintă două procese, care funcţionează conform schemei producător-
consumator şi sincronizate prin aşteptare activă.
producător consumator
ciclu ciclu
test_c: if n=0 then
generare(mesaj);
test_p: if n=N then go to test_c
go to test_p endif;
endif; n:=n-1;
n:=n+1; ieşire(mesaj);
intrare(mesaj); consumare(mesaj)
endciclu endciclu
Procedurile intrare şi ieşire sunt cele din 3.4.3.1.
Să se discute validitatea acestor programe şi să se aducă, eventual, modificări necesare, presupunând:
1) că operaţiile de consultare, incrementare şi decrementare a unei variabile în memorie sunt indivizibile,
2) că indivizibile sunt doar încărcarea, ordonarea, incrementare şi decrementarea unui registru; operaţiile de
comparare, incrementare şi decrementare a unei variabile necesită încărcarea sa într-un registru.
Exerciţiul 3.8. Programul de mai jos reprezintă două procese, care funcţionează conform schemei producător-
consumator şi sincronizate prin aşteptare activă.
producător consumator
ciclu ciclu
generare(mesaj); test_c: if NC-NP0 then
test_p: if NP-NC  N then go to test_c
go to test_p endif;
endif; ieşire(mesaj);
intrare(mesaj); consumare(mesaj)
NP:=NP+1; NC:=NC+1
endciclu endciclu
Să se discute validitatea acestor programe. Ce reprezintă variabilele întregi NP şi NC şi care sunt valorile lor
iniţiale?
Exerciţiul 3.9. Un sistem constă din n cupluri de procese comunicante două câte două conform schemei
producător-consumator. Tampoanele sunt de lungime variabilă şi administrate dinamic. Atunci când un producător
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
aşteptare al tamponului său. Când un consunator a preluat un mesaj dintr-o casetă, el restituie caseta rezervei de casete.
Iniţial rezerva conţine N casete, fiecare fiind identificată printr-o adresă adr[0..N-1]. Elaboraţi monitorul gestionării
rezervei de casete în următoarele două ipoteze:
1) Toate cuplurile de procese sunt tratate identic.
2) Prioritatea cuplului este cu atât mai mare cu cât mai puţine tampoane cuplul ocupă (această politică vizează
restabilirea echităţii pentru cazul unor procese care derulează cu viteze diferite).
Exerciţiul 3.10. Să se programeze cu ajutorul monitoarelor problema de culegere a rezultatelor de măsurare propus
în exerciţiul 2.2.
Exerciţiul 3.11. Modificaţi monitorul gestionării unui periferic asociindu-i un monitor de gardă, astfel ca tratarea
unei erori să fie declanşată, dacă transferul nu a fost terminat după un interval dat de timp.
Exerciţiul 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 fişier.
Presupunem că serverul dispune de informaţiile necesare citirii unei serii de linii, care formează fişierul f.
1) Elaboraţi programul serverului imprimantei.
2) Modificaţi acest program astfel încât clientului să i se permită:
 anularea unei imprimări cerute, înainte ca ea să fi avut loc,
 prevenirea clientului despre terminarea imprimării fişierului prin depozitarea unui mesaj într-o cutie
poştală specificată.
3) Modificaţi programul serverului pentru a permite mai multor imprimante să funcţioneze în paralel.
Exerciţiul 3.13. Vrem să construim un sistem de tratare a lucrărilor organizate pe loturi, analogic exerciţiului 9.15.
Lucrările sunt citite secvenţial de un cititor de cartele, rezultatele sunt listate la o imprimantă.
Sistemul este construit din trei procese: citire, care comandă cititorul, execuţie, care realizează execuţia unei lucrări
şi imprimare, care comandă imprimarea. Procesul imprimare este de forma:
ciclu
<citire cartelă>
<tratare cartelă>
endciclu
programul tratare cartelă poate conţine apelări <imprimare linie>.
1) Presupunem mai întâi că toate intrările-ieşirile sunt tamponate în memoria centrală. Realizaţi cu ajutorul
monitoarelor cooperarea celor trei procese.
2) Modificaţi programul precedent pentru a putea trata cazul erorilor în cursul execuţiei (presupunem, că procesul
citire poate detecta prima cartelă a unei lucrări).
3) Modificaţi programul precedent pentru a integra aici administrarea consolei operatorului (cu specificaţiile
exerciţiului 9.15).
4) Modificaţi programele precedente pentru a introduce administrarea tamponată a intrărilor şi ieşirilor
(exerciţiul precedent).
Exerciţiul 3.14. Clienţii unui server sunt împărţiţi în două clase, numite 1 şi 2. Cererile clienţilor din clasa 1 sunt
prioritare: serverul va trate o cerere din clasa 2 doar în lipsa de cereri de clasa 1.Cererile din aceeaşi clasa vor fi tratate
în ordinea sosirii. Un câmp al cererii identifică prioritatea.
1) Programaţi sincronizarea clienţilor şi a serverului utilizând monitoare. Să se folosească un fir de aşteptare
pentru fiecare clasă de cereri şi un proces dispecer pentru alimentarea acestor fire.
2) Schema de mai sus conţine un risc de blocare. Modificaţi specificaţiile şi realizarea pentru a elimina acest risc,
păstrând tratarea privilegiată a cererilor din clasa 1.

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