Scrigroup - Documente si articole

Username / Parola inexistente      

Home Documente Upload Resurse Alte limbi doc  
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml


Memoria partajata

c



+ Font mai mare | - Font mai mic



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



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1251
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved