CATEGORII DOCUMENTE |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
: | |
Memoria partajata
La fiecare dintre metodele de comunicatie intre procese prezentate pina
acum, informatia era transferata intre cele doua procese implicate, adica era
copiata in afara spatiului de adresare al procesului sursa si apoi recopiata in
spatiul de adresare al procesului destinatie, prin intermediul unor apeluri
sistem. Acest lucru necesita unele transformari asupra datelor transferate si
uneori, transferarea unor informatii suplimentare.
Atita timp cit cele doua procese comunica, ele trebuie sa se gaseasca
deja in memorie impreuna cu datele lor. Daca datele de transmis se afla deja
in memorie, cele doua procese pot accesa amindoua zona de memorie in care se
gasesc datele comune. Aceasta memorie devine memoria partajata.
Folosirea memoriei partajate ofera o metoda eleganta si rapida
de comunicatie intre cele doua procese care acceseaza in comun o zona
de memorie interna. Aceasta zona poate fi folosita de oricare dintre
procesele din sistem care ii cunosc cheia. Pentru fiecare proces care
utilizeaza memoria partajata este necesar ca ea sa se gaseasca in spatiul
sau de adresare. Acest lucru asigura de fapt eficienta maxima, in continuare
aceasta zona fiind accesata ca si o variabila obisnuita, fara folosirea
apelurilor sistem. Din pacate memoria partajata nu este disponibila in orice
implementare de Unix System V pentru ca numai anumite configuratii hardware
permit acest lucru. Folosirea unor zone de memorie in comun de catre doua
procese impune desigur si o serie de restrictii de acces si necesitati de
sincronizare care se rezolva de obicei cu ajutorul semafoarelor. Programarea
defectoasa a accesului la memoria partajata a mai multor procese poate duce
usor la blocaje (deadlocks).
Din punctul de vedere al programatorului, principiul de lucru cu
memoria partajata este foarte simplu: un segment de memorie partajata trebuie
creat si deschis, apoi atasat in spatiul de adresare al proceselor care
au nevoie de el.
Apoi, fiecare dintre procese poate scrie sau citi fara restrictii din
zona respectiva. Cind un proces nu mai are nevoie de memoria partajata, el
poate detasa segmentul. Daca nici unul dintre procese nu mai are nevoie de el,
segmentul de memorie partajata, poate fi eliminat complet. Pentru operatiile
de creare, deschidere, atasare, detasare, eliminare a segmentelor de memorie
partajata exista apeluri ale nucleului. Pentru accesarea datelor din segment
nu exista apeluri, accesul facindu-se prin intermediul pointerilor.
Pentru a adresa segmentele de date procesoarele moderne folosesc un
registru de segmentare. Daca este disponibil un astfel de registru, implementarea
memoriei partajate este foarte usoara (acest lucru este rezolvat de cei care
implementeaza UNIX-ul si nu de catre cei care folosesc memoria partajata in
aplicatii!). Accesul ulterior la acest segment se face aproape la fel de repede
ca la orice variabila locala a procesului, de aceea memoria partajata este de
departe cea mai rapida metoda de comunicatie intre procese comparativ cu metodele
prezentate pina acum.
Desigur, hardware-ul poate sa impuna si limitari. De exemplu numarul de
segmente de memorie partajata care pot fi folosite deodata de catre un proces
este dependent de arhitectura sistemului. Daca se doreste ca aplicatiile sa
ramina portabile, nu trebuie folosit decit un singur element de memorie
partajata. La fel hardware-ul determina si marimea maxima a unui segment. De
obicei un minim de 64K este disponibil.
Pentru crearea si deschiderea unui segment de memorie partajata exista
apelul sistem shmget. Se apeleaza in felul urmator:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
key_t cheie;
int marime, permisii, shmid;
shmid=shmget(cheie, marime, permisii);
La fel ca si la mesaje, cel mai important parametru este primul, cheie,
cel care da cheia de recunoastere globala a segmentului. Orice alt proces care
vrea sa foloseasca segmentul de memorie partajata, creat cu shmget, trebuie sa
cunoasca aceasta cheie. Cheia este, ca si mai inainte, de tipul key_t, dependent
de implementare, dar de obicei un long. Al doilea parametru, marime, este cel
care specifica dimensiunea in octeti a segmentului de memorie partajata care
se doreste a fi cret. Ultimul parametru, permisii, specifica drepturile de
acces la segment. Apelul returneaza in shmid un identificator al segmentului
nou creat, care este local procesului. Daca apelul esueaza in crearea
segmentului de memorie partajata, este intoarsa valoarea -1, iar eroarea
va fi documentata in variabila globala errno. Valorile de eroare din errno
sint dependente de implementare. Trebuie consultat manualele on-line pentru
functia shmget(S).
Daca marimea specificata la apel pentru segment este prea mare sau
prea mica, errno va fi setat pe valoarea EINVAL. Ce inseamna prea mic
sau prea mare pentru implementarea data, trebui de asemenea aflat.
Pentru specificarea permisiilor, se pot folosi valori asemanatoare
cu cele pentru cozi de mesaje. Trebuie deci specificate drepturile de acces
la segment si citiva comutatori de creare. De exemplu poate primi valoarea
066 | IPC_CREAT | IPC_EXCL, deci drepturi de citire/scriere doar pentru
proprietar, segmentul de memorie partajata va fi deschis numai daca nu exista
deja, caz in care va fi mai intii creat. Daca segmentul exista deja se va
intoarce valoarea de eroare -1.
Prin acest apel, a fost creat in memorie segmentul dorit. Accesul
la el se va face in continuare prin intermediul identificatorului sau, shmid.
Acesta este local procesului curent, deci nu va avea aceasi valoare si pentru
alte procese care deschid aceeasi zona de memorie partajata. La terminarea
acestui apel segmentul nu este inca gata de a fi folosit pentru procesul
curent. Aceasta din cauza ca segmentul nu se gaseste inca in spatiul de
adresare al procesului. Pentru a atasa segmentul de memorie la proces
exista apelul shmat (attach shared memory). El poate fi apelat dupa cum urmeaza:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int comutatori,shmid;
char *adresa,*adresa_impusa;
adresa=shmat(shmid, adresa_impusa, comutatori);
Prin acest apel se creaza pentru procesul curent o adresa virtuala cu
care se va putea adresa in continuare zona de memorie partajata. Variabila
adresa va contine dupa apel aceasta adresa. Prin apel se poate forta si legarea
segmentului la o anumita adresa dorita, specificata prin parametrul
adresa_impusa. Daca acest parametru are valoarea 0 si acesta este cazul comun,
atunci sistemul va decide singur care este adresa unde va fi legat segmentul
si aceasta va fi urmatoarea adresa disponibila.
Se poate de asemenea impune ca segmentul sa fie atasat la o adresa care
sa fie inceput de pagina de memorie. Pentru aceasta trebuie specificata valoarea
SHM_RND in parametrul comutatori. Daca se impune o adresa de atasare, aceasta
va fi rotunjita la urmatorul inceput de pagina. Daca nu, va fi intors urmatorul
inceput de pagina. Daca nu, va fi intors urmatorul inceput de pagina disponibil.
Alt comutator disponibil este SHM_RDONLY, care specifica faptul ca segmentul
trebuie protejat la scriere. Din pacate numai acele configuratii hardware pot
asigura o astfel de protectie. Daca parametrul comutatori are valoarea 0, atunci
accesul este si in citire si in scriere iar adresa nu va fi rotunjita in nici
un fel.
Din acest moment acesul la segmentul de memorie este posibil prin adresa
virtuala adresa. Se poate scrie si citi din memorie variabile intregi prin
instuctiuni de genul:
int i, *pint;
pint=(int *)adresa;
*pint++=i;
i=*pint;
Sau se poate adresa segmentul caracter cu caracter in felul urmator:
int i;
for(i=0;adresa[i];i++)
Dupa terminarea lucrului cu memoria partajata aceasta trebuie desigur
eliminata din spatiul de adresare al procesului. Acest lucru se face cu ajutorul
apelului shmdt (detach shared memory). Acesta se apeleaza astfel:
retur=shmdt(adresa);
Dupa acest apel nu se mai poate adresa in nici un fel segmentul de
memorie partajata, decit dupa o noua atasare. In clipa in care toate procesele
care aveau nevoie de segmentul de memorie partajata s-au detasat de la acesta,
segmentul poate fi distrus complet printr-un apel shmctl. Apelarea se face
astfel:
retur=shmctl(shmid, IPC_RMID, 0);
Trebuie acordata foarte mare atentie la distrugerea unui segment de
memorie partajata. Daca mai exista procese care au atasat acel segment pot
apare erori foarte grave, care sa duca chiar la caderea sistemului !
Urmatorul exemplu de program se poate urmari fara explicatii suplimentare.
In program memoria partajata este atasata si detasata la fiecare acces. Acest
lucru nu este in general necesar si trebuie evitat pe cit posibil datorita
pierderii de viteza. S-a folosit aceasta metoda pentru ca in unele implementari
mai vechi de Unix (de exemplu Xenix) o zona de memorie partajata nu poate fi
atasata deodata la mai multe procese. Aceasta limitare nu mai este valabila
in implementarile moderne.
In diverse implementari de Unix, au aparut multe optiuni suplimentare
in lucrul cu memoria partajata. Filozofia de baza a Unix-ului este valabila si
in acest caz 'Keep it small, keep it simple'.
shmem.h */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHMKEY (key_t)100
scrie.c */
#include 'shmen.h'
int shmid;
main()
cleanup()
citeste.c */
#include 'shmen.h'
int shmid;
main()
Semafoare
Acest lab trateaza implementarea semafoarelor in Unix System V. Desigur
este nevoie de un mecanism de sincronizare atunci cind mai multe procese
concureaza pentru resursele sistemului (printre aceste resurse sunt si
regiunile critice ale programelor). Bazele lucrului cu semafoare au fost puse
de olandezul Dijkstra. Mai intii sa vedem care sunt premisele teoretice ale
problemei. Apoi vom vedea cum sunt implementate ele in Unix System V.
O situatie tipica este urmatoarea: doua procese vor sa acceseze
acelasi segment de memorie. Ele nu pot accesa memoria direct, ci trebuie sa
folosesca un semafor (sau variabila de sincronizare), pentru a vedea daca in
momentul respectiv este permis accesul la segmentul de memorie respectiv.
Un semafor poate fi privit ca o variabila care ia numai valori intregi
si pozitive. Pentru a se putea realiza sincronizarea, este nevoie sa fie
implementate doua operatii de baza, blocarea (p(sem)) si eliberarea (v(sem)).
Aceste doua operatii trebuie sa fie atomice, cu alte cuvinte nimeni nu are
voie sa aiba o prioritate atit de mare incit sa le intrerupa. Din acest motiv
cele doua operatii trebuie sa fie implementate ca apeluri ale nucleului.
Operatia p arata in felul urmator:
p(sem) =
daca(sem este diferit de 0)
decrementeaza sem
altfel
asteapta pina cind sem devine diferit de 0;
Iar schema pentru operatia v este urmatoarea:
v(sem) =
daca(exista procese care asteapta ca sem sa devina diferit de 0)
activeaza primul proces care asteapta
altfel
incrementeaza sem;
Sub Unix System V nu se lucreaza cu un singur semafor, ci apelurile
sistem trateaza grupuri de semafoare. Aceste grupuri (tablouri) de semafoare
sunt accesate de procese printr-o cheie globala de identificare. Aceasta
generalizare a problemei duce la o usoara complicare a utilizarii semafoarelor.
Dar baza ultima ramine aceeasi si anume ca toate operatiile pe grupuri de
semafoare trebuie sa fie atomice. Aceasta presupunere usureaza mult munca
celor care dezvolta de exemplu baze de date.
Iata care sunt variabilele si structurile implicate in descrierea
apelurilor pentru utilizarea semafoarelor in Unix System V:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
key_t cheie;
int semid, nsem, permisii,comanda;
int retur,semnr;
union semunctl_arg;
Pentru crearea unui grup de semafoare se foloseste apelul semget:
semid=semget(cheie, nsem, permisii);
Prin acest apel se creeaza un grup de semafoare care va fi recunoscut
de catre orice proces prin cheia de identificare cheie. Spre deosebire de
cheie, care este globala, identificatorul semid intors de catre apel este
local pentru proces. Va fi folosit pentru referinta la semafor de catre
apelurile urmatoare. Parametrul nsem indica numarul de semafoare care vor
forma noul grup. Ca si la memoria partajata sau la mesaje, parametrul permisii
va contine drepturile de acces la semafor si atributele necesare apelului.
Fiecarui semafor ii apartin citeva valori si anume:
-semval: valoarea semaforului. Aceasta este intotdeauna un intreg pozitiv.
Aceasta valoare se poate seta numai prin intermediul apelurilor sistem, accesul
direct nefiind posibil
-sempid: identificatorul ultimului proces care a efectuat o operatie asupra
semaforului
-semncnt: numarul de procese care asteapta ca semaforul sa primeasca o valoare
mai mare decit cea curenta.
-semzcnt: numarul de procese care asteapta ca semaforul sa aiba valoarea zero.
Setarea acestor parametri se face dupa crearea semaforului. Pentru aceasa
se foloseste apelul semctl:
retur=semctl(semid, semnr, comanda, ctl_arg);
Apelul semctl este semnificativ mai complicat decit apelul msgctl.
Parametrul semid este identificatorul local al grupului de mesaje. Parametrul
semnr specifica al citelea semafor din grup va fi afectat de apel. Prin
parametrul comanda alegem actiunea dorita. Intr-o prima grupa de actiuni sunt
actiuni obisnuite IPC, cum ar fi:
IPC_STAT:informatiile de stare vor fi in ctl_arg.stat,
IPC_SET:drepturile de acces vor fi tot in ctl_arg.stat,
IPC_RMID:distrugerea grupului de semafoare.
A doua grupa de actiuni le contine pe cele care se refera la un singur
semafor din grup (specificat prin semnr):
GETVAL:intoarce valoarea semaforului,
SETVAL:seteaza valoarea semaforului data prin ctl_arg.val,
GETPID:intoarce valoarea din sempid,
GETNCNT:intoarce valoarea din semncnt,
GETZCNT:intoarce valoarea din semzcnt.
Tot in acest al doilea grup se includ si citeva actiuni asupra tuturor
semafoarelor din grup:
GETALL:pune toate valorile semafoarelor in tabloul ctl_arg.array,
SETALL:seteaza toate valorile semafoarelor cu valorile din tabloul ctl_arg.array.
Ultimul parametru al apelului semctl este de fapt un union cu trei
tipuri diferite. Fiecare actiune trateaza aceasta valoare dupa cum are nevoie.
Dupa ce semaforul a fost initializat, pot incepe de fapt operatiile cu el.
Pentru aceste operatii exista apelul semop, care este atomic. Acest lucru
inseamna ca semop asteapta pina in momentul in care toate operatiile
oplist din apel se pot executa deodata. Astfel, ori sunt terminate toate
operatiile, ori niciuna. Realizarea doar a unei parti a operatiilor nu se
intimpla niciodata.Iata apelul lui semop:
veche=semop(semid,oplist,n);
Prin aceasta toate operatiile din lista oplist se vor executa asupra
grupului de semafoare semid. veche va contine valoarea ultimului semafor
modificat. In oplist va fi prezenta o lista de n operatii de executat. Lista
de operatii este de fapt un tablou. Elementele acestui tablou sint de tipul
sembuf. Aceasta structura contine trei valori de tipul short. Acestea sunt
sem_num, sem_op si sem_flag. sem_num specifica numarul semaforului afectat in
cadrul grupului specificat de semid. sem_op este putin mai complicat. Vom
deosebi trei cazuri in functie de valoarea acestui element:
-numar pozitiv intreg(+n): aceasta valoare va fi adunata la valoarea curenta
a semaforului. Prin aceasta vor fi activate toate procesele care asteapta aceasta
valoare.
-zero: la aceasta valoare se va astepta pina cind semaforul dorit din grup va
deveni zero in cazul in care nu s-a specificat comutatorul IPC_NOWAIT in sem_flag
caz in care se semnaleaza eroare.
-numar negativ intreg (-n): in acest caz se impune o noua impartire:daca
valoarea curenta a semaforului este mai mare decit (n), valoarea va fi redusa
cu n. Prin aceasta se asigura ca valoarea nu va deveni mai mica decit zero.
Daca se nimereste ca noua valoare sa fie chiar zero atunci toate procesele
care asteapta vor fi activate. In cazul in care valoarea absoluta a lui n este
mai mare decit valoarea semaforului, reducerea valorii semaforului nu va avea
succes. In acest caz ar aparea o valoare negativa care nu este permisa. Este
posibila doar urmatoarea strategie: asteptarea pina valoarea curenta a
semaforului devine mai mare. Comutatorii din sem_flag specifica diverse
moduri de a trata actiunea. Daca este data valoarea IPC_NOWAIT, din nou
asteptarea va fi intrerupta si veche va fi valoarea -1. Aceasta in Unix
inseamna caz de eroare, iar variabila globala errno va fi setata pe valoarea
EAGAIN.
Daca se foloseste comutatorul SEM_UNDO atunci la terminarea prin exit
a unui proces, operatii pe semafor cu semop vor fi facute in sens invers. Prin
aceasta se poate evita un eventual punct mort (deadlock). Este ca si cum un
proces blocheaza un semafor, apoi primeste un semnal neasteptat si se termina
fara sa deblocheze semaforul.
In exemplu sunt implementate operatiile p si v. Pentru aceasta s-a
scris mai intii o functie de initializare a semaforului, initsemkey care
creaza un semafor si ii da valoarea initiala 1.
Functiile p si v sunt implementate cu ajutorul apelului semop. Se
lanseaza trei procese distincte, cu apelul fork, care se sincronizeaza cu
ajutorul semaforului si a operatiilor p si v.
pv.h */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SEMPERM 0600
oplist.c */
#include 'pv.h'
initsem(Key_t semkey)
p(int semid)
v(int semid)
handlesem(int semid)
main()
reader-writer.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#define SEMKEY1 (key_t)0x10
#define SEMKEY2 (key_t)0x15
#define SEMKEY (key_t)0x20
#define SIZ 5*BUFSIZ
struct data_buf;
main()
/* in procesul parinte *
writer(semid, buf1, buf2);
struct sembuf p1=;
struct sembuf p2=;
struct sembuf v1=;
struct sembuf v2=;
writer(int semid, struct databuf *buf1, struct databuf *buf2)
reader(int semid, struct databuf *buf1, struct databuf *buf2)
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1345
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved