F.Evitarea impasului
In figura 3-8 am vazut ca impasul a fost evitat nu prin impunerea unor reguli arbitrare pentru procese, ci prin analizarea atenta a fiecarei solicitari de resursa pentru a vedea daca poate fi acordata in deplina siguranta. Se punu urmatoarea intrebare: exista un algoritm care sa evite intotdeauna impasurile prin luarea celei mai bune decizii? Raspunsul este un da hotarat – putem saevitam impasurile, dar numai daca anumite informatii sunt disponibile dinainte. In aceasta sectiune vom analiza modurile de evitare a impasurilo printr-o atenta alocare a resurselor.
Algoritmul bancherului pentru o singura resursa
Un algoritm de temporizare care poate sa evite impasurile il datoram lui Dijkstra (1965) si este cunoscut sub denumirea de algoritmul bancherului. Este conceput dupa modul in care un bancher dintr-un oras mic ar putea sa trateze un grup de client carora le-a acordat linii de credit. In figura 3-11(a) vedem patru clienti si fiecaruia dintre ei I-a fost acordat un anumit numar de unitati de credit (ex. o unitate este 1Kdolari). Bancherul stie ca nu toti clientii vor avea imediat nevoie de cantitatea maxima a creditului lor, astfel ca si-a rezervat pentru a ii servi doar 10 unitati in loc de 22 . (in aceasta analogie clientii reprezinta procesele, unitatile sunt, de exemplu, unitatile de banda si bancherul reprezinta sistemul de operare).
Clientii isi conduc afacerile facand din cand in cand cereri de imprumut. La un moment dat, apare situatia aratata in figura 3-11(b). o lista de clienti care arata banii deja imprumutati (unitati de banda deja alocate) si creditul maxim dispoibil in cazul lor (numarul maxim de unitati de banda de care vor avea nevoie mai tarziu) se numeste starea sistemului in ceea ce priveste alocarea resurselor.
Despre o stare se poate spue ca e sigura atunci cand exista o secventa de alte stari care face ca toti clientii sa primeasca imprumuturi in limitele creditului lor (toate procesele sa-si primeasca toate resursele si sa termine). Starea din figura 3-11(b) este sigura deoarece, cu doua unitati ramase, bancherul poate sa intarzie orice solicitare in afara de cea a lu Marvin, lasandu-l astfel pe Marvin sa termine si sa elibereze toate cele patru resurse pe care le detine. Cu inca patru unitati, bancherul poate sa acorde unitatile necesare fie Barbarei, fie Suzannei, etc.
fig 3-11. trei stari de alocare a resurei: (a) sigura; (b) sigura; (c) nesigura
Ganditi-va ce s-ar intampla daca ar fi aprobata o solicitare a Barbarei pentru una sau mai multe unitati (vezi fig. 3-11(b)). Am avea situatia din figura 3-11(c), care nu este sigura. Daca brusc, toti clientii si-ar solicita creditul maxim, bancherul nu ar putea satisface nici una din aceste cereri si s-ar ajunge la un impas. O stare nesigura nu duce neaparat la un impas deoarece se poate ca acel client sa nu aiba nevoie de intregul credit disponibil, dar bancherul nupoate sa se bazeze pe acest lucru.
Prin urmare, algoritmul bancherului presupune analizarea fiecarei cereri pe masura ce acestea apar si verificarea daca satisfacerea ei duce la o stare sigura. Daca da, cererea este satisfacuta; altfel, este amanata. Pentru a vedea daca o stare este sigura, bancherul verifica daca are suficiente resurse pentru a satisface clientul la maxim. Daca are aceste resurse, clientul este platit din nou si urmatorul client care este cel mai aproape de limitele sale este verificat, si asa mai departe. Daca toate imprumuturile pot fi replatite inseamna ca starea este sigura si cererea initiala poate fi satisfacuta.
Algoritmul de mai sus a fost descris in termenii unei sigure clase de resurse (ex. doar unitati de banda sau doar imprimante, dar nu ambele). In figura 3-12 vedem un model pentru a trata cu doua procese si cu doua resurse, de exemplu o imprimanta si un plotter. Axa orizontala reprezinta numarul de instrunctiuni executate de procesul A. axa verticala reprezinta numarul de instrunctiuni executate de procesul B. La momentul I1 A solicita o imprimanta; la momentul I2 are nevoie de un plotter. Imprimanta si plotter-ul sunt eliberate la momentele I3 si respectiv I4. Procesul B are nevoie de plotter de la momentul I5 la momentul I7 si de imprimanta de la momentul I6 la momentul I8.
Fiecare punct din diagrama reprezinta o stare reunita a acestor doua procese. Initial, starea este in punctul p, cand nici un proces nu a executat nici o instructiune. Daca temporizatorul il alege pe A pentru a rula primul, ajungem in punctul q in care A a executat un numar de instructiuni, dar B nu a executat niciuna. In punctul q traietoria devine verticala, indicand faptul ca temporizatorul a decis ca B sa ruleze. Cand avem un singur procesor toate caile trebuie sa fie orizontale sau verticale, niciodata pe diagonala. In plus, miscarea se face intotdeauna spre nord sau spre est, niciodata spre sud sau spre vest (procesele nu pot rula inapoi).
Fig. 3-12 Doua traiectorii proces-resursa.
Cand A intersecteaza linia I1 in timp ce parcurge calea de la r la s, solicita si ii este acordata imprimanta. Cand B ajunge in punctul t, solicita plotter-ul.
Zonele hasurate sunt in mod special interesante. Zonele cu linii inclinate de la sud-vest spre nord-est reprezinta ambele procese care detin imprimanta. Regula excluderii reciproce face imposibil accesul in aceasta zona. In acelasi mod zona hasurata reprezinta ambele procese care detin plotter-ul si este la fel, imposibil de accesat.
Daca sistemul intra vreodata in casuta delimitata de I1 si I2 lateral si de I5 si I6 sus si jos, va ajunge in impas la intersectia dintre I2 si I6. In acest punct Acere plotter-ul si B cere imprimanta si ambele sunt deja acordate. Intreaga casuta este nesigura si trebuie evitata. In punctul t singurul lucru sigurul lucru sigur este rularea procesului A pana ajunge la I4. Dincolo de acesta va fi buna orice traiectorie spre u.
-
Algoritmul bancherului pentru resurse multiple
Acest model de diagrama este greu de aplicat in cazul general al unui numar arbitrar de procese si de clase de resurse, fiecare cu mai multe exemplare (ex. doua plotter-e, trei unitati de banda). Totusi, algoritmul bancherului poate fi generalizat pentru a indeplini aceasta sarcina. In figura 3-13 vedem cum functioneaza.
In figura 3-13 vedem doua matrice. Cea din stanga arata cate resurse din fiecare tip sunt in mod curent alocate fiecaruia dintre cele cinci procese. Matricea din dreapta, arata de cate resurse mai are nevoie fiecare proces pentru a se incheia. Ca in cazul cu o singura resursa, procesele trebuie sa anunte totalul de resurse de care au nevoie, inainte de a incepe saexecute, astfel incat sistemul sa poata calcula la fiecare pas matricea din dreapta.
Fig. 3-13 Algoritmul bancherului cu mai multe resurse
Cei trei vectori din dreapta figurii arata resursele existente – E, resursele detinute – P si A - resursele disponibile. De la E stim ca sistemul are sase unitati de banda, trei plotter-e, patru imprimante si doua unitati CD-ROM. Dintre acestea, cinci unitati de banda, trei plotter-e, patru imprimante si doua unitati CD-ROM sunt in mod curent alocate. Acest fapt poate fi vazut prin adunarea coloanelor cu cele patru resurse din matricea din stanga. Vectorul de resurse disponibile este diferenta dintre ceea ce are sistemul si ceea ce se utilizeaza in mod curent.
Algoritmul prin care se verifica daca o stare este sigura sau nu poate fi acum formulat.
Cautati un rand R ale carui nevoi nesatisfacute de resurse sunt mai mici sau egale cu A. Daca nu exista un astfel de rand, in cele din urma sistemul va ajunge in impas deoarece nici un proces nu se poate incheia.
Sa presupunem ca procesul din randul ales solicita toate resursele de care are nevoie (lucru care este garantat a fi posibil) si se incheie. Marcam faptul ca procesul a terminat si adaugam toate resursele sale vectorului A.
Repetam pasii 1 si 2 pana cand fie toate procesele se incheie, caz in care starea iniiala era sigura, fie pana cand apare un impas, caz in care inseamna ca acea stare nu e sigura.
Daca mai multe procese sunt potrivite pentru a fi alese la pasul 1, nu are importanta care dintre ele este selectat : cantitatea de resurse ori se mareste, ori, in cel mai rau caz, ramane aceeasi.
Acum sa ne intoarcem la exemplul din figura 3-13. starea curenta este sigura. Sa presupunem ca procesul B solicita in acest moment o imprimanta. Aceasta cerere poate fi indeplinita deoarece deoarece si starea urmatoare este sigura (procesul D poate sa termine si apoi procesele A sau E, urmate de celelalte).
Acum sa ne inchipuim ca dupa ce i se acorda lui B unul dintre cele doua imprimante ramase E vrea sa obtina ultima imprimanta. Satisfacerea acestei cereri ar reduce vectorul resurselor disponibile la (1 0 0 0), care duce la impas. In mod evident cererea lui E nu ppoate fi satisfacuta imediat si trebuie amanata pentru un timp.
Acest algoritm a fost publicat de catre Dijkstra pentru prima data in 1965. de atunci, aproape fiecare carte despre sistemele de operare a descris in detaliu acest altgoritm. Au fost scrise nenumarate lucrari despre diferite aspecte ale acestui altgoritm. Din pacate, putini autori au avut indrazneala sa sublinieze ca desi este minunat in teorie, in practica este aproape inutil deoarece deoarece sunt putine cazurile in care procesele stiu dinainte care vor fi nevoile lor maxime de resurse. In plus, numarul proceselo nu este fix, ci variaza in mod dinamic pe masura ce utilizatorii intra sau ies din sistem. Mai mult chiar, resursele care se astepta sa fie disponibile pot sa dispara brusc (unitatile de banda se pot strica.
Pe scurt, schemele descrise anterior sub denumirea “prevenire” sunt foarte restrictive, iar algoritmul descris aici ca “evitare” necesita informatii care de obicei nu sunt disponibile. Daca va puteti gandi la un algoritm general care face aceste lucruri si in practica nu numai in teorie, scrieti-l si trimiteti-l.
Pentru cazuri particulare de aplicatii exista multe algoritmuri speciale excelente. De exemplu, in multe sisteme de baze de date, o operatie care apare frecvent cere blocarea catorva inregistrari si apoi actualizeaza toate inregistrarile blocate. Atunci cand mai multe procee ruleaza in acelasi timp exista un real pericol de impas.
O abordare des intalnita este numita blocare in doua faze. In prima faza procesul incearca sa blocheze toate inregistrarile de care are nevoie, una cate una. Daca reuseste face actualizarile si elibereaza inregistrarile. Daca vreo inregistrare e deja blocata, elibereaza inregistrarile pe care le detine deja si incepe din nou. Intr-un fel aceasta abordare se aseamana cu solicitarea in avans a tuturor resurselor necesare sau cel putin inainte de a se face ceva ireversibil.
Totusi, aceasta strategie nu este general valabila. In sistemele in timp real si in sistemele de control al proceselor de exemplu, nu se accepta ca un proces sa se incheie inainte de a ajunge la sfarsit si sa inceapa de la inceput din cauza ca o resursa nu est disponibila. Nu se accepta nici sa inceapa din nou daca procesul a scris sau a citit mesaje din retea, a actualizat fisiere sau daca a facut orice altceva care nu pote fi repetat in siguanta. Algoritmul functioneaza doar in acele situatii in care programatorul a aranjat lucrurile foarte atent, astfel incat programul poate fi oprit in orice moment al primei faze si apoi restartat. Din pacate, nu toate aplicatiile pot fi structurate in acest mod.
Anexa 1 Procese sub Windows NT
Convenţii de limbaj la programarea Windows
Limbajul nativ de dezvoltare a aplicaţiilor Windows este C ++. Din această cauză, în cele ce urmează vom descrie principiile programării folosind construcţii C şi C++. Headerul conţine principalele construcţii de limabaj folosite în interfaţa Windows.
Constante
În se definesc o serie de constante. Numele acestora este compus din două părţi: o primă parte indică grupul din care face parte constanta, apoi caracterul “_” şi în final numele specific, de regulă suficient de lung încât să sugereze ce reprezintă. Iată câteva nume de grupe:
CS stilul clasei de ferestre;
SW crearea ferestrei;
IDC identificator de cursor;
IDI identificator de icon;
DT atribut de afişare texte;
WS stilul ferestrei;
WM mesaj asociat unei ferestre
De exemplu, pentru fixarea stilului unei clase se pot lega prin operatorul C ‘|’ (sau) constantele de mai jos (care nu sunt singurele, mai pot fi şi altele):
-
CS_DBLCLIKS – mesajele se transmit ferestrei prin dublu click;
-
CS_HREDRAW sau CS_VREDRAW – provoacă redesenarea ferestrei după o redimensionare orizontală/verticală.
Tipuri de date
Windows foloseşte o serie de tipuri de date noi, prin care s-a urmărit creşterea protabilităţii aplicaţiilor în cazul unor noi arhitecturi de calculatoare. Astfel, avem tipurile:
-
BOOL, BYTE, DWORD (32 biţi);
-
FARPROC (pointer spre funcţie);
-
LPSTR (pointer către string);
-
LPMSG (pointer către o structură MSG) etc.
Pentru desemnarea obiectelor sunt definite nişte tipuri de date speciale: descriptor sau handle. Acestea sunt întregi pe 16 biţi prin intermediul cărora se pot referi obiecte. Tipurile de handle folosite sunt:
HANDLE indicator general;
HBRUSH indicator general spre obiect “pensulă“;
HCURSOR indicator spre resursă cursor;
HICON indicator spre icon;
HMENU indicator spre o resursă meniu;
HWND indicator indicator spre o fereastră.
Nume de variabile
Atribuirea de nume pentru variabile se face respectându-se anumite convenţii, provenit din experienţa programatorilor. Este vorba de notaţia ungară de denumire a variabilelor. [114]. Ele nu sunt restricţii impuse de sistem, dar este de preferat să fie respectate. De regulă numele atribuite sunt lungi, încep cu literă mică, iar în cadrul numelor apar litere mari la începuturile cuvintelor care le compun. De multe ori, când este vorba de o singură variabilă de un anumit tip, numele ei este numele tipului, scris cu literă mică.
Tot ca şi convenţii, începuturile (prefixele) numelor de variabile au semnificaţie:
-
b pentru BOOL;
-
by pentru BYTE;
-
c pentru char;
-
dw pentru DWORD;
-
fn pentru funcţie;
-
h pentru handle;
-
i pentru int;
-
lp petru pointer lung;
-
w pentru WORD etc.
Aplicaţii consolă
În ceea ce priveşte programarea Windows, prezenta lucrare nu are, nici pe departe, intenţia de a o descrie exhaustiv. Vom aborda doar ceea ce este strict necesae pentru a putea evidenţia aspectele de concurenţă posibile, în contextul cadru, multiplatformă, al demersurilor noastre.
Cele mai simple aplicaţii care se pot scrie sub Windows NT sunt aplicaţiile consolă. Acestea sunt, în fapt, aplicaţii cu intrare şi ieşire standard în mod text, la fel ca şi la programele simple sub Unix. Spre exemplu, un program extrem de simplu este programul prim.cpp de mai jos (Programul 3.6):
int main (int c, char* a [ ] {
int i;
char n [120];
printf (“Numele? ”);
gets (n);
printf (“ Salut %s\nUrmeaza parametrii \
liniei de comanda\n”, n);
for (i=0; a[i]; i++)
printf (“%s\n”, a[i] );
return 0;
}
Programul 3.6. O aplicaţie consolă: prim.cpp
Presupunem că în urma compilării şi link-editării acestui program s-a obţinut filierul executabil prim.exe. O execuţie a lui se face într-o fereastră MSDos Prompt (fereastră Cmd) de sub WindowsNT. Dacă se lansează programul prin comanda:
…> prim 11111 doi trei333 4
şi se dă la cerere numele (unuia dintre autori).
E:\FLORIN> prim.exe 11111 doi trei333 4
Numele? Florian Boian
Salut Florian Boian
Urmeaza parametrii liniei de comanda
Prim.exe`
11111
doi
trei333
4
E:\FLORIN>
Programul 3.5. Rezultatul execuţiei programului prim.exe
Un al doilea exemplu de aplicaţie consolă simplă este un program filtru. Acesta citeşte linie cu linie de la intrarea standard şi dă la ieşire aceleaşi linii, scurtate la primele 10 caractere. Sursa Filtru.cpp este prezentată în continuare (Programul 3.7.).
#include
#include
int main (int c, char* a [ ] ) {
char 1 [128];
for (; ;) {
if (gets(1) = = NULL)
break;
if (strlen (1) > 10)
1 [10] = 0;
printf (“%s\r\n”, 1);
}
return 0;
}
Programul 3.7. Sursa Filtru.cpp
Lansarea unui astfel de filtru se face, de asemenea, dintr-o fereastră Cmd, putându-se, la fel ca în Unix sau Dos, să se redirecteze intrarea şi ieşirea lui standard, astfel:
Filtru.exe FisierIesire
Cum se ajunge de la aceste surse cpp la fişierele executabile corespunzătoare?
Aceasta depinde de mediul cpp cu care se deyvoltă aplicaţiile. Nu intenţionăm o prezentare exhaustivă a acestor medii, deoarece nu constituie obiectul prezentei lucrări. Vom expune, strict telegrafic, paşii realizării lor folosind mediul DevStudio pentru Microsoft Visual C++ 6.0.
-
În meniul File se selectează New, în fereastra de dialog obţinută se selectează Project.
-
În cadrul acesteia se selectează Win32 Console Application.
-
În spaţiul Project Name se scrie numele proiectantului, în cazul nostru prim sau filtru.
-
În spaţiul Location se trece locul în structura de directori, în care mediul îşi va dezvolta aplicaţia, de exemplu
\Useri\florin
Aici, mediul creează un subdirector cu numele proiectului (în cazul nostru prim sau filtru).
-
Se alege tipul de aplicaţie consolă dorit:
An empty project;
A simple application,
A “Hello world!” application;
An application that supports MFC.
Considerăm că în acest exemplu am ales:
A simple application.
-
După Ok, se afişează un mesaj de informare, iar în subdirectorul din location sunt create subdirectoarele:
SourceFiles,
HeaderFiles,
ResourceFiles.
De asemenea, este depus acolo un fişier ReadMe.txt în care se explică rolul fiecărui fişier sau director creat de mediu.
-
Se selectează din menioul FileView, se expandează directorul cu numele proiectului, apoi subdirectorul SourceFiles în care un fişier cu numele proiectului şi tipul cpp. Acest fişier sursă conţine, gata generat, scheletul programului principal (al funcţiei main). După un dublu click pe numele acestuia, el apare în fereastra de editare.
-
Din acest moment programatorul poate să scrie efectiv conţinutul aplicaţiei sale, plecând de la scheletul furnizat de mediu.
-
La terminare, meniul Build oferă, printre altele, posibilitatea de a compila surs respectivă (Compile sau Ctrl+F7), respectiv să obţină un fişier exe (Build sau F7).
-
Fişierul de tip exe obţinut este plasat în subdirectorul Debug al directorului cu numele proiectului. El poate fi lansat în execuţie din mediu sau dinafara lui, poate fi copiat în altă parte (cum l-am depus noi pe prim.exe în e:âflorin) şi lansa de acolo, etc.
Crearea unui proces Windows
Crearea unui proces în Windows NT se face prin apelul funcţiei CreateProcess de către un thread (fir de execuţie) dintr-un alt proces. Funcţia are următorul prototip:
BOOL CreateProcess (
LPCTSTR lpszImageName,
LPCTSTR lpszCommandLine,
LPSECURITY_ATTRIBUTES lpsaProcess,
LPSECURITY_ATTRIBUTES lpsaThread,
BOOL fInheritHandles,
DWORD fdwCreate,
LPVOID lpvEnvironment,
LPTSTR lpszCurDir,
LPSTARTUPINFO lpsiStartInfo,
LPROCESS_INFORMATION lppiProcInfo
) }
Atunci când un fir de execuţie apelează funcţia CreateProcess, sistemul creează un spaţiu de adresare şi încarcă noul proces în acest spaţiu. După această operaţie, sistemul creează threadul primar pentru noul proces şi-l lansează în execuţie.
Să vedem semnificaţia parametrilor funcţiei CreateProcess:
Parametrul identifică numele fişierului executabil în care este memorat codul pentru procesul ce se va crea. CreateProcess va căuta fişierul mai întâi în directorul curent, şi apoi în directorele descrise de variabila de mediu PATH. Dacă acest parametru este NULL, funcţia va lua numele fişierului din primul cuvânt al liniei de comandă specificată de parametrul lpszCommandLine.
Parametrul specifică argumentele care trebuie transmise către noul proces. La pornirea procesului, aceste argumente vor fi regăsite în argumentul lpszCmdLine al funcţiei WinMAin. lapszCommandLine punctează către un şir de caractere ANSI.
-
lpsaProcess şi lpsaThread
Identifică atributele de securitate care trebuie date noului proces şi threadului său primar. Dacă valorile lor sunt NULL, sistemul va furniza valori implicite pentru aceşti parametri.
Specifică modul în care se moştenesc referinţele la obiecte de către procesul fiu. În mod normal, în Windows NT acestea nu sunt aceleaşi pentru fiecare proces. Chiar dacă este vorba de acelaşi obiect sistem, procese diferite pot lucra cu valori diferite. Dacă dorim ca aceste valori să fie identice în procesul tată şi în procesul fiu, atunci putem specifica valoarea TRUE acestui program.
Conţine comutatorii (flags) ce afectează modul în care este creat noul proces. Se pot specifica mai mulţi comutatori combinându-I prin operatorul “|”.
Referă un bloc de memorie în care sunt memorate şirurile, care descriu variabilele de mediu ce trebuie utilizate de noul proces. De obicei, valoarea acestui paramteru trenuie să fie NULL, caz în care procesul fiu primeşte aceleaşi variabile de mediu ca şi părintele.
Parametrul permite specificarea unui nou disc şi a unui nou director curent pentru procesul nou creat. Dacă valoarea transmisă pentru acest parametru este NULL, directorul şi discul de lucru ale noului proces sunt aceleaşi ca li cele ale procesului părinte.
Punctează către o structură STARTUPINFO. Aceste informaţii sunt necesare pentru subsistemul Win32 la crearea unui nou proces. Această structură este prea complexă pentru a o descrie aici. Să reţinem doar că în ea se pot specifica:
-
caracteristicile unei ferestre consolă:
-
culoare;
-
număr de linii;
-
coloane;
-
poziţie pe ecran;
-
titlu, etc.;
-
informaţii despre modul în care va fi afişată fereastra aplicaţiei:
-
maximizată;
-
icon, etc.;
-
informaţii despre comportarea sistemului în timpul încărcării
aplicaţiei:
Conţine adresa unei structuri care va fi completată cu informaţii de către funcţia CreateProces înainte ca aceasta să-şi termine execuţia. Această structură va conţine referinţele pentru noul proces, pentru threadul său primar, precum şi identificatorii acestora. Structura este declarată astfel:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
Dostları ilə paylaş: |