Laboratoare:IPC

= Windows =

Sistemul de operare Windows pune la dispozitie o serie de mecanisme de comunicare si schimb de date intre aplicatii. Cazul de care ne vom ocupa este doar cel in care aceste aplicatii sunt procese care ruleaza pe aceeasi masina.

Inainte de a fi prezentate mecanismele de comunicare in sine trebuie introduse mecanismele de sincronizare, care sunt folosite pentru controlul accesului la resurse.

Mecanismele de sincronizare oferite de sistemul de operare Windows sunt mai multe si mai complexe decat cele din Linux. Pentru sincronizare sunt necesare unul sau mai multe obiecte de sincronizare (synchronization objects) folosite impreuna cu o functie de asteptare (wait function).

ATENTIE! Obiectele de sincronizare nu pot fi folosite fara functii de sincronizare.

Odata parcurse mecanismele de sincronizare, vor fi prezentate doar doua din mecanisme de comunicare puse la dispozitie de Windows :
 * File Mappings (memorie/fisiere partajate)
 * Mail Slots (cozi de mesaje).

Mutex-uri
Un mutex este un obiect de sincronizare care poate fi detinut (posedat, acaparat) doar de un singur proces (sau thread) la un moment dat. Drept urmare, operatiile de baza cu mutex-uri sunt cele de obtinere si de eliberare.

Odata obtinut de un proces, un mutex devine indisponibil pentru orice alt proces. Orice proces care incearca sa acapareze un mutex indisponibil, se va bloca (un timp definit sau nu) asteptand ca el sa devina disponibil.

Mutex-urile sunt cel mai des folosite pentru a permite unui singur proces la un moment dat sa acceseze o resursa.

In continuare vor fi prezentate operatiile cu mutex-uri.

Crearea/deschiderea sunt operatii prin care se pregateste un mutex. Dupa cum am spus mai sus, pentru a opera cu orice obiect de sincronizare este necesar un HANDLE al acelui obiect. Scopul functiei de creare si a celei de deschidere este acela de a obtine un HANDLE al obiectului mutex. Prin urmare, este necesar doar un singur apel, fie el de creare sau de deschidere (se presupune ca alt proces a creat deja mutex-ul). Acest apel este efectuat o singura data la initializare; odata ce avem HANDLE-ul putem obtine/elibera mutex-ul de cate ori avem nevoie.

Pentru a crea un mutex se foloseste functia CreateMutex cu sintaxa :

Pentru a deschide un mutex deja existent este definita functia OpenMutex cu sintaxa :

Obtinerea unui mutex se realizeaza folosind functiile de asteptare care sunt tratate intr-o sectiune urmatoare.

Incercarea de acaparare a unui mutex presupune urmatorii pasi :
 * 1) este mutex-ul disponibil?
 * 2) daca da, il pot acapara si devine indisponibil, si functia intoarce succes
 * 3) daca nu, astept sa devina disponibil, dupa care il acaparez, si functia intoarce succes
 * 4) time-out, si functia intoarce eroare (atentie! e posibil sa nu existe time-out)

Incercarea de obtinere se poate face cu sau fara timp de expirare (time-out) in functie de parametrii dati functiilor de asteptare. Cea mai des folosita functie de asteptare este WaitForSingleObject.

Folosind functia ReleaseMutex se cedeaza posesia mutex-ului, el devenind iar disponibil. Functia are urmatoarea sintaxa : Functia va esua daca procesul nu detine mutex-ul.

ATENTIE! pentru a putea folosi aceasta functie HANDLE-ul trebuie sa aiba dreptul de acces MUTEX_MODIFY_STATE.

Operatia de distrugere a unui mutex este aceeasi ca pentru orice HANDLE. Se foloseste functia CloseHandle. Dupa ce toate HANDLE-urile unui mutex au fost inchise, mutexul este distrus si resursele ocupate de acesta eliberate.

ATENTIE! La terminarea executiei unui program toate HANDLE</tt>-urile folosite de acesta sunt automat inchise. Deci spre deosebire de semafoarele IPC din Linux, este imposibil ca un mutex (sau semafor) in Windows sa mai existe in sistem dupa ce programele care l-au folosit/creat s-au terminat.

Asteptare dupa un singur obiect
Aceste functii asteapta dupa un singur obiect de sincronizare. Executia lor se termina cand una din urmatoarele conditii este adevarata :
 * Obiectul de sincronizare este in starea 'signaled'
 * Timpul de asteptare (time-out) a expirat. Acest timp poate fi setat ca INFINITE</tt> - timpul de asteptare nu expira niciodata.

Rezultatul intors de aceste functii poate fi :
 * WAIT_OBJECT_0</tt> - Succes
 * WAIT_ABANDONED</tt> - Obiectul specificat este un mutex care a fost abandonat, adica thread-ul care-l detinea s-a terminat fara sa-l elibereze. In acest caz threadul curent va deveni detinatorul mutexului iar starea mutexului va fi nonsignaled (mutex ocupat).
 * WAIT_IO_COMPLETION</tt> - Asteptarea a fost intrerupta de un apel asincron de procedura.
 * WAIT_TIMEOUT</tt> - Timpul de expirare s-a scurs.
 * WAIT_FAILED</tt> - Functia a esuat. Informatii despre eroare pot fi obtinute folosind functia GetLastError</tt>.

In continuare sunt prezentate pe larg functiile care fac parte din aceasta categorie :

SignalObjectAndWait semnalizeaza un obiect si asteapta dupa altul. Functia are sintaxa :

WaitForSingleObject asteapta dupa un singur obiect si are sintaxa :

WaitForSingleObjectEx permite o asteptare alertabila dupa un singur obiect si are sintaxa :

Asteptare dupa mai multe obiecte
Aceste functii asteapta dupa mai multe obiecte de sincronizare. Executia lor se termina cand una din urmatoarele conditii este adevarata:
 * Starea unui obiect de sincronizare SAU starea tuturor obiectelor de sincronizare este 'signaled' (depinde de parametri)
 * Timpul de asteptare (time-out) a expirat. Acest timp poate fi setat ca INFINITE</tt> pentru a specifica ca timpul de asteptare nu va expira niciodata

WaitForMultipleObjects asteapta dupa mai multe obiecte si are sintaxa :

WaitForMultipleObjectsEx permite o asteptare alertabila dupa mai multe obiecte si are sintaxa :

Asteptare alertabila si asteptare inregistrata
Functiile de asteptare alertabila sunt :
 * WaitForSingleObjectEx</tt>
 * WaitForMultipleObjectsEx</tt>
 * SignalObjectAndWait</tt>

Aceste functii ofera posibilitatea de a efectua operatii de asteptare alertabile. O operatie de asteptare alertabila se poate termina cand :
 * conditiile specificate sunt adevarate
 * sistemul programeaza o rutina de tratare a operatiilor de I/O terminate
 * sistemul programeaza o rutina de tratare a unui apel asincron terminat

Controlul alertabilitatii se realizeaza prin parametrul BOOL bAlertable</tt> pe care aceste functii il accepta.

Functiile de asteptare inregistrata sunt folosite de programele cu thread-uri si vor fi explicate in laboratoarele care trateaza thread-urile.

Semafoare
Un semafor (semaphore) este un obiect de sincronizare care are intern un contor ce ia doar valori pozitive. Atat timp cat semaforul (contorul) are valori strict pozitive el este considerat disponibil (signaled). Cand valoarea semaforului a ajuns la zero el devine indisponibil (nonsignaled) si urmatoarea incercare de decrementare va duce la o blocare a threadului de pe care s-a facut apelul (si a procesului, daca acesta foloseste un singur thread) pana cand semaforul devine disponibil.

Operatia de decrementare se realizeaza doar cu o singura unitate (la fel ca in API-ul POSIX, dar spre deosebire de API-ul SysV unde se poate face decrementarea atomica a unui semafor cu mai multe unitati o data), in timp ce incrementarea se poate realiza cu orice valoare in limita maxima.

Crearea si deschiderea
Functia de creare a semafoarelor este CreateSemaphore si are sintaxa : Se observa ca functia se poate folosi si pentru deschiderea unui semafor deja existent.

Alternativ, pentru a folosi un semafor deja existent este necesara obtinerea HANDLE</tt>-ului semaforului, operatie ce se realizeaza folosind functia OpenSemaphore</tt> cu urmatoarea sintaxa :

Decrementarea/asteptarea si incrementarea
Operatia de decrementare a semaforului cu sau fara asteptare se realizeaza folosind una din functiile de asteptare. Cea mai des folosita este functia WaitForSingleObject</tt>.

Incrementarea semaforului se realizeaza folosind functia ReleaseSemaphore cu sintaxa :

Distrugerea
Operatia de distrugere a unui semafor este similara cu cea de distrugere a unui mutex. Se foloseste functia <tt>CloseHandle</tt>. Dupa ce toate <tt>HANDLE</tt>-urile unui semafor au fost inchise, semaforul este distrus si resursele ocupate de acesta eliberate.

Cozi de mesaje (Mailslots)
Cozile de mesaje sunt folosite de procese pentru a comunica între ele prin mesaje. Aceste mesaje își păstrează ordinea în interiorul cozii de mesaje.

Mailslots sunt un fel de pseudo-fisiere care rezidă în memorie, și deci pot fi folosite prin intermediul funcțiilor standard de acces la fisiere. Datele dintr-un astfel de mailslot pot avea orice formă, atât timp cât nu depășesc limita de 64 Kbytes. Fiind păstrate în memorie, toate aceste date au un caracter volatil, spre deosebire de fișiere, iar când toate handle-urile la un mailslot sunt distruse, acesta la rândul său, este distrus împreună cu datele, iar memoria este eliberată.

Sistemul de cozi de mesaje bazate pe mailslots este de tipul client-server. Astfel : Este procesul care creează și deține coada de mesaje, obținând astfel un handle. Numai cu ajutorul acestui handle se poate citi din (dar nu se poate scrie în) coada de mesaje. Există și posibilitatea ca handle-ul să fie moștenit. Este un proces care pune mesaje în coadă. Pot exista mai mulți clienți simultan.
 * serverul mailslot
 * clientul mailslot

Limitări :
 * Mesajele de tip broadcast sunt limitate la maximum 424 bytes, iar încercarea de a trimite un mesaj broadcast mai mare va eșua, iar funcția va întoarce eroare.
 * NU pot fi trimise mesaje de lungime 425 bytes sau 426 bytes.
 * Lungimea maximă a unui mesaj este 64 Kbytes.

Denumirea Mailslot-urilor
Când un proces creează un mailslot, trebuie să-i atribuie o denumire, care are următoarea formă :

\\.\mailslot\[path]

Atenție! Prefixul "\\.\mailslot\" trebuie sa existe exact în această formă, el fiind urmat de un nume, care eventual va fi precedat de o cale. Calea este asemănătoare cu cea a fișierelor. Un exemplu valid : "\\.\mailslot\test\commands".

Un proces client, pentru a scrie într-un mailslot, va folosi aceeași denumire.

Cozile de mesaje pot fi folosite și pentru a comunica cu procese care rulează pe alte calculatoare. În acest sens, clientul va folosi denumiri care au structura :

\\<ComputerName>\mailslot\[path]<Nume>

Pentru a trimite mesaje unui întreg domeniu, denumirea va avea structura :

\\<DomainName>\mailslot\[path]<Nume>

Pentru a trimite mesaje tuturor, denumirea va avea structura :

\\*\mailslot\[path]<Nume>

Crearea
Pentru a crea un mailslot, se folosește funcția CreateMailslot care are următoarea sintaxă și întoarce un handle :

În cazul în care se încearcă crearea unui mailslot cu o denumire care deja există, se va întoarce INVALID_HANDLE_VALUE.

ATENȚIE! Handle-ul întors de această funcție poate fi folosit pentru a efectua doar operații de citire (nu și de scriere) cu mailslot-ul.

Deschiderea unei cozi existente
Pentru a deschide un mailslot pentru scriere, se folosește funcția <tt>CreateFile</tt> care va primi în loc de numele fișierului denumirea cozii de mesaje care se dorește a fi deschisă si flagul <tt>FILE_SHARE_READ</tt>. Pentru a permite accesul concomitent al mai multor clienţi, trebuie adăugat şi flagul <tt>FILE_SHARE_WRITE</tt>.

Scrierea și citirea
Citirea și respectiv scrierea din/în cozile de mesaje sunt asemănătoare cu operațiile cu fișiere, folosindu-se aceleași funcții :
 * <tt>ReadFile</tt> și <tt>ReadFileEx</tt> - pentru citire
 * <tt>WriteFile</tt> și <tt>WriteFileEx</tt> - pentru scriere

Obținerea de informații despre o coada de mesaje
Pentru a obține informații despre o coadă de mesaje, se folosește funcția următoare :

Schimbarea timpului de expirare
Singura caracteristică a unei cozi de mesaje, care poate fi schimbată după ce coada a fost creată, este timpul de expirare. (Dimensiunea maximă a mesajelor acceptate de o coadă nu mai poate fi schimbată după ce aceasta a fost creată)

Funcția care setează această caracteristică este următoarea :

Exemplu de utilizare
'''Exemplu 04-a. Crearea unei cozi de mesaje'''

'''Exemplu 04-b. Deschiderea cozii de mesaje si trimiterea unui mesaj'''

'''Exemplu 04-c. Citirea mesajelor din coadă'''

Memorie partajata (FileMapping)
File Mapping în cazul general permite accesul mai multor procese la un fisier ca și cand fisierul ar fi o zonă de memorie. Astfel se pot folosi toate operațiile aplicabile asupra memoriei, inclusiv pointeri.

O facilitate specială a File Mapping este aceea de "named shared memory", sau memorie partajata identificată de un nume. Această facilitate specială este subiectul laboratorului de față.

ATENȚIE! Accesul la o zonă de memorie partajată trebuie reglementat folosind unul din mecanismele de sincronizare descrise în laboratorul precedent!

Crearea unei zone de memorie partajată
Pentru crearea unei zone de memorie partajată se folosesc două funcții care trebuie apelate în această ordine :


 * 1) <tt>CreateFileMapping</tt> - este o funcție pregătitoare care creează un obiect de tipul File Mapping, reprezentat de un <tt>HANDLE</tt>.
 * 2) <tt>MapViewOfFile</tt> - pentru a mapa efectiv zona de memorie. Funcția întoarce un pointer la zona de memorie partajată.

CreateFileMapping creează o resursă (un obiect) de tipul FileMapping și are următoarea sintaxa :

Dacă există un obiect cu același nume dar de alt tip, funcția va eșua și va întoarce NULL.

MapViewOfFile întoarce un pointer la zona de memorie partajată și are sintaxa :

Accesul la o zonă de memorie partajată deja creată
Pentru a accesa o zonă de memorie partajată, creată de alt proces, se utilizează următoarele funcții (în ordinea specificată) :


 * 1) <tt>OpenFileMapping</tt> - o funcție pregătitoare care accesează (deschide) un obiect de tipul File Mapping.
 * 2) <tt>MapViewOfFile</tt> - pentru a mapa efectiv zona de memorie.

OpenFileMapping accesează o resursă/obiect deja existent de tipul FileMapping și are sintaxa :

Demaparea unei zone de memorie partajată
Pentru a demapa o zonă de memorie partajată, care a fost anterior mapată folosind funcția <tt>MapViewOfFile</tt>, se folosește funcția <tt>UnmapViewOfFile</tt> care are următoarea sintaxă :

Exemplu de utilizare
'''Exemplu 03-a. Secvența de program care creează o zonă de memorie partajată'''

'''Exemplu 03-b. Secvența de program care accesează zona de memorie partajată'''

= Linux = Linux pune la dispozitie 2 seturi de API-uri mecanisme de comunicare inter-proces, ce tin de standarde diferite: Ambele standarde specifica 3 mecanisme:
 * System V Inter-Process Communication, derivat din distributia de Unix System V release 4 AT&T
 * POSIX (Portable Operating System Interface for Unix)
 * mesaje (messages) - realizeaza schimbul de mesaje cu orice proces sau server
 * semafoare (semaphores) - realizeaza sincronizarea executiilor unor procese
 * memorie partajata (shared memory) - realizeaza partajarea memoriei intre procese

API-ul studiat in acesta laborator este cel POSIX.

Obiectele de tip IPC pe care se concentreaza laboratorul de fata sunt gestionate global de sistem si raman in viata chiar daca procesul creator moare. Faptul ca aceste resurse sunt globale in sistem are implicatii contradictorii. Pe de o parte, daca un proces se termina, datele plasate in obiecte IPC pot fi accesate ulterior de alte procese; pe de alta parte, procesul proprietar trebuie sa se ocupe si de dealocarea resurselor, altfel ele raman in sistem pana la stergerea lor manuala sau pana la un reboot. Faptul ca obiectele IPC sunt globale in sistem poate duce la aparitia unor probleme: cum numarul de mesaje care se afla in cozile de mesaje din sistem e limitat global, un proces care trimite multe asemenea mesaje poate bloca toate celelalte procese.

ATENTIE!!! Pentru folosirea API-ului trebuie sa includeti la linking biblioteca 'rt' (-lrt).

Semafoare POSIX
Semafoarele sunt resurse IPC folosite pentru sincronizarea între procese (e.g. pentru controlul accesului la resurse). Operatiile asupra unui semafor pot fi de setare sau verificare a valorii (care poate fi mai mare sau egala cu 0) sau de test and set. Un semafor poate fi privit ca un contor ce poate fi incrementat si decrementat, dar a cărui valoare nu poate scadea sub 0.

Semafoarele POSIX sunt de 2 tipuri: În continuare vor fi luate în discuție semafoarele cu nume. Diferențele față de cele bazată pe memorie constă în funcțiile de creare și distrugere, celelalte funcții fiind identice.
 * cu nume, folosite în general pentru sincronizare intre procese distincte;
 * bazate pe memorie (fără nume), ce pot fi folosite doar pentru sincronizarea intre firele de executie ale unui proces.
 * ambele tipuri de semafoare sunt reprezentate in cod prin tipul sem_t.
 * semafoarele cu nume sunt indenficate la nivel de sistem printr-un sir de forma "/nume".
 * header-ele necesare sunt <fcntl.h>, <sys/types.h> si <semaphore.h>.

Crearea și deschiderea
Un proces poate crea sau deschide un semafor existent cu functia sem_open: Comportamentul este similar cu cel de la deschiderea fișierelor. Dacă flag-ul O_CREAT este prezent, trebuie folosită cea de-a doua formă a funcției, specificând permisiunile și valoarea inițială.

Decrementare, incrementare și aflarea valorii
Un semafor este decrementat cu funcția sem_wait: Dacă semaforul are valoarea zero, funcția blochează până când un alt proces "deblochează" (incrementează) semaforul.

Pentru a încerca decrementarea unui semafor fără riscul de a rămâne blocat la acesta, un proces poate apela sem_trywait: În cazul în care semaforul are deja valoarea zero, funcția va întoarce -1 iar errno va fi setat la EAGAIN.

Un semafor este incrementat cu funcția sem_post: În cazul în care semaforul are valoarea zero, un proces blocat în sem_wait pe acesta va fi deblocat.

Valoarea unui semafor (a contorului) se poate afla cu sem_getvalue: În cazul în care exista procese blocate la semafor, implementare apelului pe Linux va returna zero în valoarea referință de <tt>pvalue</tt>.

Toate aceste funcții întorc zero în caz de succes.

Închiderea și distrugerea
Un proces închide (notifica faptul ca nu mai foloseste) un semafor printr-un apel sem_close: Un proces poate șterge un semafor printr-un apel sem_unlink: Distrugerea efectiva a semaforului are loc după ce toate procesele care îl au deschis apelează sem_close sau se termină. Totuși, chiar și în acest caz, apelul sem_unlink nu va bloca!

Cozi de mesaje
Acestea permit proceselor schimbarea de date intre procese sub forma de mesaje.
 * la nivel de sistem sunt indentificabile printr-un string de forma "/nume".
 * la nivel codului, o coada de mesage este reprezentata de un descriptor de tipul mqd_t.
 * header-ele necesare pentru lucrul cu aceste obiecte sunt <fcntl.h>, <sys/types.h> si <mqueue.h>.

Crearea si deschiderea
Functiile de creare si deschidere sunt similare ca forma si semantica celor de la semafoare:

In functie de flag-uri (unul din cele de mai jos trebuie specificat), coada poate fi deschisa pentru:
 * receptionare (O_RDONLY)
 * trimitere (O_WRONLY)
 * receptionare si trimitere (O_RDWR)

Daca attr e NULL, coada va fi creata cu atribute implicite. Structura mq_attr arata astfel:

Trimiterea si receptionarea de mesaje
Pentru a trimite un mesaj (de lungime cunoscuta, stocat intr-un buffer) in coada se apeleaza mq_send:

Mesajele sunt tinute in coada in ordine descrescatoare a prioritatii.

In cazul in care coada este plina, apelul blocheaza. Daca este o coada non-blocanta (O_NONBLOCK), functia va intoarce -1 iar errno va fi setat la EAGAIN.

Pentru a primi un mesaj dintr-o coada (si anume: cel mai vechi mesaj cu cea mai mare prioritate) se foloseste mq_receive: Daca priority este non-NULL, zona de memorie catre care face referire va retine prioritatea mesajului extras.

In cazul in care coada este vida, apelul blocheaza. Daca este o coada non-blocanta (O_NONBLOCK), comportamentul este similar cu cel al mq_send.

ATENTIE!!! La primirea unui mesaj, lungimea buffer-ului trebuie sa fie cel putin egala cu dimensiunea maxima a mesajelor pentru coada respectiva, iar la trimitere cel mult egala. Dimensiunea maxima implicita se poate afla pe Linux din /proc/sys/kernel/msgmax.

Inchiderea si stergerea
Inchiderea (eliberarea "referintei") unei cozi este posibila prin apelul mq_close: Stergerea se realizeaza cu un apel mq_unlink: Semantica este similara cu cea de la semafoare: coada nu va fi stearsa efectiv decat dupa ce restul proceselor implicate o inchid.

Memorie partajata
Acest mecanism permite comunicarea intre procese prin accesul direct si partajat la o zona de memorie bine determinata.
 * la nivelul sistemului, o zona este identificata printr-un string de forma "/nume";
 * la nivelul codului, o zona este reprezentata printr-un file descriptor (int).
 * header-ele necesare pentru lucrul cu aceste obiecte sunt <fcntl.h>, <sys/types.h> si <sys/mman.h>.

Crearea si deschiderea
Apelul de creare/deschidere este similar ca semantica apelului open pentru fisiere "obsinuite": Ca flag de acces trebuie specificat fie O_RDONLY fie O_RDWR.

Redimensionarea
O zona de memorie partajata nou creata are dimensiunea initiala zero. Pentru a o dimensiona se foloseste ftruncate:

Maparea si eliberarea
Pentru a putea utiliza o zona de memorie partajata dupa deschidere, aceasta trebuie mapata in spatiul de memorie al procesului. Aceasta se realizeaza printr-un apel mmap: Valoarea intoarsa reprezinta un pointer catre inceputul zonei de memorie sau MAP_FAILED in caz de esec. Acest apel are o larga aplicabilitate si va fi discutat in cadrul laboratorului de memorie virtuala. Momentan, pentru a mapa intregul continut al unei zone (shm_fd) de dimensiune cunoscuta (shm_len), recomandam folosirea apelului

Cand maparea nu mai este necesara, prin apelul munmap se realieaza demaparea:

Inchiderea si stergerea
Inchiderea unei zone de memorie partajata este identica cu inchidere unui fisier: apelul close.

Odata ce o zona de memoria a fost demapata si inchisa in toate procesele implicate, se poate sterge prin shm_unlink: Semantica este identica cu cea de la functiile *_unlink anterioare: stergerea efectiva este amanata pana ce toate procesele implicate inchid zona in cauza.

Memoria partajata
In Linux, zonele pot fi regasite in /dev/shm, ca intrari formate din numele dat la creare + suffixul ".shm".

Cozi de mesaje
Continutul cozilor (continutul mesajelor) nu poate fi vizualizat, insa informatii statistice pot fi obtinute prin montarea unui pseudo-sistem de fisiere:

= Exercitii =

Prezentare
Pentru a urmari mai uşor noţiunile expuse la începutul laboratorului urmăriţi aceasta prezentare: odp, pdf.

Quiz
Pentru autoevaluare raspundeti la intrebarile din acest quiz.

Exercitii pentru laborator
Exercitiile sunt independente de platforma. Folositi arhiva de sarcini.


 * 1) (2 puncte) Intrați în directorul <tt>01_xmit/</tt>.
 * 2) * Inspectati continutul fisierelor <tt> util.h </tt>, <tt> client.c </tt> (sender) si <tt> server.c</tt> (receiver)
 * 3) * Procesul sender primeste din linia de comanda un numar intreg pozitiv si doreste sa-l trimita procesului receiver.
 * 4) * Folosindu-va doar de semafoare ca mijloc de comunicatie realizati transferul numarului astfel:
 * 5) * Procesul sender creaza semaforul, procesul receiver deschide semaforul si de asemenea la sfarsit il sterge.
 * 6) * Poate procesul receiver sa isi mai primeasca numarul daca procesul sender s-a terminat?
 * 7) * Hints: Windows: Analizati functia ReleaseSemaphore si observati cum se poate citi valoarea unui semafor.
 * 8) (2 puncte) Implementați un protocol simplu de comunicație între un client și un server folosind cozi de mesaje. Clientul se va conecta la server și va trimite acestuia numărul 1337 pe care server-ul îl va afișa. Apoi clientul va trimite server-ului un mesaj de închidere.
 * 9) * Hints:Fişierul <tt>common.h</tt> conţine structurile necesare protocolului. Pe Windows nu puteți selecta din coadă tipul mesajului pe care doriți să-l citiți, deci asigurați-vă că mesajele vor putea fi trimise și recepționate în mod asemănător pe Windows și Linux
 * 10) (2 puncte): Folosind memoria partajată, realizați un transfer simplu de informație între două procese astfel: server-ul va crea o zona de 4k de memorie și va pune numărul 1337 începând cu primul octet, clientul va citi și afișa acest număr.
 * 11) * Hints: Deoarece clientul trebuie ruleze după ce serverul a creat, mapat și scris în zona partajată și înainte ca acesta să o elibereze, folosiți o functie de sleep pentru server.

= Soluţii = Soluţii exerciţii laborator 5

= Resurse utile =
 * Fast User-level Locking In Linux