CATEGORII DOCUMENTE |
Comunicarea intre procese prin pachetul IPC - UNIX
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 in comun parti din spatiul lor virtual de adrese, iar semafoarele permit proceselor sa-si sincronizeze executia. Implementate in 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 incluzand o cheie si un indicator. Nucleul cauta in tabela corespunzatoare intrarea desemnata de catre cheie. Procesele pot invoca apelurile de tip "get" cu parametrul cheie avand valoarea IPCIPRIVATE, pentru a se asigura de obtinerea unei intrari nefolosite. Procesele pot seta indicatorul la valoarea IPCICREAT pentru a crea o noua intrare, daca cea precizata prin cheie nu exista deja, si de asemenea pot determina semnalizarea erorilor prin pozitionarea indicatorilor IPCIEXCL si IPCICREAT, pentru situatia in 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, rezultand de aici analogia cu apelurile sistem creat si open.
In 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. Cand 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, in cazul unei noi alocari a acesteia printr-un apel de tip "get". Orice incercare ulterioara a proceselor de a accesa intrarea respectiva, pe baza vechiului descriptor, se va solda cu eroare. In legatura cu exemplul de mai sus, daca descriptorul asociat intrarii 1 din tabela pentru mesaje este 201, atunci cand intrarea respectiva este eliberata, nucleul ii atribuie acesteia un nou descriptor, 301. Procesele care incearca 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 incluzand identificatorul de utilizator si identificatorul de grup ale procesului care a creat intrarea, un identificator 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. Cand 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. In 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 in cauza este executat de catre superutilizator dreptul de scriere nu este suficient pentru a se permite modificarea parametrilor unei intrari. Nucleul copiaza datele precizate de catre utilizator in intrarea din tabela, setand valorile identificatorilor utilizator si de grup, permisiunile de acces, precum si continutul altor campuri, in functie de tipul mecanismului de comunicare. Nucleul nu modifica valorile identificatorilor utilizator si de grup apartinand celui care a creat intrarea, astfel incat utilizatorul care a creat intrarea respectiva isi pastreaza drepturile de control asupra acesteia. In sfarsit, 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 incat, la urmatoarea alocare acesteia el va returna un alt descriptor. Din acest motiv, asa cum s-a explicat mai sus, apelurile sistem vor intoarce un mesaj de eroare, daca un proces va incerca sa acceseze o intrare utilizand unul din vechii descriptori.
1. Comunicarea intre procese prin mesaje
Exista patru apeluri sistem pentru comunicarea prin mesaje msgget intoarce (sau creeaza) un descriptor de mesaje, care desemneaza o coada de mesaje, utilizat ca parametru in 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, in cazul general al apelurilor de tip "get". Nucleul memoreaza mesajele sub forma unei liste inlantuite (coada de mesaje) careia ii corespunde un descriptor, si foloseste un descriptor de coada de mesaje -msgqid- ca index intr-un tabela de antete de cozi de mesaje. In plus fata de campul permisiuni asociat oricarei mecanism IPC, mentionat mai sus, structura de date asociata unei cozi de mesaje contine urmatoarele campuri
Pointeri catre primul si ultimul mesaj din lista inlantuita
Numarul de mesaje si numarul total de octeti de date din lista inlantuita
Numarul maxim de octeti de date pe care ii poate contine lista
inlantuita
Identificatorii ultimelor procese care au trimis si, respectiv receptionat,
mesaje
Momentele de timp ale ultimelor operatii msgsnd, msgrcv si msgctl.
Atunci cand un utilizator invoca apelul sistem msgget pentru a crea un nou descriptor, nucleul cauta in 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 avand urmatoarea sintaxa
msgsnd(msgqid, msg, count, flag)
unde msgqid este descriptorul cozii de mesaje, returnat in 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 intregi) si un sir de caractere, count specifica dimensiunea in octeti a sirului de caractere din structura msg, iar flag precizeaza actiunea pe care urmeaza sa o intreprinda nucleul, in situatia in care nu mai are la dispozitie suficient spatiu in 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 in spatiul nucleu; actualizeaza structura de date: -insereaza in lista noul antet de mesaj; -initializeaza pointerul din antetul de mesaj sa pointeze catre zona de date; -inscrie 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
In 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 intreaga si pozitiva. Daca toate conditiile sunt indeplinite, 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 sfarsitul listei inlantuite de antete, corespunzatoare cozii respective. In antet se inscriu tipul si dimensiunea mesajului, se initializeaza antetul pentru a referi sirul de caractere ce constituie mesajul propriu-zis, iar in coada de antete se actualizeaza campurile 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 in coada de mesaje. Daca numarul de octeti depaseste limita cozii de mesaje, procesul care trimite mesajul se pune in asteptare pana la eliberarea spatiului din coada de mesaje, prin trimiterea altor mesaje deja existente in coada. Daca procesul a semnalizat nucleului sa nu-l treaca in asteptare (cu ajutorul indicatorului IPCINOWAIT), atunci el revine imediat din apelul sistem, cu eroare. Figura 10.5 prezinta mesajele dintr-o coada, evidentiind coada de antete, listele inlantuite 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 intreg, isi copiaza propriul identificator de proces in zona de text a mesajului, atribuie valoarea 1 pentru tipul mesajului, apoi invoca apelul sistem msgsnd pentru a trimite mesajul. Vom reveni mai tarziu 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 in 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 in coada de mesaje. Valoarea intoarsa de apel, count, reprezinta numarul de octeti de date receptionati efectiv de catre utilizator.
In figura 10.7 nucleul verifica daca utilizatorul are drepturile necesare de acces la coada de mesaje. Daca mesajul cerut de utilizator are valoarea din campul type egala cu 0, nucleul va localiza primul mesaj din lista inlantuita. Daca lungimea mesajului este mai mica sau egala cu dimensiunea ceruta de utilizator, atunci nucleul copiaza mesajul propriu-zis in structura de date a utilizatorului si isi actualizeaza in mod corespunzator structurile interne de date decrementeaza valoarea contorului de mesaje aflate in lista si numarul de octeti de date din coada de mesaje, inscrie momentul receptionarii mesajului si identificatorul procesului care a receptionat mesajul, reface lista inlantuita ca urmare a eliminarii mesajului si elibereaza spatiul in care a fost memorat mesajul.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSGKEY 75 /*cheia mesajului=index in 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 in asteptare ca urmare a faptului ca nu a existat spatiu suficient in 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 in asteptare) return (eroare); sleep(eveniment:sosirea unui mesaj in coada de mesaje); goto ciclu; } |
Figura 10.7. Algoritmul pentru receptionarea unui mesaj
Daca lungimea mesajului este mai mare decat parametrul maxcount precizat de utilizator, atunci nucleul intoarce eroare la revenirea din apelul sistem si lasa mesajul in lista. Daca procesul ignora restrictiile de dimensiune a mesajelor (prin pozitionarea indicatorului MSGINOERROR, atunci nucleul trunchiaza mesajul, intoarce numarul de octeti cerut si sterge intregul mesaj din lista. Prin stabilirea corespunzatoare a valorii parametrului type, un proces poate receptiona mesaje de un anumit tip. Daca valoarea este un intreg pozitiv, atunci nucleul intoarce primul mesaj de tipul respectiv. Daca valoarea este un intreg negativ, atunci nucleul cauta tipul cel mai mic ca valoare dintre toate mesajele din coada de mesaje, presupus a fi mai mic sau egal in valoare absoluta decat valoarea type, si intoarce 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 intoarce mesajul care are tipul 1, din cadrul cozii de mesaje. In toate cazurile, daca nici un mesaj din coada de mesaje nu satisface conditia de tip din cererea de receptionare, nucleul trece procesul in starea de asteptare, daca acesta nu a specificat revenirea imediata din contextul nucleu prin pozitionarea bitului IPCINOWAIT.
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 creeaza o structura de mesaj prin setarea flag-ului, din cadrul apelului mssget, la valoarea IPCICREAT 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.
In acest exemplu, procesul server isi trimite propriul identificator de proces catre procesul client in 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 cand 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 in cadrul sistemului de fisiere.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #define 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, in ordinea in 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 intre procese.
Un proces poate inspecta starea unui descriptor de mesaj, ii 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.
Intorcandu-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 ramane in sistem, chiar daca nici un proces nu o mai refera. Incercarile ulterioare de a crea (in exclusivitate) o noua coada de mesaje pe baza cheii date, vor esua pana in momentul in care vechea coada va fi stearsa.
2.Comunicarea intre procese prin zone de memorie partajata
Procesele pot comunica direct unele cu celelalte prin folosirea in comun a unor parti din spatiul lor virtual de adrese si prin scrierea si citirea datelor memorate in memoria partajata. Apelurile sistem pentru manipularea zonelor de memorie partajata sunt similare apelurilor sistem dedicate comunicatiei prin mesaje. Apelul sistem shmget creeaza o noua zona regiune de memorie partajata sau intoarce 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 partajata utilizand aceleasi instructiuni masina pe care le utilizeaza si la scrierea ori citirea din memoria obisnuita. Dupa atasarea unei zone de memorie partajata, aceasta devine parte a spatiului virtual de adrese al procesului, accesibila in acelasi mod in 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 in tabela cu zonele de memorie partajata daca gaseste in aceasta tabela o intrare care sa corespunda cheii si daca drepturile de acces sunt validate, atunci intoarce descriptorul acelei intrari din tabela. Daca nu gaseste o intrare careia sa-i corespunda cheia key, si daca utilizatorul a dat indicatorului flag valoarea IPCICREAT, in scopul crearii unei noi regiuni comune de memorie, atunci nucleul verifica daca valoarea parametrului size se situeaza intre valorile limita minima si maxima de memorie ale sistemului si apoi aloca o structura de date pentru o regiune de memorie, utilizand algoritmul allocreg. Nucleul memoreaza in 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 ii este asociata memorie. Nucleul aloca memorie regiunii (tabele de pagini s.a.m.d.) numai atunci cand un proces ataseaza regiunea respectiva la spatiul sau de adrese. De asemenea, nucleul stabileste valoarea unui indicator din intrarea in tabela de regiunilor pentru a indica faptul ca regiunea nu trebuie eliberata atunci cand ultimul proces care a atasat-o spatiului sau de adrese se termina. Astfel, datele din zona de memorie partajata raman intacte desi nici un proces nu mai include zona respectiva in spatiul sau virtual de adrese.
Figura 10.9. Structurile de date implicate in 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 intoarsa, 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 intoarce 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 atat amplasarea cat si dimensiunea ei trebuie alese judicios pentru ca, in 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 in consecinta nucleul nu trebuie sa plaseze zona de memorie partajata in apropierea regiunii de date. Similar, nucleul nu trebuie sa plaseze zona de memorie partajata in apropierea varfului stivei, pentru a preveni astfel fenomenul de crestere a stivei in 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 inaintea adresei de inceput a regiunii de stiva.
Nucleul verifica daca regiunea de memorie partajata incape in spatiul de adrese al procesului si o ataseaza, utilizand algoritmul attachreg. Daca procesul apelant este primul proces care ataseaza regiunea respectiva, atunci nucleul aloca tabelele necesare, utilizand algoritmul growreg, corecteaza valoarea continuta in campul ²momentul ultimei atasari² al intrarii corespunzatoare din tabela de memorie partajata, si intoarce 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 intoarsa de un apel shmat anterior. Desi ar parea mai logic ca apelul shmdt sa primeasca drept parametru un identificator, se intrebuinteaza totusi adresa virtuala a regiunii de memorie partajata pentru ca procesul sa poata face distinctie intre 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, utilizand algoritmul detachreg. Deoarece tabelele de regiuni nu contin pointeri inapoi catre tabela cu regiuni de memorie partajata, nucleul cauta in aceasta tabela intrarea corespunzatoare regiunii si corecteaza valoarea din campul care contine momentul ultimei detasari a regiunii.
Sa consideram programul din figura 10.11. Un proces creeaza 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 in ²prima² regiune de memorie partajata si citeste date din cea de-a ²doua²
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define SHMKEY 75 #define 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 pana cand primul proces scrie o valoare nenula in primul cuvant al regiunii de memorie partajata si apoi citeste continutul acesteia. Primul proces executa un apel pause pentru a permite executia celui de al doilea cand 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 in tabela regiunilor de memorie partajata, atunci cand se face interogarea sau setarea parametrilor de stare.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define SHMKEY 75 #define K 1024 int shmid; main() |
Figura 10.12. Folosirea in comun a memoriei de catre procese
Nucleul trateaza comenzile de interogare asupra starii si de schimbare a proprietarului si a drepturilor de acces, in mod asemanator implementarii adoptate pentru comunicarea prin mesaje. Cand 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, utilizand algoritmul freereg. Daca regiunea mai este inca 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 cand toate procesele au detasat regiunea respectiva, nucleul o elibereaza. Acest caz este analog celui de la sistemul de fisier, in care un proces poate deschide un fisier si poate continua sa-l acceseze si dupa executarea apelului sistem unlink.
3.Comunicarea intre procese prin semafoare
Semafoarele reprezinta una din solutiile la problema sincronizarii executiei proceselor, propusa de E.W. Dijkstra. In continuare se vor prezenta cateva probleme generale, de ordin teoretic, cu privire la semafoare. Un semafor reprezinta o variabila de tip intreg care indica numarul de activari salvate pentru viitoarea utilizare a unei resurse. Aceasta variabila poate avea valoarea
-zero, semnificand faptul ca nu s-au salvat activari pentru viitoarea
utilizare a resursei;
-n, pozitiva, semnificand faptul ca sunt in asteptare n activari ale resursei;
Un semafor S este un ansamblu format din variabilele intregi 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 cateva 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.
In continuare se va prezenta problema producator-consumator, in care este vorba despre un set de N buffere. Procesul producator completeaza cate un buffer cu informatii, in 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 PRODUCATOR();
procedura CONSUMATOR();
Pe langa aspectele pozitive ale solutiei se pot semnala si unele limite ale metodei
-in sistemele mari este dificila folosirea semafoarelor;
-exista posibilitatea ca, din cauza unor erori de programare, sa se sara in mod accidental peste executarea procedurii P, ceea ce duce la accesul neprotejat la regiunile critice;
-in mod analog, daca se sare peste executia procedurii V, se poate ajunge la fenomenul de blocare;
-in 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. Inainte 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 incheie 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 cand sa incerce din nou accesarea resursei si faptul ca fisierele de blocare pot ramane nesterse atunci cand sistemul iese in mod accidental din functiune, la urmatoarea initializare a sistemelor ele continuand sa blocheze accesul.
Dijkstra a publicat algoritmul Dekker care descrie o implementare a semafoarelor, care dupa cum s-a precizat sunt variabile de tip intreg 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, in 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, in 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 decat 1. Nucleul executa toate operatiile in mod atomic nici un alt proces neputand modifica valoarea semaforului pana in momentul incheierii tuturor operatiilor. Daca nucleul nu poate executa toate operatiile, atunci nu executa nici una, caz in care procesul trece in asteptare pana in momentul in care nucleul poate efectua toate operatiile.
In 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 diferitelor operatiuni de control asupra setului de semafoare si semop pentru a modifica valorile semafoarelor.
Figura 10.13. Structurile de date implicate in lucrul cu semafoare
Apelul sistem semget creeaza 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 partajata. 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. creeaza un semafor cu doua elemente.
Procesele opereaza asupra valorii semafoarelor cu ajutorul apelului sistem semop, avand urmatoarea sintaxa
oldval = semop(id, oplist, count)
unde id este descriptorul intors de apelul semget, oplist este un pointer catre o lista de operatii asupra semaforului, iar count este dimensiunea listei. Valoarea intoarsa, oldval, reprezinta valoarea ultimului semafor din set asupra caruia s-au facut operatii, inainte 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 incheie cu eroare. Daca, pe timpul executarii operatiilor asupra semafoarelor, nucleul trebuie sa astepte, atunci acesta restaureaza valorile initiale (de la inceputul executiei apelului sistem) ale semafoarelor pe care deja le-a modificat, trecand apoi in asteptare pana la producerea evenimentului pe care il asteapta, dupa care reia de la inceput executia apelului sistem. Astfel, operatiile cu semafoare sunt executate atomic, adica fie toate odata, fie nici una.
Nucleul modifica valoarea unui semafor in conformitate cu valoarea operatiei. Daca aceasta este pozitiva, atunci nucleul incrementeaza valoarea semaforului si trezeste toate procesele care asteptau 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 in asteptare. Daca valoarea operatiei este negativa, iar in valoare absoluta este mai mica sau egala cu valoarea semaforului, atunci nucleul aduna acea valoare (negativa) la valoarea semaforului. Daca rezultatul acestei insumari este zero, nucleul trezeste toate procesele care asteptau ca valoarea semaforului sa devina zero. Daca valoarea semaforului este strict mai mica decat valoarea absoluta a operatiei, atunci nucleul pune procesul in asteptare pe evenimentul: valoarea semaforului creste. Ori de cate ori un proces asteapta in mijlocul executiei unei operatii cu semafoarele, el asteapta la un nivel de prioritate deci se va trezi la primirea unui semnal.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define SEMKEY 75 int semid; unsigned int count; struct sembuf psembuf,vsembuf; /* operatii corespunzatoare lui P si V */ main(argc, argv) int argc; char *argv[ ]; /* trece in asteptare pana la trezirea de catre un semnal */ else if(argv[1][0]=='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 in 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 inregistrarile 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 il executa de trei ori in urmatoarea secventa
a.out &
a.out a &
a.out b &
Atunci cand programul este rulat fara parametri, procesul creeaza un set de semafoare cu doua elemente, ale caror valori sunt initializate cu 1. Apoi, procesul executa apelul pause( ) si trece in starea de asteptare pana cand este trezit de un semnal, si sterge semaforul in cadrul functiei cleanup( ). Atunci cand programul se executa cu parametrul 'a', procesul (A) face in 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 incearca decrementarea valorii unui semafor care are deja valoarea 0, atunci este trecut in starea de asteptare si in 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 in starea de asteptare, iar valorile semafoarelor vor bascula intre valorile 0 si 1. Atunci cand programul se executa cu parametrul 'b', procesul (B) decrementeaza semafoarele 0 si 1 in ordinea inversa celei in care a facut-o procesul A.
Atunci cand procesele A si B se executa simultan, poate aparea situatia in 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. In acest caz, ambele procese trec in starea de asteptare, fiind in imposibilitatea de a-si continua executia. Ele s-au blocat reciproc si isi incheie 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 psembufs2t;
psembufs0t.semInum=0;
psembufs1t.semInum=1;
psembufs0t.semIop=-1;
psembufs1t.semIop=-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 in starea de asteptare pana cand 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 pana in momentul in care le poate decrementa pe amandoua.
Un proces poate activa indicatorul IPCINOWAIT in cadrul apelului sistem semop daca nucleul ajunge in situatia in care procesul trebuie sa treaca in starea de asteptare pana cand 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 treaca in starea de asteptare daca nu poate executa operatia atomic.
Situatii periculoase pot aparea atunci cand un proces executa o operatie asupra unui semafor, probabil pentru blocarea unei resurse, si apoi isi incheie 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 incheierea imediata a executiei unui proces. Daca, in 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. In 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 SEMIUNDO in cadrul apelului sistem semop atunci cand procesul isi incheie 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, cate 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 cand un proces executa primul sau apel semop, avand setat indicatorul SEMIUNDO, nucleul aloca dinamic structuri de tip undo. La urmatoarele apeluri semop care seteaza indicatorul SEMIUNDO, nucleul cauta printre structurile de tip undo pe aceea care contine acelasi identificator 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 SEMIUNDO. Daca nu exista o asemenea structura pentru semafor, atunci nucleul va crea una, gestionand 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 cand un proces isi incheie executia, nucleul apeleaza o rutina speciala care parcurge toate structurile undo asociate procesului modificand semafoarele corespunzatoare.
Revenind la figura 10.14., nucleul creeaza o structura undo ori de cate ori un proces decrementeaza valoarea unui semafor si elimina o structura de tip undo ori de cate 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 in 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 incheia executia in acest moment, atunci nucleul ar parcurge fiecare structura, adaugand valoarea 1 la fiecare semafor, readucandu-le la valoarea la zero. In 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 intreaga 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, in mod asemanator celui in 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, IPCIRMID, 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 cand procesele isi reiau executia, vor constata ca identificatorul de semafor nu mai este valabil si vor intoarce o eroare catre apelant.
4.Concluzii generale
Exista cateva asemanari intre sistemul de fisiere si mecanismele de comunicare intre 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 intradevar, procesele pot avea acces la unul din mecanismele de comunicare, cu conditia sa cunoasca identificatorul corect si sa indeplineasca 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. In acest fel, procesele care nu sunt programate foarte minutios pot lasa la voia intamplarii, dupa incheierea executiei lor, structuri de date care nu mai sunt necesare nimanui si, in consecinta, care nu mai sunt folosite de nimeni. Cu toate ca, dupa incheierea executiei unui proces, nucleul poate salva informatiile de stare si datele in cadrul structurilor de date ale mecanismelor de comunicare, totusi este mai indicata folosirea fisierelor in acest scop.
Mecanismele de comunicare intre procese introduc un termen nou, chei, in 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 gandite pentru a opera intr-un mediu concentrat (de tip "o singura masina"). Numele de fisiere sunt mai potrivite pe un mediu distribuit. Utilizarea cheilor in locul numelor de fisiere, mai implica si faptul ca facilitatile IPC sunt entitati date ca atare, folositoare in cadrul aplicatiilor dedicate, dar carora le lipsesc posibilitatile de construire de instrumente, esentiale in 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 in lucru, ele nu ar trebui incluse in nucleu. Totusi, in cazul aplicatiilor care coopereaza, ele asigura performante superioare fata de facilitatile oferite de sistemul de fisiere.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1462
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved