COMUNICA II INTER-PROCESE
Mecanismele de comunicare între procese permit acestora sa schimbe date între ele si sa-si sincronizeze executia. Au fost prezentate deja câteva modalitati de comunicare între procese, cum ar fi pipe-urile (nenumite), pipe-urile numite si semnalele. Pipe-urile (nenumite) au dezavantajul ca sunt cunoscute numai de catre procesele care sunt fii ai procesului care a invocat apelul sistem pipe. Procesele neînrudite nu pot comunica prin pipe-uri. Desi pipe-urile numite permit comunicarea între procese neînrudite, ele nu reprezinta totusi o modalitate de comunicare în retea si nici nu pot fi usor adaptate pentru folosirea cailor multiple de comunicare între grupuri diferite de procese este imposibila multiplexarea unui pipe numit pentru a pune la dispozitie canale separate perechilor de procese care comunica. De asemenea, procesele oarecare mai pot comunica si prin trimiterea de semnale unul altuia, cu ajutorul apelului sistem kill, dar în acest caz "mesajul" consta doar într-un numar asociat semnalului.
În acest capitol se descriu alte forme de comunicare între procese. Capitolul începe prin studierea executiei pas cu pas a proceselor, întelegând prin aceasta faptul ca un proces urmareste si controleaza executia pas cu pas a altui proces, iar apoi continua cu explicarea modalitatilor de comunicare între procese, modalitati specifice variantei System V si cunoscute sub numele System V IPC comunicarea prin mesaje, prin memorie partajata si prin semafoare. Se face o trecere în revista a metodelor obisnuite prin care se realizeaza comunicarea între procese de pe masini diferite prin intermediul unei retele si, în final, se face o prezentare la nivel utilizator a socket-urilor BSD. Nu se iau în discutie probleme specifice retelelor, cum ar fi protocoale si adrese, acestea nefacând obiectul de studiu al cartii.
10.1. Executia pas cu pas a proceselor
Sistemul de operare UNIX pune la dispozitie o modalitate rudimentara de comunicare între procese în scopul executarii pas cu pas a acestora, facilitate utila in depanarea programelor. Un proces depanator (debugger process), cum ar fi sdb, creaza un proces pe care îl executa pas cu pas si îi controleaza executia cu ajutorul apelului sistem ptrace, stabilind si anulând puncte de întrerupere (breakpoints) si scriind (citind) date în (din) spatiul sau virtual de adrese. În consecinta, executia pas cu pas a unui proces consta în sincronizarea executiei procesului depanator cu cea a procesului depanat si în controlul executiei celui de-al doilea.
if((pid=fork())==0)
/* aici continua procesul parinte=procces 727e43h ul depanator */
for(;;)
Figura 10.1. Structura unui proces de depanare
Pseudocodul din figura 10.1. pune in evidenta o structura caracteristica pentru un program de depanare. Procesul depanator creaza un proces fiu, care invoca apelul sistem ptrace al carui rezultat este pozitionarea pe 1 a bitului de executie pas cu pas în intrarea corespunzatoare procesului fiu, din tabela de procese. Procesul fiu executa, prin apelul sistem exec, programul care urmeaza sa fie depanat. De exemplu, daca utilizatorul doreste sa depaneze programul a.out , atunci argumentul transmis apelului sistem exec va fi sirul de caractere a.out si procesul fiu va executa acest program. Nucleul va executa apelul sistem exec ca de obicei, dar la sfârsit va observa faptul ca bitul de executie pas cu pas este pozitionat pe 1 si, în consecinta, va trimite procesului fiu un semnal trap. La întoarcerea din apelul sistem exec, nucleul verifica daca s-au trimis semnale -asa cum o face, de altfel, la întoarcerea din orice alt apel sistem- gaseste semnalul "trap" pe care, chiar el, tocmai l-a trimis si executa pas cu pas codul procesului fiu, întocmai ca pe o secventa speciala de tratare a semnalelor. Observând pozitionarea pe 1 a bitului de executie pas cu pas din propria intrare în tabela de procese, procesul fiu trezeste procesul parinte din starea de asteptare în care intrase prin executarea apelului sistem wait (dupa cum se va vedea mai târziu), intra într-o stare speciala de executie pas cu pas (trace), care nu este evidentiata în diagrama starilor proceselor si face o comutare de context.
În mod obisnuit, procesul parinte este deja intrat intr-un ciclu, asteptând sa fie trezit de catre procesul fiu care se executa pas cu pas. Când procesul fiu trezeste procesul parinte, acesta încheie executia apelului sistem wait, citeste comenzile de la intrare ale utilizatorului si le converteste intr-o serie de apeluri ptrace în scopul controlarii executiei procesului fiu.
Sintaxa apelului sistem ptrace este urmatoarea
ptrace( cmd, pid, addr, data)
unde cmd specifica diverse comenzi cum ar fi citirea sau scrierea datelor, ori reluarea executiei, pid reprezinta identificatorul de proces al procesului care se executa pas cu pas, addr reprezinta adresa virtuala, din cadrul procesului fiu, la (de la) care se va scrie (citi) si data reprezinta o valoare de tip întreg care va fi scrisa. La executarea apelului sistem ptrace, nucleul verifica daca procesul depanator are vreun proces fiu al carui identificator este egal cu valoarea parametrului pid si daca acest proces fiu se afla în starea "executie pas cu pas", utilizînd apoi o structura de date globala, dedicata executiei pas cu pas, pentru realizarea transferului de date între cele doua procese. Nucleul blocheaza accesul la structura de date globala amintita, pentru a împiedica scrierea de catre alte procese aflate în executie pas cu pas, copiaza valorile parametrilor cmd, addr si data în structura de date, trezeste procesul fiu si-l trece în starea "gata de executie", punându-se apoi în asteptare pîna la sosirea raspunsului de la procesul fiu. Când procesul fiu îsi reia executia (în modul nucleu), el executa comanda prevazuta în parametrul cmd, scrie rezultatul executarii comenzii în structura de date globala dedicata executiei pas cu pas si apoi trezeste procesul depanator. În functie de comanda precizata prin parametrul cmd, procesul fiu poate sa reintre în starea "executie pas cu pas" asteptând o noua comanda, sau poate încheia tratarea semnalului reluându-si executia. Când procesul depanator îsi reia executia, nucleul memoreaza valoarea de retur furnizata de procesul executat pas cu pas, deblocheaza accesul la structura de date globala corespunzatoare si reda controlul utilizatorului.
Daca, în momentul intrarii procesului fiu în starea "executie pas cu pas", procesul depanator nu se afla în asteptare în apelul sistem wait, el nu va putea determina care este procesul fiu ce se va executa pas cu pas, decât în momentul invocarii apelului sistem wait, moment în care se va termina imediat si va proceda asa cum tocmai a fost descris mai sus.
int datas32t;
main()
Figura 10.2. Programul trace -- un proces executat pas cu pas
Sa luam în consideratie cele doua programe, denumite trace si debug, prezentate în figurile 10.2 si respectiv 10.3. Rulând programul trace la terminal, valorile vectorului data vor fi egale cu zero procesul afiseaza adresa vectorului data si îsi încheie executia. Rulând acum programul debug , având ca parametru o valoare egala cu cea afisata de catre programul trace, acesta salveaza valoarea parametrului ce i s-a transmis în variabila addr , creaza un proces fiu care invoca apelul sistem ptrace pentru a putea executa pas cu pas si un alt proces executa programul trace.
adefine TRÎSETUP 0
adefine TRÎWRITE 5
adefine TRÎRESUME 7
int addr;
main(argc,argv)
int argc;
char *argvst;
for(i=0;i<32;i++)
/* procesul executat pas cu pas îsi reia executia */
ptrace(TRÎRESUME,pid,1,0);
}
Figura 10.3 Debug-un proces de executie pas cu pas
La sfârsitul executiei apelului sistem exec, nucleul trimite procesului fiu (sa-l numim procesul trace) un semnal SIGTRAP, iar procesul trace intra în starea "executie pas cu pas", asteptând comenzi de la procesul debug. Daca procesul debug se afla în asteptare în algoritmul wait, acesta se trezeste, gaseste procesul fiu executat pas cu pas si paraseste apelul wait. Apoi, procesul debug invoca apelul sistem ptrace, scrie valoarea variabilei de ciclare i în spatiul de adrese al procesului trace si incrementeaza valoarea din variabila addr în cadrul procesului trace, variabila addr reprezinta adresa unei intrari din vectorul data. Ultima invocare a apelului ptrace de catre procesul debug are ca efect executia procesului trace , iar în acest caz vectorul data contine valorile de la 0 la 31. Un program depanator cum ar fi sdb are acces la tabela de simboluri a procesului executat pas cu pas, din continutul careia determina adresele utilizate ca parametri pentru apelurile procedurii ptrace.
Utilizarea apelului sistem ptrace în scopul executarii pas cu pas a proceselor, este o metoda rudimentara care prezinta mai multe dezavantaje.
Nucleul trebuie sa execute patru comutari de context, pentru a realiza transferul a doi octeti de date între procesul depanator si procesul executat pas cu pas nucleul executa o comutare de context în cadrul procesului depanator la invocarea apelului sistem ptrace, pâna când procesul executat pas cu pas trimite un raspuns,comuta contextul la si de la procesul executat pas cu pas, si comuta contextul înapoi la procesul depanator împreuna cu raspunsul catre apelul ptrace. Aceste comutari sunt necesare, întrucât un proces depanator nu are alta modalitate de a accesa spatiul virtual de adrese al procesului executat pas cu pas, si în consecinta este un proces lent.
Un proces depanator poate executa pas cu pas mai multe procese fiu simultan, cu toate ca, în practica, aceasta facilitate este rareori exploatata. Un aspect mai critic îl reprezinta, însa, faptul ca un proces depanator nu poate executa pas cu pas decât procese fiu. Daca un proces fiu, executat pas cu pas, invoca apelul sistem fork (adica îsi creaza propriul proces fiu), atunci procesul depanator nu are nici un control asupra procesului "nepot" ceea ce reprezinta o restrictie majora în cazul depanarii programelor complexe. Daca un proces executat pas cu pas invoca apelul sistem exec, depanarea se continua si la nivelul imaginilor -ulterioare apelului exec - ale procesului depanat, din cauza apelului ptrace initial, depanatorul neputând, însa, sa stie numele imaginii procesului care a rezultat în urma apelului exec, ceea ce face dificila depanarea simbolica.
Un proces depanator nu poate executa pas cu pas un alt proces care se afla deja în executie, daca acesta din urma nu a invocat, în prealabil, apelul sistem ptrace , pentru a preveni nucleul asupra faptului ca permite executia sa, pas cu pas. Acest lucru este, de asemenea, un dezavantaj, deoarece pentru a executa pas cu pas un anumit proces aflat deja în executie, acesta trebuie în prealabil terminat (cu apelul kill) si relansat în executie, în modul "executie pas cu pas".
Nu este posibila executia pas cu pas a programelor de tip setuid, deoarece utilizatorii ar putea încalca drepturile de acces, având posibilitatea de a scrie în spatiul virtual de adrese al acestor programe, cu ajutorul apelului sistem ptrace, si de a executa operatiuni nepermise. De exemplu, sa presupunem ca un astfel de program, invoca apelul exec având ca parametru numele de fisier privatefile. Un utilizator istet s-ar putea folosi de apelul sistem ptrace pentru a modifica parametrul respectiv la valoarea bin sh , lansând astfel în executie shell-ul (cu toate programele asociate acestuia) fara a avea permisiunea de a o face. Apelul exec ignora valoarea bitului setuid daca procesul este executat pas cu pas, pentru a evita scrierea de catre un utilizator în cadrul spatiului de adrese al unui astfel de program.
Killian descrie o alta modalitate de executie pas cu pas a proceselor, bazata pe lucrul cu sistemele de fisiere, descris în capitolul 5 . Administratorul de sistem monteaza un sistem de fisiere, proc -utilizatorii identifica procesele pe baza identificatorilor acestora si le trateaza ca fiind fisiere din proc. Nucleul acorda permisiunea de deschidere a fisierelor, în conformitate cu identificatorul utilizatorului si identificatorul de grup. Utilizatorii au posibilitatea de a examina spatiul de adrese al procesului prin citire si de a stabili puncte de întrerupere prin scriere în fisier. Apelul stat furnizeaza diverse informatii de ordin statistic despre procese. Aceasta metoda înlatura trei dintre dezavantajele utilizarii apelului ptrace. Mai întâi, aceasta metoda este mai rapida deoarece un proces depanator poate transfera o cantitate mai mare de date la un apel sistem, în comparatie cu apelul ptrace. În al doilea rând, un proces depanator poate executa pas cu pas orice proces, care nu trebuie sa fie neaparat un proces fiu al sau. În sfârsit, un proces depanat nu necesita masuri prealabile pentru a permite executia sa pas cu pas un proces depanator poate executa pas cu pas procese deja în curs de executie. Ca parte a mecanismului obisnuit de protectie a fisierelor, apare faptul ca doar superutilizatorul poate executa pas cu pas procesele de tip setuid pentru fisierele din radacina.
10.2 Comunicarea între procese prin pachetul IPC
Pachetul System V IPC din UNIX se compune din trei mecanisme. Mesajele permit proceselor sa trimita fluxuri de date formatate catre alte procese, memoria partajata permite proceselor sa foloseasca în comun parti din spatiul lor virtual de adrese, iar semafoarele permit proceselor sa-si sincronizeze executia. Implementate în mod unitar, aceste mecanisme au o serie de proprietati comune
Fiecare mecanism contine o tabela ale carei intrari descriu toate instantele acelui mecanism.
Fiecare intrare din tabela contine o cheie numerica, care reprezinta numele sau desemnat de utilizator.
Fiecare mecanism dispune de un apel sistem de tip "get" destinat crearii unei noi intrari, sau obtinerii uneia deja existente, parametrii acestor apeluri incluzând o cheie si un indicator. Nucleul cauta în tabela corespunzatoare intrarea desemnata de catre cheie. Procesele pot invoca apelurile de tip "get" cu parametrul cheie având valoarea IPC PRIVATE, pentru a se asigura de obtinerea unei intrari nefolosite. Procesele pot seta indicatorul la valoarea IPC CREAT pentru a crea o noua intrare, daca cea precizata prin cheie nu exista deja, si de asemenea pot determina semnalizarea erorilor prin pozitionarea indicatorilor IPC EXCL si IPC CREAT, pentru situatia în care deja exista o intrare desemnata prin acea cheie. Apelurile sistem de tip "get"returneaza un descriptor determinat de nucleu, care va fi utilizat ulterior ca parametru al altor apeluri sistem, rezultând de aici analogia cu apelurile sistem creat si open.
În cadrul fiecarui mecanism IPC, pentru determinarea indexului din tabela structurilor de date, nucleul utilizeaza urmatoarea formula de calcul bazata pe valoarea descriptorului
index = descriptor modulo (numarul de intrari din tabela)
De exemplu, daca tabela structurilor de date pentru mesaje contine 100 de intrari, atunci descriptorii pentru intrarea 1 sunt 1, 101, 201 s.a.m.d. Când un proces elibereaza o intrare, nucleul creste valoarea descriptorului asociat acesteia cu o valoare egala cu numarul de intrari din tabela. Noua valoare, astfel obtinuta, devine noul descriptor al intrarii respective, în cazul unei noi alocari a acesteia printr-un apel de tip "get". Orice încercare ulterioara a proceselor de a accesa intrarea respectiva, pe baza vechiului descriptor, se va solda cu eroare. În legatura cu exemplul de mai sus, daca descriptorul asociat intrarii 1 din tabela pentru mesaje este 201, atunci când intrarea respectiva este eliberata, nucleul îi atribuie acesteia un nou descriptor, 301. Procesele care încearca sa acceseze intrarea, pe baza vechiului descriptor (201), vor primi un mesaj de eroare, deoarece acest descriptor nu mai este valabil. Nucleul recicleaza eventual numerele pentru descriptori, probabil dupa o lunga perioada de timp.
Fiecare intrare a unui mecanism IPC dispune de o structura de date pentru stocarea permisiunilor, aceasta incluzând indentificatorul de utilizator si indentificatorul de grup ale procesului care a creat intrarea, un indentificator de utilizator si unul de grup fixati printr-un apel sistem de tip "control" si un set de permisiuni de citire-scriere-executie separat pentru utilizator, grup si ceilalti, similare permisiunilor de acces la fisiere.
Fiecare intrare contine si alte informatii de stare, cum ar fi identificatorul ultimului proces care a actualizat intrarea (a trimis ori a primit un mesaj, a atasat o zona comuna de memorie s.a.m.d.), precum si momentul ultimului acces sau al ultimei actualizari a intrarii.
Fiecare mecanism dispune de un apel sistem de tip "control" prin care se primesc informatii despre starea unei intrari, se seteaza starea unei intrari, sau se elibereaza o intrare. Când un proces solicita informatii despre starea unei intrari, nucleul verifica daca acesta are drept de citire si apoi copiaza datele din intrare la adresa specificata de utilizator. În mod similar, pentru modificarea valorilor dintr-o intrare, nucleul verifica daca identificatorul utilizator al procesului corespunde cu identificatorul utilizatorului, sau cu identificatorul celui care a creat intrarea, sau daca procesul în cauza este executat de catre superuutilizator dreptul de scriere nu este suficient pentru a se permite modificarea parametrilor unei intrari. Nucleul copiaza datele precizate de catre utilizator în intrarea din tabela, setând valorile identificatorilor utilizator si de grup, permisiunile de acces, precum si continutul altor câmpuri, în functie de tipul mecanismului de comunicare. Nucleul nu modifica valorile identificatorilor utilizator si de grup apartinând celui care a creat intrarea, astfel încât utilizatorul care a creat intrarea respectiva îsi pastreaza drepturile de control asupra acesteia. În sfârsit, un utilizator poate elibera o intrare din tabela, daca este superutilizator, sau daca identificatorul procesului sau corespunde celui din structura de date a intrarii. Nucleul creste numarul de descriptor asociat intrarii, astfel încât, la urmatoarea alocare aacesteia el va returna un alt descriptor. Din acest motiv, asa cum s-a explicat mai sus, apelurile sistem vor întoarce un mesaj de eroare, daca un proces va încerca sa acceseze o intrare utilizând unul din vechii descriptori.
10.2.1. Comunicarea între procese prin mesaje
Exista patru apeluri sistem pentru comunicarea prin mesaje msgget întoarce (sau creaza) un descriptor de mesaje, care desemneaza o coada de mesaje, utilizat ca parametru în celelalte apeluri sistem msgctl dispune de optiuni de setare sau examinare a valorilor parametrilor asociati unui descriptor de mesaje, precum si de o optiune de eliberare a descriptorilor msgsnd trimite un mesaj, iar msgrcv receptioneaza un mesaj.
Sintaxa apelului sistem msgget este urmatoarea
msgqid = msgget(key, flag)
unde msgqid este descriptorul returnat de apelul sistem, iar key si flag au semnificatiile descrise mai sus, în cazul general al apelurilor de tip "get". Nucleul memoreaza mesajele sub forma unei liste înlantuite (coada de mesaje) careia îi corespunde un descriptor, si foloseste un descriptor de coada de mesaje -msgqid- ca index într-un tabela de antete de cozi de mesaje. În plus fata de câmpul permisiuni asociat oricarei mecanism IPC, mentionat mai sus, structura de date asociata unei cozi de mesaje contine urmatoarele câmpuri
Pointeri catre primul si ultimul mesaj din lista înlantuita
Numarul de mesaje si numarul total de octeti de date din lista înlantuita
Numarul maxim de octeti de date pe care îi poate contine lista
înlantuita
Identificatorii ultimelor procese care au trimis si, respectiv receptionat,
mesaje
Momentele de timp ale ultimelor operatii msgsnd, msgrcv si msgctl.
Atunci când un utilizator invoca apelul sistem msgget pentru a crea un nou descriptor, nucleul cauta în tabela de cozi de mesaje pentru a vedea daca exista vreuna care sa corespunda cheii specificate. Daca nu exista nici o intrare care sa corespunda cheii, nucleul aloca o noua structura de date de tip coada de mesaje, o initializeaza si returneaza utilizatorului un identificator pentru aceasta noua structura. Daca exista deja o astfel de coada de mesaje, atunci nucleul verifica drepturile de acces si revine din apelul sistem.
Procesele folosesc apelul sistem msgsnd pentru a trimite un mesaj, apelul având urmatoarea sintaxa
msgsnd(msgqid, msg, count, flag)
unde msgqid este descriptorul cozii de mesaje, returnat în mod obisnuit de apelul sistem msgget, msg este un pointer catre o structura de date -controlata de catre utilizator- compusa din tipul mesajului (care poate lua valori întregi) si un sir de caractere, count specifica dimensiunea în octeti a sirului de caractere din structura msg, iar flag precizeaza actiunea pe care urmeaza sa o întreprinda nucleul, în situatia în care nu mai are la dispozitie suficient spatiu în buffer-ele interne de memorie.
algoritmul msgsnd /* trimitere mesaj */
intrari (1)descriptorul cozii de mesaje
(2)adresa structurii de date pentru mesaje
(3)lungimea mesajului
(4)indicatori
iesiri numarul de octeti efectiv trimisi
obtine un antet de mesaj;
copiaza mesajul propriu-zis din spatiul utilizator în spatiul nucleu;
actualizeaza structura de date:
-insereaza în lista noul antet de mesaj;
-initializeaza pointerul din antetul de mesaj sa pointeze catre zona de date;
-înscrie valoarea:-contorului de octeti;
-momentelor de timp;
-identificatorului procesului transmitator;
trezeste toate procesele care asteapta sa citeasca mesaje din coada;
}
Figura 10.4. Algoritmul pentru transmiterea unui mesaj
În figura 10.4. nucleul verifica daca procesul care trimite un mesaj are drept de scriere pentru descriptorul respectiv, daca lungimea mesajului nu depaseste limitele sistemului, daca coada de mesaje nu contine prea multi octeti si daca tipul mesajului are o valoare întreaga si pozitiva. Daca toate conditiile sunt îndeplinite, nucleul aloca spatiu pentru mesaj din spatiul liber pentru mesaje, gestionat de catre o tabela (map) similara celei care gestioneaza spatiul liber de pe dispozitivul de swap si copiaza acolo datele din spatiul utilizatorului. Nucleul aloca un antet de mesaj pe care-l plaseaza la sfârsitul listei înlantuite de antete, corespunzatoare cozii respective. În antet se înscriu tipul si dimensiunea mesajului, se initializeaza antetul pentru a referi sirul de caractere ce constituie mesajul propriu-zis, iar în coada de antete se actualizeaza câmpurile ce contin informatii de ordin statistic (numarul de mesaje si de octeti de date din coada de mesaje, momentele de timp si identificatorul procesului care a trimis mesajul). Apoi, nucleul trezeste procesele care asteptau sosirea unui mesaj în coada de mesaje. Daca numarul de octeti depaseste limita cozii de mesaje, procesul care trimite mesajul se pune în asteptare pâna la eliberarea spatiului din coada de mesaje, prin trimiterea altor mesaje deja existente în coada. Daca procesul a semnalizat nucleului sa nu-l treaca în asteptare (cu ajutorul indicatorului IPC NOWAIT), atunci el revine imediat din apelul sistem, cu eroare. Figura 10.5 prezinta mesajele dintr-o coada, evidentiind coada de antete, listele înlantuite de antete de mesaje si pointerii din antetele de mesaje catre zona de date.
![]() |
Figura 10.5. Structurile de date pentru comunicarea prin mesaje
Sa consideram programul din figura 10.6 un proces invoca apelul sistem msgget pentru a obtine un descriptor pentru cheia MSGKEY. Procesul stabileste o lungime a mesajului de 256 de octeti, desi foloseste doar primul întreg, îsi copiaza propriul identificator de proces în zona de text a mesajului, atribuie valoarea 1 pentru tipul mesajului, apoi invoca apelul sistem msgsnd pentru a trimite mesajul. Vom reveni mai târziu la acest exemplu.
Procesele receptioneaza mesaje cu ajutorul apelului sistem msgrcv, care are urmatoarea sintaxa
count = msgrcv(id, msg, maxcount, type, flag)
unde id este descriptorul de mesaj, msg este adresa unei structuri de date a utilizatorului în care se va scrie mesajul primit, maxcount este dimensiunea sirului de caractere din structura de date msg, type specifica tipul de mesaj pe care utilizatorul doreste sa-l citeasca, iar flag precizeaza ce trebuie sa faca nucleul daca nu exista mesaje în coada de mesaje. Valoarea întoarsa de apel, count, reprezinta numarul de octeti de date receptionati efectiv de catre utilizator.
În figura 10.7 nucleul verifica daca utilizatorul are drepturile necesare de acces la coada de mesaje. Daca mesajul cerut de utilizator are valoarea din câmpul type egala cu 0, nucleul va localiza primul mesaj din lista înlantuita. Daca lungimea mesajului este mai mica sau egala cu dimensiunea ceruta de utilizator, atunci nucleul copiaza mesajul propriu-zis în structura de date a utilizatorului si îsi actualizeaza în mod corespunzator structurile interne de date decrementeaza valoarea contorului de mesaje aflate în lista si numarul de octeti de date din coada de mesaje, înscrie momentul receptionarii mesajului si identificatorul procesului care a receptionat mesajul, reface lista înlantuita ca urmare a eliminarii mesajului si elibereaza spatiul în care a fost memorat mesajul.
ainclude <sys/types.h>
ainclude <sys/ipc.h>
ainclude <sys/msg.h>
adefine MSGKEY 75 /*cheia mesajului=index în tabela de mesaje */
struct msgform /* descrierea formei mesajului */
;
main()
Figura 10.6. Un proces client
Daca exista procese care doresc sa trimita mesaje si sunt în asteptare ca urmare a faptului ca nu a existat spatiu suficient în lista de mesaje, atunci nucleul le trezeste.
Algoritmul msgrcv /* receptionare mesaj */
intrari: (1)descriptorul de mesaj
(2)adresa vectorului de date pentru mesajele care sosesc
(3)dimensiunea vectorului de date
(4)tipul de mesaj cerut
(5)indicatori
iesiri: numarul de octeti din mesajul returnat
/* nu exista nici un mesaj */
if (indicatorul precizeaza ca procesul sa nu treaca în asteptare)
return (eroare);
sleep(eveniment:sosirea unui mesaj în coada de mesaje);
goto ciclu;
Figura 10.7. Algoritmul pentru receptionarea unui mesaj
Daca lungimea mesajului este mai mare decât parametrul maxcount precizat de utilizator, atunci nucleul întoarce eroare la revenirea din apelul sistem si lasa mesajul în lista. Daca procesul ignora restrictiile de dimensiune a mesajelor (prin pozitionarea indicatorului MSG NOERROR, atunci nucleul trunchiaza mesajul, întoarce numarul de octeti cerut si sterge întregul mesaj din lista.Prin stabilirea corespunzatoare a valorii parametrului type, un proces poate receptiona mesaje de un anumit tip. Daca valoarea este un întreg pozitiv, atunci nucleul întoarce primul mesaj de tipul respectiv. Daca valoarea este un întreg negativ, atunci nucleul cauta tipul cel mai mic ca valoare dintre toate mesajele din coada de mesaje, presupus a fi mai mic sau egal în valoare absoluta decât valoarea type, si întoarce primul mesaj de acest tip. De exemplu, daca o coada contine trei mesaje, ale caror tipuri sunt 3, 2 si respectiv 1, si daca un utilizator cere un mesaj cu valoarea pentru tip egala cu -2, atunci nucleul întoarce mesajul care are tipul 1, din cadrul cozii de mesaje. În toate cazurile, daca nici un mesaj din coada de mesaje nu satisface conditia de tip din cererea de receptionare, nucleul trece procesul în starea de asteptare, daca acesta nu a specificat revenirea imediata din contextul nucleu prin pozitionarea bitului IPC NOWAIT
Sa privim programele din figurile 3.6 si 3.8. Programul din figura 10.8 prezinta structura unui server care furnizeaza servicii generale proceselor client. De exemplu, acesta (server-ul) poate primi, de la procesele client, cereri de furnizare de informatii dintr-o baza de date procesul server constituie singurul punct de acces la continutul bazei de date, ceea ce face mai usoara rezolvarea problemei consistentei si securitatii datelor. Procesul server creaza o structura de mesaj prin setarea flag-ului, din cadrul apelului mssget, la valoarea IPC CREAT si receptioneaza toate mesajele cu tipul 1 (adica cereri de la procesele client). Procesul server citeste textul mesajului, cauta identificatorul procesului client si seteaza valoarea tipului mesajului care va fi returnat la valoarea identificatorului procesului client.
În acest exemplu, procesul server îsi trimite propriul identificator de proces catre procesul client în cadrul textului mesajului, iar procesul client va receptiona numai mesajele al caror tip este egal cu valoarea identificatorului de proces al serveru-lui. Astfel, procesul server receptioneaza numai mesajele trimise acestuia de catre procesele client, iar procesele client receptioneaza numai mesajele trimise lor de catre procesul server. Procesele coopereaza pentru a stabili canale multiple de comunicare pe o singura coada de mesaje.
Mesajele sunt formate din perechi tip-date, pe când datele din fisier formeaza un sir de octeti. Parametrul type permite proceselor sa selecteze, daca se doreste, mesaje de un anumit tip, facilitate de care nu se putea beneficia cu usurinta în cadrul sistemului de fisiere.
ainclude <sys/types.h>
ainclude <sys/ipc.h>
ainclude <sys/msg.h>
adefine MSGKEY 75
struct msgform
msg;
int msgid;
main()
cleanup() /* rutina pentru tratarea oricarei semnal */
Figura 10.8. Un proces server
Astfel, procesele pot extrage mesaje de un anumit tip din coada de mesaje, în ordinea în care acestea sosesc, iar nucleul pastreaza ordinea corecta a acestora. Desi este posibila implementarea, la nivel utilizator, a unei scheme de comunicare prin fisiere, comunicarea prin mesaje ofera aplicatiilor o modalitate mai eficienta de transfer al datelor între procese.
Un proces poate inspecta starea unui descriptor de mesaj, îi poate seta valoarea si poate sterge un descriptor de mesaj cu ajutorul apelului sistem msgctl. Sintaxa apelului este:
msgctl(id, cmd, mstatbuf)
unde id identifica descriptorul de mesaj, cmd precizeaza tipul comenzii, iar mstatbuf reprezinta adresa structurii de date utilizator care va contine parametrii de control sau rezultatele unei interogari.
Întorcându-ne la exemplul din figura 10.8 procesul server receptioneaza semnalele si invoca functia cleanup pentru a sterge coada de mesaje din memoria sistemului. Daca procesul server nu intercepteaza semnalele, sau daca primeste un semnal SIGKILL (care nu poate fi interceptat), atunci coada de mesaje va ramâne în sistem, chiar daca nici un proces nu o mai refera. Încercarile ulterioare de a crea (în exclusivitate) o noua coada de mesaje pe baza cheii date, vor esua pâna în momentul în care vechea coada va fi stearsa.
10.2.2.Comunicarea între procese prin zone de memorie partajata
Procesele pot comunica direct unele cu celelalte prin folosirea în comun a unor parti din spatiul lor virtual de adrese si prin scrierea si citirea datelor memorate în memoria partajata. Apelurile sistem pentru manipularea zonelor de memorie partajata sunt similare apelurilor sistem dedicate comunicatiei prin mesaje. Apelul sistem shmget creaza o noua zona regiune de memorie partajata sau întoarce una existenta, apelul sistem shmat ataseaza logic o regiune de memorie la spatiul virtual de adrese al unui proces, apelul sistem shmdt detaseaza o regiune de spatiul virtual de adrese al unui proces, iar apelul sistem shmctl manipuleaza diversi parametri asociati zonelor de memorie partajata. Procesele scriu si citesc memoria patajata utilizând aceleasi instructiuni masina pe care le utilizeaza si la scrierea ori citirea din memoria obisnuita. Dupa atasarea unei zone de memorie patajata, aceasta devine parte a spatiului virtual de adrese al procesului, accesibila în acelasi mod în care sunt accesibile si celelalte adrese virtuale.
Sintaxa apelului sistem shmget este urmatoarea
shmid = shmget(key, size, flag)
unde size reprezinta numarul de octeti din regiunea de memorie. Nucleul cauta cheia key în tabela cu zonele de memorie partajata daca gaseste în aceasta tabela o intrare care sa corespunda cheii si daca drepturile de acces sunt validate, atunci întoarce descriptorul acelei intrari din tabela. Daca nu gaseste o intrare careia sa-i corespunda cheia key, si daca utilizatorul a dat indicatorului flag valoarea IPC CREAT, în scopul crearii unei noi regiuni comune de memorie, atunci nucleul verifica daca valoarea parametrului size se situeaza între valorile limita minima si maxima de memorie ale sistemului si apoi aloca o structura de date pentru o regiune de memorie, utilizând algoritmul allocreg. Nucleul memoreaza în tabela cu zonele de memorie partajata(Shared Memory Table) drepturile de acces, dimensiunea si un pointer catre intrarea din tabela de regiuni (Figura 10.19.) si stabileste valoarea unui indicator pentru a arata ca regiunii nu îi este asociata memorie. Nucleul aloca memorie regiunii (tabele de pagini s.a.m.d.) numai atunci când un proces ataseaza regiunea respectiva la spatiul sau de adrese. De asemenea, nucleul stabileste valoarea unui indicator din intrarea în tabela de regiunilor pentru a indica faptul ca regiunea nu trebuie eliberata atunci când ultimul proces care a atasat-o spatiului sau de adrese se termina. Astfel, datele din zona de memorie partajata ramân intacte desi nici un proces nu mai include zona respectiva în spatiul sau virtual de adrese.
Figura 10.9. Structurile de date implicate în gestiunea
zonelor de memorie partajata
Un proces ataseaza o regiune de memorie comuna spatiului sau virtual de adrese cu ajutorul apelului sistem shmat, care are urmatoarea sintaxa
virtaddr = shmat(id, addr, flags)
unde id, returnat de apelul sistem shmget anterior, identifica regiunea de memorie partajata, addr reprezinta adresa virtuala unde utilizatorul doreste sa ataseze regiunea de memorie partajata, iar flags specifica daca regiunea este accesibila numai pentru citire (read-only) si daca nucleul trebuie sa rotunjeasca adresa specificata de utilizator.
Valoarea întoarsa, virtaddr, reprezinta adresa virtuala la care nucleul a atasat regiunea, care nu trebuie sa fie neaparat egala cu valoarea ceruta de catre proces.
La executia apelului sistem shmat nucleul verifica daca procesul are drepturile necesare de acces la regiunea de memorie (Figura 10.10.). Acesta examineaza adresa specificata de catre utilizator daca este zero, atunci nucleul alege o adresa virtuala convenabila.
Algoritmul shmat /* ataseaza o zona de memorie */
intrari: (1)descriptorul zonei de memorie partajata
(2)adresa virtuala la care sa se faca atasarea
(3)indicatori
iesiri: adresa virtuala la care s-a atasat zona de memorie partajata
else /*utilizatorul doreste ca nucleul sa gaseasca o adresa
corespunzatoare*/
nucleul alege o adresa virtuala (daca nu este nici una disponibila întoarce
eroare);
ataseaza regiunea la spatiul de adrese al procesului(alg. attachreg)
if (regiunea este atasata pentru prima data)
aloca tabele de pagini, memorie pentru regiune (alg. growreg);
return(adresa virtuala la care s-a facut atasarea);
Figura 10.10. Algoritmul de atasare a unei zone de memorie partajata
Zona de memorie partajata nu trebuie sa se suprapuna peste alte regiuni din cadrul spatiului virtual de adrese al procesului, deci atât amplasarea cât si dimensiunea ei trebuie alese judicios pentru ca, în cazul unor modificari ulterioare de dimensiune, alte regiuni sa nu se extinda peste zona de memorie partajata. De exemplu, un proces poate mari dimensiunea regiunii sale de date prin apelul sistem brk, noua regiune de date fiind virtual contigua vechii regiuni de date în consecinta nucleul nu trebuie sa plaseze zona de memorie partajata în apropierea regiunii de date. Similar, nucleul nu trebuie sa plaseze zona de memorie partajata în apropierea vârfului stivei, pentru a preveni astfel fenomenul de crestere a stivei în interiorul zonei de memorie partajata. De exemplu, daca stiva creste catre valorile mari ale adreselor, cel mai bun loc de amplasare a zonei de memorie partajata ar putea fi imediat înaintea adresei de început a regiunii de stiva.
Nucleul verifica daca regiunea de memorie partajata încape în spatiul de adrese al procesului si o ataseaza, utilizând algoritmul attachreg. Daca procesul apelant este primul proces care ataseaza regiunea respectiva, atunci nucleul aloca tabelele necesare, utilizând algoritmul growreg, corecteaza valoarea continuta în câmpul momentul ultimei atasari al intrarii corespunzatoare din tabela de memorie partajata, si întoarce adresa virtuala la care a atasat regiunea respectiva.
Un proces detaseaza o regiune de memorie comuna din spatiul sau virtual de adrese cu ajutorul apelului sistem
shmdt(addr)
unde addr reprezinta adresa virtuala întoarsa de un apel shmat anterior. Desi ar parea mai logic ca apelul shmdt sa primeasca drept parametru un identificator, se întrebuinteaza totusi adresa virtuala a regiunii de memorie partajata pentru ca procesul sa poata face distinctie între mai multe instante ale unei regiuni de memorie partajata, care sunt atasate la spatiul virtual de adrese al procesului, si pentru ca identificatorul poate fi sters. Nucleul cauta regiunea atasata procesului la adresa virtuala indicata si o detaseaza din spatiul de adrese al procesului, utilizând algoritmul detachreg. Deoarece tabelele de regiuni nu contin pointeri înapoi catre tabela cu regiuni de memorie partajata, nucleul cauta în aceasta tabela intrarea corespunzatoare regiunii si corecteaza valoarea din câmpul care contine momentul ultimei detasari a regiunii.
Sa consideram programul din figura 10.11. Un proces creaza o regiune de memorie partajata de 128 Ko si o ataseaza de doua ori la spatiul sau de adrese, la doua adrese virtuale diferite. Procesul scrie date în prima regiune de memorie partajata si citeste date din cea de-a doua
ainclude <sys/types.h>
ainclude <sys/ipc.h>
ainclude <sys/shm.h>
adefine SHMKEY 75
adefine K 1024
int shmid;
main()
cleanup()
Figura 10.11. Atasarea unei regiuni de memorie partajata de doua ori
la spatiul de adrese al unui proces
Figura 10.12. prezinta un alt proces care ataseaza aceeasi regiune (acesta preia numai 64 Ko, pentru a ilustra faptul ca fiecare proces poate atasa zone de memorie partajata de dimensiuni diferite procesul respectiv asteapta pâna când primul proces scrie o valoare nenula în primul cuvânt al regiunii de memorie partajata si apoi citeste continutul acesteia. Primul proces executa un apel pause pentru a permite executia celui de al doilea când primul proces intercepteaza un semnal, el sterge regiunea de memorie partajata.
Pentru examinarea starii si stabilirea valorilor parametrilor unei regiuni de memorie partajata, un proces utilizeaza apelul sistem shmctl, care are urmatoarea sintaxa
shmctl(id, cmd, shmstatbuf)
unde id identifica intrarea corespunzatoare din tabela regiunilor de memorie partajata, cmd precizeaza tipul operatiunii, iar shmstatbuf reprezinta adresa unei structuri de date utilizator care contine informatiile de stare din intrarea în tabela regiunilor de memorie partajata, atunci când se face interogarea sau setarea parametrilor de stare.
ainclude <sys/types.h>
ainclude <sys/ipc.h>
ainclude <sys/shm.h>
adefine SHMKEY 75
adefine K 1024
int shmid;
main()
Figura 10.12. Folosirea în comun a memoriei de catre procese
Nucleul trateaza comenzile de interogare asupra starii si de schimbare a proprietarului si a drepturilor de acces, în mod asemanator implementarii adoptate pentru comunicarea prin mesaje. Când este stearsa o zona de memorie partajata, nucleul elibereaza intrarea corespunzatoare din tabela de regiuni de memorie partajata si examineaza continutul intrarii din tabela de regiuni daca nu mai exista nici un proces care sa aiba atasata regiunea respectiva la spatiul sau de adrese, atunci nucleul elibereaza intrarea din aceasta tabela si toate resursele asociate, utilizând algoritmul freereg. Daca regiunea mai este înca atasata la vreun proces (adica valoarea contorului de referinte este pozitiva), atunci nucleul sterge valoarea indicatorului care semnalizeaza ca regiunea nu trebuie sa fie eliberata la detasarea ei de la spatiul de adrese al ultimului proces. Procesele care utilizeaza regiunea de memorie partajata pot continua sa o faca, dar nici un alt proces nu o mai poate atasa. Atunci când toate procesele au detasat regiunea respectiva, nucleul o elibereaza. Acest caz este analog celui de la sistemul de fisier, în care un proces poate deschide un fisier si poate continua sa-l acceseze si dupa executarea apelului sistem unlink.
10.2.3.Comunicarea între procese prin semafoare
Semafoarele reprezinta una din solutiile la problema sincronizarii executiei proceselor, propusa de E.W. Dijkstra. În continuare se vor prezenta câteva probleme generale, de ordin teoretic, cu privire la semafoare. Un semafor reprezinta o variabila de tip întreg care indica numarul de activari salvate pentru viitoarea utilizare a unei resurse. Aceasta variabila poate avea valoarea
-zero, semnificând faptul ca nu s-au salvat activari pentru viitoarea
utilizare a resursei;
-n, pozitiva, semnificând faptul ca sunt în asteptare n activari ale resursei;
Un semafor S este un ansamblu format din variabilele întregi P(s) si firul de asteptare asociat, f(s). La crearea contorului de resurse, e(s), firul de asteptare f(s) este vid. Pentru a actiona asupra semafoarelor, Dijkstra propune doua primitive, P si V, care implementeaza generic doua actiuni: -activare si asteptare.
Astfel, procedura P(s) este executata de un proces oarecare, r, pentru ocuparea unei resurse si are urmatorul pseudocod
procedura P(s);
Valoarea e(s) are urmatoarele interpretari:
-daca e(s) > 0 atunci el indica numarul de resurse disponibile;
-daca e(s) < 0 atunci el indica numarul de cereri de resurse nesatisfacute;
Procedura V(s) este executata de un proces oarecare, q, pentru eliberarea unei resurse si are urmatorul pseudocod
procedura V(s);
Se pot enumera câteva proprietati ale semafoarelor
-un semafor nu poate fi initializat cu valori negative, dar poate deveni
negativ dupa un numar de operatii de tip P ;
-daca notam cu : -nP(s)-numarul de operatii P asupra lui S;
-nV(s)-numarul de operatii V asupra lui S;
-eo(s) -valoarea initiala a lui S;
atunci este adevarata relatia: e(s) = eo(s) - nP( s) + nV(s);
-fie nf(s) numarul de procese care, dupa ce au facut P(s), fie nu au fost
blocate, fie au fost blocate, dar au fost ulterior deblocate cu V(s).
Atunci este adevarata relatia: nf(s) nP(s);
-efectul primitivelor P si V asupra lui nf(s) este urmatorul:
- efectul primitivei P(s): nP(s) := nP(s) + 1;
daca nP(s) eo(s) + nV(s)
atunci nf(s) = nf(s) - 1;
- efectul primitivei V(s): nV(s) := nV(s) +1;
daca nP(s) eo(s) + nV(s)
atunci nf(s) = nf(s) + 1;
-executia primitivelor P si V lasa invarianta relatia: nf(s) = min( nP(s), eo(s) + nV(s)) ;
-observatie: - relatia de mai sus spune ca numarul de procese din firul de
asteptare este cel mai mic dintre numarul de cereri de resurse
si numarul de resurse disponibile.
În continuare se va prezenta problema producator-consumator, în care este vorba despre un set de N buffere. Procesul producator completeaza câte un buffer cu informatii, în timp ce procesul consumator foloseste aceste informatii golind buffere-le. Pentru implementarea celor doua proceduri se folosesc trei semafoare
plin -pentru numarul de buffere din set ocupate (initial, plin=0)
gol -pentru numarul de buffere din set libere (initial, gol=N)
mutex -pentru protejarea accesului la setul de buffere (initial,
mutex=1). Acesta este un semafor de excluziune mutuala.
procedura PRODUC~TOR();
procedura CONSUMATOR();
Pe lânga aspectele pozitive ale solutiei se pot semnala si unele limite ale metodei
-în sistemele mari este dificila folosirea semafoarelor;
-exista posibilitatea ca, din cauza unor erori de programare, sa se sara în mod accidental peste executarea procedurii P, ceea ce duce la accesul neprotejat la regiunile critice;
-în mod analog, daca se sare peste executia procedurii V, se poate ajunge la fenomenul de blocare;
-în procesul de compilare nu se poate verifica utilizarea corecta a semafoarelor;
Apelurile sistem pentru lucrul cu semafoare permit proceselor sa-si sincronizeze executia prin efectuarea unor operatii atomice asupra unei multimi de semafoare. Înainte de implementarea semafoarelor, daca un proces dorea blocarea accesului la o resursa, atunci trebuia sa creeze un fisier de blocare cu ajutorul apelului sistem creat. Apelul creat se încheie cu eroare daca fisierul exista deja, iar procesul presupune ca un altul a blocat accesul la resursa respectiva. Principalele dezavantaje ale acestei abordari sunt faptul ca procesele nu stiu când sa încerce din nou accesarea resursei si faptul ca fisierele de blocare pot ramâne nesterse atunci când sistemul iese în mod accidental din functiune, la urmatoarea initializare a sistemelor ele continuând sa blocheze accesul.
Dijkstra a publicat algoritmul Dekker care descrie o implementare a semafoarelor, care dupa cum s-a precizat sunt variabile de tip întreg pe care sunt definite doua operatii elementare (atomice) P si V. Operatia P decrementeaza valoarea semaforului daca aceasta este pozitiva, iar operatia V incrementeaza valoarea semaforului. Deoarece ambele operatii sunt elementare, în orice moment cel mult o singura operatie de tip P sau V se poate executa asupra unui semafor. Apelurile sistem pentru lucrul cu semafoare, din cadrul System V, sunt o generalizare a operatiilor P si V propuse de Dijkstra, în sensul ca se pot executa simultan mai multe operatii asupra unui semafor, si operatiile de incrementare sau decrementare se pot face asupra unor valori mai mari decât 1. Nucleul executa toate operatiile în mod atomic nici un alt proces neputând modifica valoarea semaforului pâna în momentul încheierii tuturor operatiilor. Daca nucleul nu poate executa toate operatiile, atunci nu executa nici una, caz în care procesul trece în asteptare pâna în momentul în care nucleul poate efectua toate operatiile.
În cadrul System V, un semafor se compune din urmatoatele elemente
¨valoarea semaforului
¨identificatorul ultimului proces care a modificat valoarea semaforului
¨numarul de procese care asteapta cresterea valorii semaforului
¨numarul de procese care asteapta ca valoarea semaforului sa fie egala cu 0
Apelurile sistem pentru lucrul cu semafoare sunt semget pentru a crea si pentru a obtine accesul la un set de semafoare, semctl pentru executarea diveritelor operatiuni de control asupra setului de semafoare si semop pentru a modifica valorile semafoarelor.
Figura 10.13. Structurile de date implicate în lucrul cu semafoare
Apelul sistem semget creaza o lista de semafoare, si are urmatoarea sintaxa
id = semget(key, count, flag)
unde key, id si flag au aceleasi semnificatii ca si parametrii corespunzatori din cadrul mecanismului de comunicare prin memorie patajata. Nucleul aloca o intrare care pointeaza catre o structura de tip lista de semafoare, cu count elemente. (Figura 10.13.). Intrarea mai specifica de asemenea si numarul de semafoare din lista, momentul executarii ultimului apel semop si momentul executarii ultimului apel semctl. De exemplu, apelul sistem semget din figura 10.14. creaza un semafor cu doua elemente.
Procesele opereaza asupra valorii semafoarelor cu ajutorul apelului sistem semop, având urmatoarea sintaxa
oldval = semop(id, oplist, count)
unde id este descriptorul întors de apelul semget, oplist este un pointer catre o lista de operatii asupra semaforului, iar count este dimensiunea listei.Valoarea întoarsa, oldval, reprezinta valoarea ultimului semafor din set asupra caruia s-au facut operatii, înainte de operatia respectiva. Formatul fiecarui element din oplist este urmatorul
¨numarul semaforului, care identifica intrarea corespunzatoare listei de
semafoare pe care se opereaza
¨operatia propriu-zisa
¨valoarea indicatorilor
Nucleul citeste lista de operatii asupra semafoarelor, oplist, din cadrul spatiului de adrese utilizator si verifica validitatea numerelor de semafoare si daca procesul are drepturile de acces necesare pentru a citi sau pentru a modifica valoarea semafoarelor (figura 10.15.). Daca procesului nu i se acorda permisiunea, atunci apelul sistem se încheie cu eroare. Daca, pe timpul executarii operatiilor asupra semafoarelor, nucleul trebuie sa astepte, atunci acesta restaureaza valorile initiale (de la începutul executiei apelului sistem) ale semafoarelor pe care deja le-a modificat, trecând apoi în asteptare pâna la producerea evenimentului pe care îl asteapta, dupa care reia de la început executia apelului sistem. Astfel, operatiile cu semafoare sunt executate atomic, adica fie toate odata, fie nici una.
Nucleul modifica valoarea unui semafor în conformitate cu valoarea operatiei. Daca aceasta este pozitiva, atunci nucleul incrementeaza valoarea semaforului si trezeste toate procesele care asteaptau ca valoarea semaforului sa creasca.
Daca valoarea operatiei este zero, atunci nucleul verifica valoarea semaforului daca aceasta este zero, atunci se continua cu celelalte operatii din lista de operatii, altfel nucleul incrementeaza numarul de procese care asteapta ca valoarea semaforului sa devina zero, si trece în asteptare. Daca valoarea operatiei este negativa, iar în valoare absoluta este mai mica sau egala cu valoarea semaforului, atunci nucleul aduna acea valoare (negativa) la valoarea semaforului. Daca rezultatul acestei însumari este zero, nucleul trezeste toate procesele care asteaptau ca valoarea semaforului sa devina zero. Daca valoarea semaforului este strict mai mica decât valoarea absoluta a operatiei, atunci nucleul pune procesul în asteptare pe evenimentul: valoarea semaforului creste. Ori de câte ori un proces asteapta în mijlocul executiei unei operatii cu semafoarele, el asteapta la un nivel de prioritate deci se va trezi la primirea unui semnal.
ainclude <sys/types.h>
ainclude <sys/ipc.h>
ainclude <sys/sem.h>
adefine SEMKEY 75
int semid;
unsigned int count;
/* definitia structurii de date sembuf din fisierul sys/sem.h> */
/* struct sembuf */
/* ; */
struct sembuf psembuf,vsembuf; /* operatii corespunzatoare lui P si V */
main(argc, argv)
int argc;
char *argvst;
else if(argvs1ts0t=='a')
else
semid=semget(SEMKEY, 2, 0777);
psembuf.semÎop=-1;
psembuf.semÎflg=SEMÎUNDO;
vsembuf.semÎop=1;
vsembuf.semÎflg=SEMÎUNDO;
for(count=0; ;count++)
}
cleanup()
Figura 10.14. Operatii de blocare si de deblocare
Algoritmul semop /*operatii asupra semafoarelor */
intrari:(1)descriptorul de semafor
(2)lista de operatii asupra semafoarelor
(3)numarul de elemente din lista de semafoare
iesiri:valoarea initiala a ultimului semafor care a fost modificat
else if(operatia este negativa)
anuleaza toate operatiile deja facute cu semafoarele în acest
apel sistem(iteratia anterioara);
if(indicatorii specifica sa nu astepte)
return(eroare);
sleep(eveniment:valoarea semaforului creste);
goto start; /* se reia ciclul */
}
else /* valoarea operatiei este zero */
}
}
/* toate operatiile cu semafoare au reusit */
actualizeaza înregistrarile de timp, identificatorii proceselor;
return(valoarea initiala a ultimului semafor modificat);
Figura 10.15. Algoritmul pentru operarea asupra semafoarelor
Sa consideram programul din figura 10.14, (a.out) si sa presupunem ca un utilizator îl executa de trei ori în urmatoarea secventa
a.out &
a.out a &
a.out b &
Atunci când programul este rulat fara parametri, procesul creaza un set de semafoare cu doua elemente, ale caror valori sunt initializate cu 1. Apoi, procesul executa apelul pause( ) si trece în starea de asteptare pâna când este trezit de un semnal, si sterge semaforul în cadrul functiei cleanup( ). Atunci când programul se executa cu parametrul 'a', procesul (A) face în cadrul ciclului patru operatii distincte cu semafoarele decrementeaza valoarea semaforului 0, decrementeaza valoarea semaforului 1, executa instructiunea printf si apoi incrementeaza valoarea semaforului 1 si a semaforului 0. Daca un proces încearca decrementarea valorii unui semafor care are deja valoarea 0, atunci este trecut în starea de asteptare si în consecinta semaforul se considera blocat. Deoarece semafoarele au fost initializate cu valoarea 1 si nici un alt proces nu le mai foloseste, procesul A nu va trece niciodata în starea de asteptare, iar valorile semafoarelor vor bascula între valorile 0 si 1. Atunci când programul se executa cu parametrul 'b', procesul (B) decrementeaza semafoarele 0 si 1 în ordinea inversa celei în care a facut-o procesul A.
Atunci când procesele A si B se executa simultan, poate aparea situatia în care procesul A a blocat semaforul 0 si doreste sa blocheze semaforul 1, iar procesul B a blocat semaforul 1 si doreste sa blocheze semaforul 0. În acest caz, ambele procese trec în starea de asteptare, fiind în imposibilitatea de a-si continua executia. Ele s-au blocat reciproc si îsi încheie executia numai la primirea unui semnal.
Pentru a evita astfel de probleme, procesele pot executa simultan mai multe operatiuni asupra semafoarelor. Acest rezultat se poate obtine folosind urmatoarele date si instructiuni
struct sembuf psembufs t
psembufs t.sem num=0;
psembufs t.sem num=1;
psembufs t.sem op=-1;
psembufs t.sem op=-1;
semop(semid, psembuf, 2);
Structura psembuf reprezinta o lista de operatii care decrementeaza simultan semafoarele 0 si 1. Daca vreuna din operatii nu se poate executa, atunci procesul intra în starea de asteptare pâna când ambele operatii se pot executa atomic. De exemplu, daca valoarea semaforului 0 este 1 si valoarea semaforului 1 este 0, atunci nucleul va lasa ambele valori nemodificate pâna în momentul în care le poate decrementa pe amândoua.
Un proces poate activa indicatorul IPC NOWAIT în cadrul apelului sistem semop daca nucleul ajunge în situatia în care procesul trebuie sa treaca în starea de asteptare pâna când valoarea semaforului creste sau devine egala cu zero, atunci nucleul revine din apelul sistem cu eroare. Astfel, este posibila realizarea unui semafor conditional, la care un proces sa nu treca în starea de asteptare daca nu poate executa operatia atomic.
Situatii periculoase pot aparea atunci când un proces executa o operatie asupra unui semafor, probabil pentru blocarea unei resurse, si apoi îsi încheie executia fara sa restabileasca valoarea initiala a semaforului. Astfel de situatii pot aparea ca rezultat al unei greseli de programare, sau ca rezultat al receptionarii unui semnal care determina încheierea imediata a executiei unui proces. Daca, în figura 10.14., procesul receptioneaza un semnal kill dupa decrementarea valorilor semafoarelor, el nu va mai putea sa le incrementeze din nou, deoarece semnalele kill nu pot fi tratate explicit sau ignorate. În consecinta, alte procese vor gasi semaforul blocat chiar daca procesul care a realizat blocarea nu mai exista. Pentru a evita asemenea probleme, un proces poate seta valoarea indicatorului SEM UNDO în cadrul apelului sistem semop atunci când procesul îsi încheie executia, nucleul inverseaza efectul fiecarei operatii cu semafoare pe care a facut-o procesul respectiv. Pentru a implementa aceasta facilitate, nucleul memoreaza o tabela care contine o intrare pentru fiecare proces din sistem. Fiecare intrare indica un set de structuri undo, câte una pentru fiecare semafor folosit de catre proces (Figura 10.16.). Fiecare structura undo este o lista de tripleti care contin un identificator de semafor, un numar de semafor din setul determinat de identificatorul de semafor si o valoare de corectie.
![]() |
Figura 10.16. Structurile Undo pentru semafoare
Atunci când un proces executa primul sau apel semop, având setat indicatorul SEM UNDO, nucleul aloca dinamic structuri de tip undo. La urmatoarele apeluri semop care seteaza indicatorul SEM UNDO, nucleul cauta printre structurile de tip undo pe aceea care contine acelasi indentificator de semafor si acelasi numar ca si cele din apelul semop daca gaseste o asemenea structura, atunci nucleul scade valoarea operatiei din valoarea de corectie. Astfel, structura undo va contine suma cu semn schimbat a valorilor tuturor operatiilor pe care procesul le-a executat asupra semaforului pentru care a fost setat indicatorul SEM UNDO. Daca nu exista o asemenea structura pentru semafor, atunci nucleul va crea una, gestionând totodata o lista de structuri de tip undo ordonate dupa identificatorul de semafor si dupa numar. Daca o valoare de corectie devine zero, atunci nucleul elimina structura de tip undo care o contine. Atunci când un proces îsi încheie executia, nucleul apeleaza o rutina speciala care parcurge toate structurile undo asociate procesului modificând semafoarele corespunzatoare.
Revenind la figura 10.14., nucleul creaza o structura undo ori de câte ori un proces decrementeaza valoarea unui semafor si elimina o structura de tip undo ori de câte ori procesul incrementeaza valoarea unui semafor, deoarece valoarea de corectie din structura undo devine zero.
Figura 10.17. Secventa de structuri Undo
Figura 10.17 prezinta succesiunea evolutiei structurilor de tip undo în cazul executiei programului cu parametrul 'a'. Dupa prima operatie, procesul contine o singura structura de tip undo corespunzatoare numarului de semafor 0 si valorii de corectie 1, iar dupa cea de-a doua operatie procesul contine si o doua structura corespunzatoare numarului de semafor 1 si valorii de corectie 1. Daca procesul si-ar încheia executia în acest moment, atunci nucleul ar parcurge fiecare structura, adaugând valoarea 1 la fiecare semafor, readucându-le la valoarea la zero. În mod obisnuit, nucleul decrementeaza valoarea de corectie a semaforului 1 pe timpul celei de-a treia operatii, corespunzator cresterii valorii semaforului, si elimina structura, deoarece valoarea sa de corectie a devenit zero. Dupa cea de-a patra operatie, procesul nu mai contine nici o structura de tip undo, deoarece toate valorile de corectie vor fi zero.
Operatiile din lista de operatii asupra semafoarelor permit proceselor sa evite blocarile, dupa cum a fost exemplificat mai sus, dar sunt complicate si majoritatea aplicatiilor nu au nevoie de întreaga lor capacitate. Aplicatiile care necesita utilizarea mai multor semafoare vor trebui sa rezolve problema conditiilor de blocare reciproca a proceselor la nivel utilizator, pentru ca nucleul sa nu contina astfel de apeluri sistem complicate.
Apelul sistem semctl contine o multime de operatii de control relative la semafoare, si are urmatoarea sintaxa
semctl(id, number, cmd, arg)
Parametrul arg este declarat de tip union
union semunion
arg;
Nucleul interpreteaza parametrul arg pe baza valorii parametrului cmd, în mod asemanator celui în care interpreteaza comenzile ioctl. Efectele comenzii se produc pentru valori ale parametrilor cmd care restaureaza sau seteaza parametrii de control (drepturile de acces si alti parametri), initializeaza una sau toate valorile semafoarelor dintr-un set de semafoare, sau realizeaza citirea valorilor semafoarelor. Pentru comanda de stergere a unui semafor, IPC RMID, nucleul gaseste toate procesele care au structuri de tip undo pentru semaforul respectiv si sterge aceste structuri. Apoi, reinitializeaza structura de date pentru semafoare si trezeste toate procesele care asteapta producerea unui eveniment relativ la semafoare. Atunci când procesele îsi reiau executia, vor constata ca identificatorul de semafor nu mai este valabil si vor întoarce o eroare catre apelant.
10.2.4.Concluzii generale
Exista câteva asemanari între sistemul de fisiere si mecanismele de comunicare între procese (mecanismele IPC). Apelurile sistem "get" sunt similare apelurilor sistem creat si open, iar apelurile sistem "control" contin o optiune de stergere a descriptorilor din sistem, ca si apelul sistem unlink. Dar nu exista nici o operatiune analoaga apelului sistem close, din cadrul sistemului de fisiere. Astfel, nucleul nu are o evidenta a proceselor care pot accesa vreunul din mecanismele de comunicare, si întradevar, procesele pot avea acces la unul din mecanismele de comunicare, cu conditia sa cunoasca identificatorul corect si sa îndeplineasca restrictia privind drepturile de acces, desi aceste procese nu au invocat vreodata un apel sistem de tip "get". Nucleul nu poate sterge automat structurile de date ale mecanismelor de comunicare, deoarece el nu stie daca acestea mai sunt sau nu folosite. În acest fel, procesele care nu sunt programate foarte minutios pot lasa la voia întâmplarii, dupa încheierea executiei lor, strucuri de date care nu mai sunt necesare nimanui si, în consecinta, care nu mai sunt folosite de nimeni. Cu toate ca, dupa încheierea executiei unui proces, nucleul poate salva informatiile de stare si datele în cadrul structurilor de date ale mecanismelor de comunicare, totusi este mai indicata folosirea fisierelor în acest scop.
Mecanismele de comunicare între procese introduc un termen nou, chei, în locul arhicunoscutelor si traditionalelor fisiere. Este dificila extinderea semanticii cheilor la nivelul unei retele, deoarece acestea pot descrie obiecte diferite de pe masini diferite. Pe scurt, cheile au fost gândite pentru a opera într-un mediu concentrat (de tip "o singura masina"). Numele de fisiere sunt mai potrivite pe un mediu distribuit. Utilizarea cheilor în locul numelor de fisiere, mai implica si faptul ca facilitatile IPC sunt entitati date ca atare, folositoare în cadrul aplicatiilor dedicate, dar carora le lipsesc posibilitatile de construire de instrumente, esentiale în cazul fisierelor si al pipe-urilor, de exemplu. Mare parte din functionalitatea lor poate fi emulata prin folosirea celorlalte facilitati ale sistemului, astfel ca, din punctul de vedere al elegantei în lucru, ele nu ar trebui incluse în nucleu. Totusi, în cazul aplicatiilor care coopereaza, ele asigura performante superioare fata de facilitatile oferite de sistemul de fisiere.
10.3 Comunicare prin socket-uri
Modul în care pot comunica procesele care se executa pe masini diferite difera în functie de protocol si de mediul de transmisie. Mai mult, aceste metode nu pot permite comunicarea între procese de pe aceeasi masina, deoarece ele presupun existenta unui proces server care se afla în asteptare în cadrul unui apel sistem open sau read, relativ la un driver. Pentru a furniza metode generale de comunicare între procese si pentru a permite utilizarea protocoalelor de retea sofisticate, sistemul BSD pune la dispozitie un mecanism cunoscut sub numele de comunicare prin socket-uri. În acest subcapitol sunt descrise unele aspecte, la nivel utilizator, ale socket-urilor.
Structura acestui mecanism la nivelul nucleului, contine trei parti nivelul socket, nivelul protocol si nivelul dispozitiv (Figura 10.18). Nivelul socket furnizeaza interfata dintre apelurile sistem si nivelurile inferioare, nivelul protocol contine modulele de protocoale folosite pentru comunicatie (în figura, TCP si IP), iar nivelul dispozitiv contine driverele de care controleaza lucrul dispozitivelor de retea. La configurarea sistemului se precizeaza combinatiile de protocoale acceptate si driverele, metoda care nu este la fel de flexibila ca lucrul cu stream-uri. Procesele comunica dupa modelul client-server un proces server asculta la un socket, unul din capetele liniei bidirectionale de comunicatii, iar procesul client comunica cu serverul prin alt socket, adica celalalt capat al liniei, care poate fi pe o alta masina. Nucleul pastreaza conexiunile interne si directioneaza datele de la client catre server.
Figura 10.18. Modelul comunicarii prin socket-uri
Socket-urile care au aceleasi proprietati de comunicatie, cum ar fi conventiile de nume si formatele de adresa, sunt grupate în domenii. Sistemul BSD 4.2 suporta domeniul UNIX de comunicare între procesele de pe aceeasi masina, si domeniul Internet de comunicare între procese de pe masini diferite prin retea utilizând protocoalele de comunicatie DARPA(Defense Advanced Research Project Agency). Fiecare socket are un tip, si anume circuit virtual (stream socket, în terminologia Berkeley) sau datagrama. Un circuit virtual permite accesul secvential si functionarea corecta a datelor. Datagramele nu garanteaza accesul secvential la date si integritatea acestora, în schimb sunt mai putin costisitoare decât circuitele virtuale, pentru ca nu necesita operatiuni de initializare deosebite prin urmare ele sunt utile pentru anumite tipuri de comunicari. Sistemul contine un protocol implicit pentru fiecare combinatie acceptata de tip domeniu-socket. De exemplu, protocolul TCP (Transport Connect Protocol) furnizeaza serviciul de circuit virtual, iar protocolul UDP (User Datagram Protocol) furnizeaza serviciul de datagrame, în domeniul Internet.
Mecanismul de comunicare prin socket-uri contine câteva apeluri sistem. Apelul sistem socket stabileste punctul final al unei legaturi de comunicatie.
sd = socket(format, type, protocol);
unde parametrul format precizeaza domeniul de comunicatie (domeniul UNIX, sau domeniul Internet), type indica tipul de comunicatie prin socket (circuit virtual, sau datagrama), iar protocol precizeaza un anumit protocol pentru controlul comunicatiei. În cadrul altor apeluri sistem, procesele vor întrebuinta descriptorul de socket, sd. Apelul sistem close închide socket-urile.
Apelul sistem bind asociaza un nume descriptorului de socket
bind(sd, address, length)
unde sd este descriptorul de socket, iar address este adresa unei structuri care precizeaza un indicator al domeniului si protocolului de comunicatie, precizate în cadrul apelului sistem socket. Parametrul length reprezinta lungimea structurii de date address fara acest parametru nucleul nu ar sti cât de lunga este adresa, deoarece lungimea acesteia poate diferi de la un domeniu (sau protocol) la altul. De exemplu, în cadrul domeniului UNIX, o adresa este un nume de fisier. Procesul server ataseaza adresele din apelul bind unor socket-uri si face publice numele lor pentru a fi identificate de catre procesele client.
Apelul sistem connect cere nucleului sa faca o conexiune cu un socket existent
connect(sd, address, length)
unde semnificatia parametrilor este aceeasi ca la apelul bind, cu deosebirea ca parametrul address reprezinta adresa socket-ului destinatie care va constitui celalalt capat al liniei de comunicatie. Ambele socket-uri trebuie sa foloseasca acelasi domeniu si protocol de comunicatie, ramânând în sarcina nucleului initializarea corecta a legaturilor de comunicatie. Daca tipul socket-ului este datagrama, atunci apelul connect informeaza nucleul asupra adresei de utilizat în cadrul apelurilor send ulterioare prin socket-ul respectiv în momentul apelului nu se realizeaza nici o legatura.
Atunci când un proces server accepta legaturile printr-un circuit virtual, nucleul trebuie sa puna într-o coada de asteptare cererile care sosesc, pâna în momentul în care va putea sa le satisfaca. Apelul sistem listen precizeaza lungimea maxima a cozii de asteptare
listen(sd, qlength)
unde sd este descriptorul de socket si qlength reprezinta numarul maxim de cereri care vor fi luate în consideratie.
Figura 10.19. Acceptarea unui apel de catre un proces server
Apelul sistem accept primeste cererile de conectare la un proces server
nsd = accept(sd, address, addrlen)
unde sd este descriptorul de socket, address indica o zona de date utilizator pe care nucleul o completeaza cu adresa de retur a procesului client care se conecteaza, iar addrlen precizeaza dimensiunea acestei zone. La revenirea din apelul accept, nucleul scrie în addrlen un numar care semnifica dimensiunea spatiului ocupat în zona de date. Apelul accept întoarce un nou descriptor de socket, nsd, diferit de descriptorul sd. Un proces server poate continua sa asculte la socket-ul anuntat, în timp ce comunica cu un proces client pe un canal separat de comunicatie (Figura 10.19).
Apelurile sistem send si recv permit transferul datelor printr-un socket. Sintaxa apelului sistem send este
count = send(sd, msg, length, flags)
unde sd este descriptorul de socket, msg este un pointer catre datele care urmeaza sa fie transmise, length reprezinta lungimea datelor de transmis, iar count este numarul de octeti efectiv transmisi. Parametrului flags i se poate atribui valoarea SOF OOB pentru a realiza o transmitere out-of-band a unor date, întelegând prin aceasta ca datele trimise nu fac parte din schimbul obisnuit de date între procesele care comunica. De exemplu, un program de deschidere de sesiune la distanta poate trimite un mesaj out-of-band pentru a simula apasarea tastei Del de catre un utilizator, la un terminal. Sintaxa apelului sistem recv este
count = recv(sd, buf, length, flags)
unde buf este locul unde se memoreaza datele care sosesc, length este lungimea asteptata a datelor, iar count este numarul de octeti efectiv copiati în programul utilizatorului. Parametrul flags poate primi valoarea peek , pentru un mesaj care soseste, în scopul examinarii continutului sau fara a-l scoate din coada de asteptare, sau valoarea out-of-band pentru cazul explicat mai sus. Versiunile pentru datagrame ale acestor apeluri sistem, sendto si recvfrom, au în plus parametri pentru adrese. Procesele pot utiliza apelurile sistem read si write în cazul circuitelor virtuale, în locul apelurilor send si recv, dupa initializarea legaturii. Astfel, procesele server se pot îngriji de negocierea protocoalelor de retea si de crearea proceselor care utilizeaza numai apeluri sistem read si write, ca si când acestea ar lucra cu fisiere obisnuite.
Apelul sistem shutdown închide o conexiune cu un socket
shutdown(sd, mode)
unde mode indica daca partea care trimite datele, partea care le receptioneaza, sau ambele parti încheie transmisia datelor. Acest apel informeaza protocoalele de nivel inferior sa încheie comunicarea prin retea, însa cu mentinerea intacta a descriptorilor de socket-uri. Apelul sistem close elibereaza si descriptorul de socket.
Apelul sistem getsockname furnizeaza numele asociat unui socket, printr-un apel bind anterior
getsockname(sd, name, length)
Apelurile getsockopt si setsockopt obtin si respectiv seteaza diferite optiuni asociate socket-ului, în concordanta cu domeniul si protocolul de comunicatie prin socket.
Sa consideram programul server din figura 10.20. Procesul creaza un circuit virtuak în cadrul domeniului UNIX si printr-un apel bind îi asociaza numele sockname. Apoi invoca apelul sistem listen, pentru a preciza lungimea cozii de asteptare pentru mesajele care sosesc si intra într-o bucla, asteptând cererile care sosesc.
Apelul accept asteapta pâna când protocolul de nivel inferior notifica sosirea unei cereri de legatura prin socket-ul care are numele respectiv apoi, apelul accept întoarce un nou descriptor pentru cererea care soseste.
Procesul server creaza un proces care sa comunice cu procesul client procesele parinte si fiu îsi închid descriptorii initiali pentru a evita interferarea cu comunicarile celorlalte procese. Procesul fiu îsi continua dialogul cu procesul client, încheindu-si executia (în acest exemplu) dupa apelul read. Procesul server reintra în bucla si asteapta o alta cerere de legatura, în cadrul apelului accept.
ainclude <sys/types.h>
ainclude <sys/socket.h>
main()
close(ns);
}
Figura 10.20. Un proces server din cadrul domeniului UNIX
Figura 10.21 prezinta procesul client corespunzator procesului server. Procesul creaza un un socket în acelasi domeniu ca si serverul si emite o cerere connect pentru numele sockname, asociat unui socket anume de catre procesul server. La iesirea din apelul connect, procesul client dispune de un circuit virtual catre un proces server. În acest exemplu, procesul client scrie un singur mesaj si îsi încheie executia. Daca procesul server furnizeaza servicii proceselor dintr-o retea, atunci în cadrul apelurilor sale sistem se specifica faptul ca socket-ul apartine domeniului Internet, prin socket(AF INET, SOCK STREAM, 0) si i se asociaza o adresa de retea obtinuta dintr-un nume de server. Sistemul BSD dispune de apeluri de biblioteca, care îndeplinesc aceste functii.
ainclude <sys/types.h>
ainclude<sys/socket.h>
main()
Figura 10.21 Un proces client din cadrul domeniului UNIX
Similar, al doilea parametru al apelului connect facut de procesul client, trebuie sa contina informatia de adresa necesara identificarii masinii în retea (sau adresa de rutare pentru transmiterea mesajelor la masina de destinatie, prin masini intermediare), precum si informatii suplimentare de identificare a unui anumit socket de pe masina de destinatie. Daca procesul server doreste sa asculte atât în retea, cât si la procesele locale, el trebuie sa foloseasca doua socket-uri si apelul sistem select pentru a determina care din procesele client se conecteaza.
|