Laboratoare:Old:IPC (1)

= Linux =

Linux ofera o serie de metode de comunicare intre procese, grupate sub denumirea System V Inter-Process Communication si derivate din distributia de Unix System V release 4 AT&amp;T. Standardul System V Interface Definition consta din trei mecanisme IPC:


 * 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

Aceste mecanisme permit comunicatia intre procese prin mesaje, coordonarea accesului mai multor procese la resurse si partajarea datelor intre procese intr-o memorie comuna. Obiectele de tip IPC 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.

Crearea resurselor IPC se face din program cu ajutorul apelurilor de sistem ce vor fi prezentate mai jos, dar managementul lor se poate face si direct din linie de comanda. Exista doua utilitare pentru managementul resurselor IPC din sistem: pentru stergerea resurselor lasate de procese care nu au facut dealocare, sau, numai pentru verificarea numarului si proprietatilor resurselor alocate in sistem. Acestea sunt ipcs, care ofera informatie despre facilitatile IPC, si ipcrm, care sterge o resursa specificata printr-un ID.

Chei de acces
Atunci cand doua sau mai multe procese independente folosesc un anumit tip de IPC pentru schimb de informatie, obiectul IPC trebuie sa aiba un identificator(cheie) astfel incat un proces sa poata crea obiectul IPC  iar alte procese sa poata obtine acelasi obiect IPC. In cazul cozilor de mesaje, semafoarelor si segmentelor de memorie partajata cheia de acces este de tipul key_t , in mod obisnuit un intreg de cel putin 32 de biti. Valorile pentru cheie se obtin cu ajutorul functiei ftok.

Functia ftok converteste o cale existenta si un intreg intr-o valoare de tip key_t ( cheie de acces IPC ).

Procesele participante se pun de acord asupra unei cai din sistem si a unui id, apoi apeleaza functia ftok pentru a obtine o cheie comuna care va fi folosita ulterior la crearea obiectelor IPC.

Cozile de mesaje, multimile de semafoare si memoriile partajate primesc permisiuni asemanatoare fisierelor (cu exceptia faptului ca nu se folosesc decat read si write, nu si exec). Procesul care creeaza obiectele este si proprietarul default, ca si in cazul fisierelor, dar, spre deosebire de fisiere, procesul creator poate oferi sau revoca dreptul de proprietar altor procese.

Fiecare coada de mesaje, multime de semafoare sau segment de memorie partajata are o structura asociata (msqid_ds, semid_ds , respectiv shmid_ds) si grupeaza informatiile referitoare la creator, proprietar, permisiuni de acces intr-o structura ipc_perm.

Exista limite impuse de sistem sau de implementare asupra numarului si dimensiunii acestor resurse, discutate in sectiunile urmatoare.

Permisiuni
Fiecare resursa are asociata o structura ipc_perm care defineste id-urile procesului creator si proprietar, si permisiunile de acces: Procesul creator este si proprietar implicit, dar proprietarul poate fi reasignat de creator. Numai procesele proprietar, creator sau super-userul pot dealoca resursa.

Permisiunile de acces sunt specificate ca si pentru fisiere, ca permisiuni de read, write si exec pentru user, group si others. In cazul acestor resurse exec nu este folosit. In plus, pentru memorii partajate permisiunile de read si write pentru segmente sunt determinate de un flag separat care nu e memorat in campul ipc_perm.mode (specificat in apelul shmat). Un segment de memorie atasat cu acces la write poate fi citit.

Cei mai putin semnificativi 9 biti din flagul specificat de user intr-un apel de sistem sunt comparati cu valorile memorate in ipc_perm.mode pentru a determina daca accesul este acordat; daca apelul de sistem creeaza o resursa, valorile bitilor de acces specificati de user initializeaza ipc_perm.mode</tt>.

Campurile cuid</tt>, cgid</tt>, key</tt> si seq</tt> nu pot fi modificate de catre user.

Apelurile de sistem
Apelurile de sistem pentru lucrul cu resurse IPC sunt de trei tipuri: get, ctl si op.

get
Apelul de tip get (semget</tt> pentru semafoare, msgget</tt> pentru cozi de mesaje sau shmget</tt> pentru segmente de memorie partajata) primeste o cheie si intoarce un ID numeric folosit pentru accesul la resursa. Acest ID este un index intr-o tabela de resurse de acel tip (el este unic intre obiecte de acelasi tip, dar pot exista un semafor si un segment de memorie partajata, de exemplu, cu acelasi ID). Acest ID este calculat pe baza numarului de secventa seq</tt> memorat in structura ipc_perm</tt>.

Apelul primeste ca flaguri permisiunile si alte caracteristici de acces in forma: IPC_CREAT</tt> comanda crearea resursei in cazul in care ea nu exista; IPC_CREAT | IPC_EXCL</tt> specifica esuarea apelului daca resursa asociata lui key exista deja. Apelul intoarce un identificator folosit pentru accesele viitoare la coada de mesaje. O cheie speciala este IPC_PRIVATE</tt>, care asigura, in cazul succesului apelului, ca este creata o noua resursa.

ctl
Apelul de tip ctl (semctl</tt>, msgctl</tt> sau shmctl</tt>) modifica sau citeste informatiile din structura asociata resursei indicata de <tt>id</tt>.

Comenzile acceptate de apeluri ctl sunt urmatoarele :


 * <tt>IPC_STAT</tt> - citeste informatia relativ la resursa specificata in bufferul alocat de user; userul trebuie sa aiba acces pentru citire la resursa.
 * <tt>IPC_SET</tt> - scrie informatia aflata in buffer in structura asociata resursei; userul trebuie sa fie procesul creator, proprietar sau super-userul.
 * <tt>IPC_RMID</tt> - sterge resursa; userul trebuie sa fie procesul creator, proprietar sau super-userul. O coada de mesaje sau o multime de semafoare este imediat stearsa; segmentele de memorie partajata sunt distruse la ultima operatie de detach dupa comanda <tt>IPC_RMID</tt>.

Apelul <tt>semctl</tt> ofera in plus optiuni prin care se pot seta sau determina valorile semafoarelor dintr-o multime.

op
Apelurile de acest gen trimit sau primesc mesaje (<tt>msgsnd</tt> și <tt>msgrcv</tt> - pentru cozi de mesaje), citesc sau modifică valorile semafoarelor (<tt>semop</tt>), atașează sau detașează segmente de memorie partajată (<tt>shmat</tt> și respectiv <tt>shmdt</tt>). Flagul <tt>IPC_NOWAIT</tt> face ca apelul sa eșueze setând <tt>errno = EAGAIN</tt> dacă procesul trebuie să aștepte la apel.

Toate aceste apeluri vor fi detaliate în continuare pentru cele 3 tipuri de IPC.

Semafoare
Semafoarele sunt resurse IPC folosite pentru sincronizarea intre 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 carui valoare nu poate scadea sub 0.

Un identificator <tt>id</tt> permite accesul la o multime de nsems semafoare - un apel semget ofera accesul nu la un singur semafor, ci la un set/multime de semafoare (eventual continand un singur semafor). Operatiile de citire, incrementare sau decrementare a semafoarelor dintr-o multime sunt executate printr-un apel <tt>semop</tt>, care proceseaza nsops operatii la un moment dat, operatii aplicate numai in cazul in care toate se termina cu succes; fiecare operatie e specificata printr-o structura sembuf. Operatiile asupra semafoarelor pot avea un flag <tt>SEM_UNDO</tt>, care comanda ca la terminarea procesului rezultatul operatiei sa fie undone.

Daca se incearca o operatie de decrementare si aceasta nu se poate produce (valoarea semaforului nu poate scadea sub 0), procesul va fi pus in asteptare intr-o coada pana la incrementarea valorii semaforului - semval (daca nu a fost specificat <tt>IPC_NOWAIT</tt>). O operatie de citire poate duce, la fel, la punerea procesului in asteptare intr-o coada, pana cand semval devine 0.

O multime de semafoare este descrisa de structura : Fiecare semafor e descris intern de structura :

semget
O multime de semafoare e alocata printr-un apel de sistem <tt>semget</tt> : Parametri :


 * <tt>key</tt> - un intreg obtinut de obicei prin <tt>ftok</tt> sau <tt>IPC_PRIVATE</tt>
 * <tt>nsems</tt> - numarul de semafoare in multime (0 <= <tt>nsems</tt> <= <tt>SEMMSL</tt>; 0 inseamna don't care)
 * <tt>semflg</tt>
 * <tt>IPC_CREAT</tt> - creeaza o resursa daca ea nu exista deja
 * <tt>IPC_CREAT | IPC_EXCL</tt> - daca resursa exista deja, apelul esueaza
 * rwxrwxrwx - permisiuni de acces specificate ca in cazul fisierelor

Apelul intoarce un intreg folosit pentru accesele viitoare sau -1 la insucces.

O multime de semafoare noua este alocata daca nu exista o resursa asociata cheii date. In acest caz permisiunile de acces sunt copiate intr-o structura <tt>sem_perm</tt>. Pentru a se asigura ca se aloca o noua instanta, procesul user trebuie sa foloseasca flagul <tt>IPC_CREAT</tt> sau cheia <tt>IPC_PRIVATE</tt>.

Erori :


 * <tt>EINVAL</tt> - nsems not in above range (allocate); nsems greater than number in array (procure).
 * <tt>EEXIST</tt> - (allocate) <tt>IPC_CREAT | IPC_EXCL</tt> specified and resource exists.
 * <tt>EIDRM</tt> - (procure) The resource was removed.
 * <tt>ENOMEM</tt> - could not allocate space for semaphore array.
 * <tt>ENOSPC</tt> - No arrays available (<tt>SEMMNI</tt>), too few semaphores available (<tt>SEMMNS</tt>).
 * <tt>ENOENT</tt> - Resource does not exist and <tt>IPC_CREAT</tt> not specified.
 * <tt>EACCES</tt> - (procure) do not have permission for specified access.

semop
Operatiile pe semafoare sunt executate de apeluri <tt>semop</tt> :

int semop(int semid, struct sembuf *sops, unsigned nsops);

Parametri :


 * <tt>semid</tt> - ID-ul obtinut printr-un apel semget
 * <tt>sops</tt> - vectorul de operatii pe semafoare
 * <tt>nsops</tt> - numarul de operatii in vector (0 < <tt>nsops</tt> < <tt>SEMOPM</tt>)

Intoarce semval pentru ultima operatie efectuata, sau -1 in caz de eroare.

O operatie e descrisa de structura :

struct sembuf { 	ushort sem_num;        /*  semaphore  index  in  array  */ short sem_op;          /*  semaphore  operation  */ short sem_flg;         /*  operation  flags  */ }

Valoarea <tt>sem_op</tt> va fi adaugata valorii curente <tt>semval</tt> a semaforului cu indexul <tt>sem_num</tt> (intre 0 si nsems-1) din multime. Flagurile pot avea valorile <tt>IPC_NOWAIT</tt> sau <tt>SEM_UNDO</tt>.

Doua tipuri de operatii pot avea ca rezultat punerea unui proces in asteptare :


 * daca <tt>sem_op = 0</tt> (operatia este de citire) si <tt>semval != 0</tt>, procesul asteapta in coada pana cand semval devine 0 sau se termina cu codul de eroare <tt>EAGAIN</tt> daca <tt>(IPC_NOWAIT & sem_flg) != 0</tt>
 * daca <tt>sem_op < 0</tt> si <tt>(semval + sem_op) < 0</tt>, procesul fie asteapta in coada marirea valorii semval, fie se termina cu codul de eroare <tt>EAGAIN</tt> daca <tt>(sem_flg & IPC_NOWAIT) != 0</tt>.

Vectorul <tt>sops</tt> este intai citit si sunt efectuate verificari preliminare pe argumente; operatiile sunt parcurse pentru a determina daca oricare din ele are nevoie de permisiuni de scriere sau cere o operatie de undo. Apoi operatiile sunt incercate si procesul este pus in asteptare daca oricare din operatiile care nu a specificat <tt>IPC_NOWAIT</tt> nu se poate executa. Aceste verificari se repeta la trezirea procesului. Daca o operatie care cere <tt>IPC_NOWAIT</tt> nu se poate efectua, apelul se termina cu codul se eroare <tt>EAGAIN</tt>.

Operatiile sunt executate atunci cand toate se pot efectua fara sa intervina asteptari in coada.

Erori :


 * <tt>E2BIG</tt> - <tt>nsops > SEMOPM</tt>
 * <tt>EACCES</tt> - Do not have permission for requested (read/alter) access.
 * <tt>EAGAIN</tt> - An operation with <tt>IPC_NOWAIT</tt> specified could not go through.
 * <tt>EFAULT</tt> - The array <tt>sops</tt> is not accessible.
 * <tt>EFBIG</tt> - An operation had <tt>semnum >= nsems</tt>.
 * <tt>EIDRM</tt> - The resource was removed.
 * <tt>EINTR</tt> - The process was interrupted on its way to a wait queue.
 * <tt>EINVAL</tt> - nsops is 0, <tt>semid</tt> < 0 or unused.
 * <tt>ENOMEM</tt> - <tt>SEM_UNDO</tt> requested. Could not allocate space for undo structure.
 * <tt>ERANGE</tt> - <tt>sem_op + semval > SEMVMX</tt> for some operation.

semctl
int semctl(int semid, int semnum, int cmd, ...);

Parametri :


 * <tt>semid</tt> - ID-ul obtinut printr-un apel <tt>semget</tt>
 * <tt>cmd</tt>
 * <tt>GETPID</tt> - intoarce pid-ul procesului care a executat ultimul <tt>semop</tt>
 * <tt>GETVAL</tt> - intoarce valoarea semaforului cu index <tt>semnum</tt>
 * <tt>GETNCNT</tt> - intoarce numarul de procese care asteapta marirea semval
 * <tt>GETZCNT</tt> - intoarce numarul de procese care asteapta ca semval sa devina 0
 * <tt>SETVAL</tt> - seteaza semval = <tt>arg.val</tt>
 * <tt>GETALL</tt> - citeste toate valorile semval in <tt>arg.array</tt>
 * <tt>SETALL</tt> - seteaza toate valorile semval la valorile date in <tt>arg.array</tt>
 * <tt>IPC_RMID</tt> - sterge setul de semafoare din sistem
 * ultimul argument este necesar doar pentru unele comenzi; cand este prezent are tipul <tt>union semun</tt>

Intoarce in caz de succes informatiile precizate mai sus sau 0, si -1 in caz de eroare.

Primele 5 operatii actioneaza asupra semaforului de indice <tt>semnum</tt> din multime. Urmatoarele 3 opereaza asupra tuturor semafoarelor din multime.

Dupa cum am precizat, tipul argumentului optional este un union:

union semun { 	int val;               /*  value  for  SETVAL  */ struct semid_ds *buf;  /*  buffer  for  IPC_STAT  and  IPC_SET  */ ushort *array;         /*  array  for  GETALL  and  SETALL  */ }

In cazul <tt>IPC_SET</tt>, <tt>SETVAL</tt>, <tt>SETALL</tt> <tt>sem_ctime</tt> este updatat. In cazul <tt>IPC_SET</tt> campurile <tt>sem_perm.uid</tt>, <tt>sem_perm.gid</tt>, <tt>sem_perm.mode</tt> primesc valorile precizate de user. In cazul <tt>SETALL</tt>, <tt>SETVAL</tt> informatiile de undo sunt sterse pentru semafoarele modificate in toate procesele.

Procesele care asteapta intr-o coada sunt trezite daca un semval devine 0 sau isi mareste valoarea.

Erori :


 * <tt>EACCES</tt> - do not have permission for specified access.
 * <tt>EFAULT</tt> - arg is not accessible.
 * <tt>EIDRM</tt> - The resource was removed.
 * <tt>EINVAL</tt> - <tt>semid</tt> < 0 or <tt>semnum</tt> < 0 or <tt>semnum >= nsems</tt>
 * <tt>EPERM</tt> - <tt>IPC_RMID</tt>, <tt>IPC_SET</tt> ... not creator, owner or super-user.
 * <tt>ERANGE</tt> - <tt>arg.array[i].semval > SEMVMX</tt> or < 0 for some <tt>i</tt>.

Limitari
Dimensiunile structurilor :


 * <tt>semid_ds</tt> : 44; o structura pentru o multime de semafoare
 * <tt>sem</tt> : 8; o structura pentru fiecare semafor din sistem
 * <tt>sembuf</tt> : 6; alocata de user
 * <tt>sem_undo</tt> : 20; o structura pentru fiecare cerere de undo

Limite :


 * <tt>SEMVMX</tt> : 32767; valoarea maxima a semaforului (short);
 * <tt>SEMMNI</tt> : numarul de identificatori de multimi de semafoare in tot sistemul;
 * <tt>SEMMSL</tt> : numarul maxim de semafoare pentru un ID; cum exista o structura <tt>semid_ds</tt> pentru o multime si o structura <tt>sem</tt> pentru un semafor, <tt>SEMMSL = (PAGE_SIZE - sizeof(semid_ds)) / sizeof(sem)</tt>. Din implementare <tt>SEMMSL</tt> = 500.
 * <tt>SEMMNS</tt> : numarul maxim de semafoare in sistem;
 * <tt>SEMOPM</tt> : numarul maxim de operatii intr-un apel <tt>semop</tt>.

Exemplu semafor Linux
Prezentam un exemplu simplu de folosire a semafoarelor in Linux: un proces server creeaza un semafor pe care il foloseste ca sa astepte ca un proces client sa inceapa executia.

'''Exemplu 01-a. ipc1-common.h''' '''Exemplu 01-b. ipc1-server.cpp'''

'''Exemplu 01-c. ipc1-client.cpp'''

Note suplimentare
ATENTIE! Dupa un apel <tt>fork</tt>, procesul fiu nu mosteneste si informatia de undo a semafoarelor. La un apel <tt>exec</tt> informatia de undo ramane intacta.

= Windows =

La fel ca sistemul de operare Linux, Windows-ul 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) si Mail Slots (cozi de mesaje).

Obiecte de sincronizare
Obiectele de sincronizare (synchronization objects) sunt gestionate global de catre sistem, si sunt reprezentate in programe prin tipul <tt>HANDLE</tt>. Acest <tt>HANDLE</tt> nu este altceva decat un identificator, similar cu descriptorul de fisier. Orice operatie asupra unui obiect de sincronizare va implica cel putin un parametru de tip <tt>HANDLE</tt>, prin acesta specificandu-se obiectul asupra caruia are efect functia.

Un obiect de sincronizare este caracterizat (in mod general) de :


 * tip (event, mutex, semaphore, wait timer, process, etc)
 * nume, care poate fi inexistent
 * stare (disponibil = 'signaled' sau indisponibil = 'nonsignaled')

Starea unui obiect de sincronizare poate fi disponibil ('signaled') sau indisponibil ('nonsignaled') si poate fi modificata de operatiile cu respectivul obiect si de functiile de asteptare aplicate lui; acestea din urma fiind la randul lor afectate de starea obiectului.

Dupa tip, obiectele de sincronizare se impart in :

Sunt obiecte al caror unic scop este sincronizarea. In aceasta categorie intra : Sunt obiecte care au alta functionalitate primara, dar ofera si posibilitatea de a fi folosite pentru sincronizare. Din aceasta categorie fac parte (printre altele) :
 * obiecte folosite exclusiv pentru sincronizare
 * Evenimentele (Events)
 * Mutex-urile
 * Semafoarele (semaphores)
 * Waitable timer
 * obiecte care pe langa functia de baza pot fi folosite si pentru sincronizare
 * Intrarea de consola (console input)
 * Procesele
 * Thread-urile

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 si dechiderea
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 <tt>HANDLE</tt> al acelui obiect. Scopul functiei de creare si a celei de deschidere este acela de a obtine un <tt>HANDLE</tt> 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 <tt>HANDLE</tt>-ul putem obtine/elibera mutex-ul de cate ori avem nevoie.

Pentru a crea un mutex se foloseste functia <tt>CreateMutex</tt> cu sintaxa : parametrii avand urmatoarea semnificatie :

Atentie, este case-sensitive! Daca este <tt>NULL</tt>, obiectul mutex creat va fi anonim. Daca exista deja un mutex cu numele specificat, se face o cerere de deschidere cu drepturile <tt>MUTEX_ALL_ACCESS</tt>. In acest caz <tt>bInitialOwner</tt> este ignorat. Daca exista deja un obiect eveniment, semafor, waitable timer, job sau file-mapping cu numele specificat, functia va intoarce eroare.
 * <tt>lpMutexAttributes</tt> - Pointer la o structura <tt>SECURITY_ATTRIBUTES</tt> care determina daca <tt>HANDLE</tt>-ul returnat poate fi mostenit de procesele copil. Daca este <tt>NULL</tt>, se considera ca nu poate fi mostenit. Mai multe informatii despre aceasta structura se gasesc in laboratorul anterior, in care se prezinta procesele.
 * <tt>bInitialOwner</tt> - Daca este <tt>TRUE</tt> procesul care creeaza mutex-ul il va detine automat. Daca este <tt>FALSE</tt>, mutex-ul creat va fi disponibil.
 * <tt>lpName</tt> - Pointer la un sir de caractere care specifica numele mutex-ului.

Se observa ca se poate folosi functia de creare a unui mutex pe post de functie de deschidere a unui mutex deja existent.

Pentru a deschide un mutex deja existent este definita functia <tt>OpenMutex</tt> cu sintaxa : Parametrii au urmatoarea semnificatie :


 * <tt>dwDesiredAccess</tt> - Modul de acces dorit. Functia esueaza daca accesul dorit nu este permis de descriptorul de securitate al obiectului.
 * <tt>bInheritHandle</tt> - <tt>TRUE</tt> daca se doreste ca procesele copil sa mosteneasca acest <tt>HANDLE</tt>.
 * <tt>lpName</tt> - Numele mutexului; case-sensitive.

Obtinerea
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 <tt>WaitForSingleObject</tt>.

Eliberarea
Folosind functia <tt>ReleaseMutex</tt> se cedeaza posesia mutex-ului, el devenind iar disponibil. Functia are urmatoarea sintaxa : Singurul parametru reprezinta <tt>HANDLE</tt>-ul mutex-ului care se doreste eliberat.

Functia va esua daca procesul nu detine mutex-ul.

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

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

ATENTIE! La terminarea executiei unui program toate <tt>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.

Functii de asteptare
Functiile de asteptare (wait functions) au rolul de a bloca executia unui proces/thread pana la indeplinirea unor conditii. Tipul functiei de asteptare si parametrii acesteia determina aceste conditii. Pana cand conditiile sunt indeplinite procesul/threadul intra in stare de asteptare.

Exista patru tipuri de functii de asteptare in functie de numarul de obiecte de sincronizare folosite si de comportament :


 * single-object
 * multiple-object
 * alertable
 * registered

Functii de 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 <tt>INFINITE</tt> - timpul de asteptare nu expira niciodata.

Rezultatul intors de aceste functii poate fi :


 * <tt>WAIT_OBJECT_0</tt> - Succes
 * <tt>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).
 * <tt>WAIT_IO_COMPLETION</tt> - Asteptarea a fost intrerupta de un apel asincron de procedura.
 * <tt>WAIT_TIMEOUT</tt> - Timpul de expirare s-a scurs.
 * <tt>WAIT_FAILED</tt> - Functia a esuat. Informatii despre eroare pot fi obtinute folosind functia <tt>GetLastError</tt>.

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

SignalObjectAndWait
Aceasta functie semnalizeaza un obiect si asteapta dupa altul. Functia are sintaxa : Parametrii au umatoarea semnificatie :


 * <tt>hObjectToSignal</tt> - Obiectul care va fi semnalat (semafor, mutex sau eveniment).
 * <tt>hObjectToWaitOn</tt> - Obiectul dupa care se asteapta sa fie liber (semafor, mutex, event, waitable timer, proces, thread, etc).
 * <tt>dwMilliseconds</tt> - Timpul dupa care expira, in milisecunde. Poate fi <tt>INFINITE</tt>.
 * <tt>bAlertable</tt> - Daca este <tt>TRUE</tt>, functia se termina cand sistemul programeaza o rutina de tratare a unei operatii de I/O asincron terminata sau a unui apel asincron terminat (mai multe detalii la laboratorul de Semnale).

WaitForSingleObject
Functia asteapta dupa un singur obiect si are sintaxa : Parametrii au umatoarea semnificatie :


 * <tt>hHandle</tt> - <tt>HANDLE</tt>-ul obiectului dupa care se asteapta.
 * <tt>dwMilliseconds</tt> - timpul de asteptare. Poate fi <tt>INFINITE</tt>.

Valorile returnate pot fi doar : <tt>WAIT_ABANDONED</tt>, <tt>WAIT_OBJECT_0</tt>, <tt>WAIT_TIMEOUT</tt> si bineinteles <tt>WAIT_FAILED</tt>.

WaitForSingleObjectEx
Functia permite o asteptare alertabila dupa un singur obiect si are sintaxa : Parametrii au semnificatiile similare celor doua functii deja prezentate.

Functii de 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 <tt>INFINITE</tt> pentru a specifica ca timpul de asteptare nu va expira niciodata

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

WaitForMultipleObjects
Functia asteapta dupa mai multe obiecte si are sintaxa : Parametrii au umatoarea semnificatie :


 * <tt>nCount</tt> - Numarul de obiecte.
 * <tt>lpHandles</tt> - Pointer la un vector de <tt>HANDLE</tt>-uri ale obiectelor de asteptare. Lista poate contine tipuri de obiecte diferite, dar nu poate contine un obiect de doua ori.
 * <tt>bWaitAll</tt> - Daca este <tt>TRUE</tt> se asteapta ca TOATE obiectele sa fie in starea 'signaled'. Daca este <tt>FALSE</tt> este de ajuns ca un singur obiect sa fie in starea 'signaled'.
 * <tt>dwMilliseconds</tt> - timpul de asteptare. Poate fi <tt>INFINITE</tt>.

Valorile posibile pe care le intoarce aceasta functie sunt :

Daca <tt>bWaitAll</tt> este <tt>TRUE</tt>, valoarea returnata indica faptul ca starea tuturor obiectelor este 'signaled'. Daca <tt>bWaitAll</tt> este <tt>FALSE</tt>, valoarea returnata minus <tt>WAIT_OBJECT_0</tt> indica indexul obiectului (din vector) care a cauzat terminarea asteptarii. In cazul in care mai multe obiecte au cauzat terminarea asteptarii, valoarea in cauza este valoarea celui cu indexul cel mai mic. Daca <tt>bWaitAll</tt> este <tt>TRUE</tt>, valoarea returnata indica faptul ca starea tuturor obiectelor este 'signaled', dar cel putin unul din ele este un mutex abandonat. Daca <tt>bWaitAll</tt> este <tt>FALSE</tt>, valoarea returnata minus <tt>WAIT_ABANDONED_0</tt> indica indexul obiectului care a cauzat terminarea asteptarii si care este un mutex abandonat.
 * <tt>WAIT_OBJECT_0</tt> ...pana la... (<tt>WAIT_OBJECT_0 + nCount - 1</tt>)
 * <tt>WAIT_ABANDONED_0</tt> ...pana la... (<tt>WAIT_ABANDONED_0 + nCount - 1</tt>)
 * <tt>WAIT_TIMEOUT</tt> - timpul de asteptare a expirat

WaitForMultipleObjectsEx
Functia permite o asteptare alertabila dupa mai multe obiecte si are sintaxa : Parametrii au semnificatia similara cu cei de la functia <tt>WaitForMultipleObjects</tt>, iar <tt>bAlertable</tt> are aceeasi semnificatie ca la functia <tt>WaitForSingleObjectEx</tt>.

Valorile pe care le intoarce aceasta functie sunt cele de la functia <tt>WaitForMultipleObjects</tt> si <tt>WAIT_IO_COMPLETION</tt>, cu semnificatiile corespunzatoare.

Functii de asteptare alertabile
Functiile care se incadreaza in aceasta categorie sunt :


 * <tt>WaitForSingleObjectEx</tt>
 * <tt>WaitForMultipleObjectsEx</tt>
 * <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 <tt>BOOL bAlertable</tt> pe care aceste functii il accepta.

Functii de asteptare inregistrate
Aceste functii sunt folosite de programele cu thread-uri si vor fi explicate in laboratoarele care trateaza thread-urile.

Exemplu mutex Windows
Exemplul complet se gaseste pe MSDN.

'''Exemplul 02-a. Crearea unui mutex (windows)''' '''Exemplul 02-b. Folosirea mutex-ului pentru a accesa o baza de date partajata'''

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 (spre deosebire de Linux 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
Functia de creare a semafoarelor este <tt>CreateSemaphore</tt> si are sintaxa : Parametrii au urmatoarea semnificatie :

Poate avea dimensiunea maxima <tt>MAX_PATH</tt> si este case-sensitive! Daca este <tt>NULL</tt>, obiectul semafor creat va fi anonim. Daca exista deja un semafor cu numele specificat, se face o cerere de deschidere cu drepturile <tt>SEMAPHORE_ALL_ACCESS</tt>. In acest caz <tt>lInitialCount</tt> si <tt>lMaximumCount</tt> sunt ignorate, deoarece ele au fost deja setate cand semaforul a fost creat. Daca exista deja un event, mutex, waitable timer, job sau file-mapping cu numele specificat, functia va intoarce eroare.
 * <tt>lpSemaphoreAttributes</tt> - Pointer la o structura <tt>SECURITY_ATTRIBUTES</tt> care determina daca <tt>HANDLE</tt>-ul returnat poate fi mostenit de procesele copil. Daca este <tt>NULL</tt>, se considera ca nu poate fi mostenit.
 * <tt>lInitialCount</tt> - Valoarea initiala a semaforului. Poate sa fie chiar zero.
 * <tt>lMaximumCount</tt> - Valoarea maxima a semaforului. Trebuie sa fie strict pozitiva.
 * <tt>lpNAME</tt> - Pointer la un sir de caractere care specifica numele semaforului.

Se observa ca functia <tt>CreateSemaphore</tt> se poate folosi si pentru deschiderea unui semafor deja existent.

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


 * <tt>dwDesiredAccess</tt> - Modul de access dorit. Functia esueaza daca accesul dorit nu este permis de descriptorul de securitate al obiectului.
 * <tt>bInheritHandle</tt> - <tt>TRUE</tt> daca se doreste ca procesele copil sa mosteneasca acest <tt>HANDLE</tt>.
 * <tt>lpNAME</tt> - Numele semaforului; case-sensitive.

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

Incrementarea semaforului
Incrementarea semaforului se realizeaza folosind functia <tt>ReleaseSemaphore</tt> cu sintaxa : Parametrii au urmatoarea semnificatie :


 * <tt>hSemaphore</tt> - <tt>HANDLE</tt>-ul reprezentand semaforul asupra caruia se executa operatia.
 * <tt>lReleaseCount</tt> - Valoarea cu care se incrementeaza semaforul; trebuie sa fie strict pozitiva. Daca valoarea in urma incrementarii semaforului ar depasi valoarea maxima el NU este incrementat si functia esueaza.
 * <tt>lpPreviousCount</tt> - Pointer unde va fi intoarsa valoarea semaforului inainte de a fi incrementat. Poate fi <tt>NULL</tt> daca nu ne intereseaza aceasta valoare.

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.

Exemplu semafor Windows
Exemplul arata cum poate fi folosit un semafor pentru a limita numarul de ferestre ce pot fi create de diferite procese.

'''Exemplu 3-a. Crearea unui semanfor''' '''Exemplu 3-b. Folosirea semaforului'''

Observatii

 * Numele obiectelor de sincronizare (parametrul <tt>LPCTSTR lpName</tt>) nu poate contine caracterul '\' (backslash), pentru sistemele de operare Windows 95/98/Me/NT.
 * Numele obiectelor de sincronizare (parametrul <tt>LPCTSTR lpName</tt>) poate contine prefixul "Global\" sau "Local\" pentru a crea explicit un obiect in spatiul de nume global sau sesiune. Folosit doar in cazul in care Terminal Services ruleaza.

= Exerciții =

Quiz
Pentru autoevaluare raspundeți la întrebările din acest quiz.

Exerciții pre-laborator
Folosiți [[media:lab5-pre.zip|arhiva de pre-sarcini]] a laboratorului.

Linux
Folosiți directorul <tt>lin/</tt> din [[media:lab5-pre.zip|arhiva de pre-sarcini]] a laboratorului.


 * 1) Intrati in directorul <tt>view/</tt>.
 * 2) *Compilati programul <tt>view.c</tt> si obtineti executabilul <tt>view.</tt>
 * 3) *Dupa rularea executabilului folositi comanda <tt> ipcs </tt> cu parametrul corespunzator pentru a observa toate semafoarele din sistem.
 * 4) *Folosind iesirea programului view, eliminati din sistem semaforul creat folosind comanda <tt>ipcrm </tt> utilizand fie cheia , fie id-ul cu care a fost creat semaforul.
 * 5) * Cititi paginile de manual pentru comenzile <tt> ipcs </tt> si <tt> ipcrm </tt> pentru a determina parametrii cu care trebuie folosite.
 * 6) Intrati in directorul <tt>semrm/</tt>.
 * 7) *Completati fisierul <tt> semrm.c </tt> astfel incat sa realizeze stergerea unui semafor din sistem pe baza cheii sau a id-ului primit ca parametru in linia de comanda.
 * 8) *Recititi pagina de manual pentru comanda <tt>ipcrm</tt> si observati cu ce parametru se sterge un semfor cunoscandu-i cheia sau id-ul.
 * 9) *Ce concluzie puteti trage referitor la id-ul semaforului?
 * 10) Intrati in directorul <tt>cs/</tt>.
 * 11) *Completati functiile <tt>cs_enter </tt> si <tt>cs_leave</tt> astfel incat sa implementati o sectiune critica.
 * 12) *Numarul proceselor care au voie sa fie in sectiunea critica la un moment dat este dat ca parametru in linia de comanda.
 * 13) *Folositi un semafor initializat cu numarul de procese acceptate in sectiunea critica.

Windows
Folosiți directorul <tt>win/</tt> din [[media:lab5-pre.zip|arhiva de pre-sarcini]] a laboratorului.
 * 1) Intrati in directorul <tt>cs/</tt>.
 * 2) *Completati functiile <tt>CSEnter </tt> si <tt>CSLeave</tt> astfel incat sa implementati o sectiune critica.
 * 3) *Numarul proceselor care au voie sa fie in sectiunea critica la un moment dat este dat ca parametru in linia de comanda.
 * 4) *Folositi un semafor initializat cu numarul de procese acceptate in sectiunea critica.
 * 5) Folositi-va imaginatia si gasiti diferentele dintre un mutex si un semafor binar.

Exerciții de laborator
Folosiți [[media:lab5-tasks.zip|arhiva de sarcini]] a laboratorului.

Pentru Linux folositi directorul <tt>lin/</tt> iar pentru Windows folositi directorul <tt> win/ </tt> din [[media:lab5-tasks.zip|arhiva de sarcini]] a laboratorului.


 * 1) (1 punct) Intrați în directorul <tt>sem/</tt>.
 * 2) * Inspectati continutul fisierelor : <tt>util.h</tt>,  <tt>server.c</tt> si <tt>client.c </tt>
 * 3) * Completați fișierul <tt>server.c</tt> cu definiția funcțiilor <tt>create_sem</tt>, <tt>decrement_sem</tt> si <tt>erase_sem</tt> dupa cum urmeaza:
 * 4) ** <tt>create_sem</tt> creaza o multime cu un singur semafor si initializeaza contorul semaforului la 0.
 * 5) ** <tt>decrement_sem </tt> decrementeaza semaforul al carui id il primeste ca parametru.
 * 6) ** <tt>erase_sem</tt> sterge semaforul al carui id il primeste ca parametru.
 * 7) * Completati fisierul <tt>client.c</tt> cu definitia functiilor <tt>open_sem</tt> si <tt>increment_sem</tt> dupa cum urmeaza:
 * 8) ** <tt>open_sem</tt> deschide un semafor folosind cheia de acces primita ca parametru.
 * 9) ** <tt>increment_sem</tt> incrementeaza semaforul al carui id il primeste ca parametru.
 * 10) * Nu este nevoie sa verificati, in cadrul acestor functii , valorile intoarse de apelurile de biblioteca. Verificarile se fac in functia main a fiecarui program.
 * 11) * Compilati cele doua programe si verificati daca functiile implementate functioneaza corect.
 * Hints:
 * 1) ***Linux
 * 2) **** pentru operatiile de incrementare/decrementare completati corespunzator structura struct sembuf si folositi functia semop.
 * 3) **** folositi paginile de manual semctl(2), semop(2) si  semget(2).
 * 4) **** urmariti exemplul de semafor in Linux din laborator.
 * 5) ***Windows
 * 6) **** numele semaforului creat/deschis este definit in fisierul <tt> util.h </tt> de constanta <tt>NUME_SEMAFOR</tt>.
 * 7) (2 puncte) Intrați în directorul <tt>transmission/</tt>.
 * 8) * Inspectati continutul fisierelor <tt> util.h </tt>, <tt> sender.c </tt> si <tt> receiver.c</tt>
 * 9) * Procesul sender primeste din linia de comanda un numar intreg pozitiv si doreste sa-l trimita procesului receiver.
 * 10) * Folosindu-va doar de semafoare ca mijloc de comunicatie realizati transferul numarului astfel:
 * 11) ** Completati fisierul <tt> sender.c </tt> cu definitia functiei <tt> offer </tt> care ofera numarul.
 * 12) ** Completati fisierul <tt> receiver.c </tt> cu definita functiei <tt> receive </tt> care preia numarul.
 * 13) * Procesul sender creaza semaforul, procesul receiver deschide semaforul si de asemenea la sfarsit il sterge.
 * 14) * Poate procesul receiver sa isi mai primeasca numarul daca procesul sender s-a terminat?
 * Hints:
 * 1) *** Linux
 * 2) **** Valoarea unui semafor poate fi setata la un numar pozitiv, si de asemenea valoarea poate fi citita.
 * 3) **** Folositi uniunea <tt> semun</tt> din fisierul <tt> util.h </tt> si apelul <tt> semctl </tt> pentru a seta valoarea unui semafor.
 * 4) **** Atentie: variabila de tip <tt> semun </tt> trebuie transmisa prin valoare si nu prin adresa in apelul <tt>semctl</tt>.
 * 5) *** Windows
 * 6) **** Analizati functia <tt>ReleaseSemaphore</tt> si observati cum se poate citi valoarea unui semafor.
 * 7) ( 2 puncte ) Intrati in directorul <tt> mutex/ </tt>.
 * 8) * Completati fisierul <tt> mutex.c </tt> cu functiile <tt> open_mutex </tt>, <tt> destroy_mutex </tt> , <tt> lock_mutex </tt> si <tt>unlock_mutex </tt> astfel incat sa realizati implementarea unui mutex folosind semafoare:
 * 9) ** functia <tt> open_mutex </tt> la primul apel creaza un semafor iar la apelurile ulterioare deschide semaforul existent.
 * 10) ** functia <tt> lock_mutex </tt> obtine mutex-ul eventual blocandu-se pana il obtine.
 * 11) ** functia <tt> unlock_mutex </tt> elibereaza mutex-ul.
 * 12) ** functia <tt> destroy_mutex </tt> sterge semaforul.
 * 13) * Folositi use_mutex.c pentru a testa mutex-ul implementat.
 * Hints:
 * 1) *** Observati structura <tt>mutex_t</tt> din fisierul <tt> mutex.h</tt>, campul <tt> locked </tt> retine starea mutex-ului pentru procesul curent.
 * 2) *** Tratati cazurile in care mutex-ul este deja obtinut si doriti sa il mai obtineti o data, de asemenea cazul in care mutex-ul este deja eliberat si doriti sa il mai eliberati o data.
 * 3) ( 3 puncte ) Intrati in directorul <tt> pc/ </tt>
 * 4) * Folositi fisierele <tt> producer.c </tt>, <tt> consumer.c</tt> si <tt> common.h</tt> pentru a implementa problema producator-consumator.
 * 5) ** Completati functiile din fisierul <tt> common.h </tt> pentru a realiza operatiile corespunzatoare pe semafoare.
 * 6) ** Folosindu-va de functiile din <tt> common.h </tt> implementati in <tt> producer.c </tt> un producator de obiecte si in <tt> consumer.c </tt> un consumator de obiecte.
 * 7) *** Obiectele produse sunt imaginare ( nu trebuie sa existe doar trebuie sa afisati un mesaj atunci cand produceti respectiv consumati un obiect ).
 * 8) *** Numarul de obiecte produse/consumate este egal cu constanta <tt> HOW_MANY</tt> definita in <tt> common.h</tt>.
 * 9) *** Dimensiunea buffer-ului este egala cu constanta <tt> DIMENSIUNE_BUFFER </tt> definita in <tt> common.h </tt>.
 * Hints:
 * 1) *** Folositi doua semafoare, cheile de acces le obtineti folosind constantele PATH_SEMAFOR_TO_EMPTY si PATH_SEMAFOR_TO_FULL
 * 2) *** Problema producator-consumator este o problema clasica de sincronizare.
 * 3) ( 5 puncte ) Intrati in directorul <tt> rbarrier/ </tt>.
 * 4) * Completati functiile din fisierul <tt> common.h </tt> pentru a realiza operatiile corespunzatoare cu semafoare.
 * 5) * Folosindu-va de functiile din <tt> common.h </tt> completati functiile din fisierul <tt>barrier.c </tt> pentru a implementa o bariera reentranta.
 * 6) ** Atentie: Aveti voie sa folositi doar semafoare si (eventual) mutex-uri.

= Soluții =

[[Media:lab5-tasks-sol.zip|Soluţii exerciţii laborator 5]]

= Resurse utile =


 * 1) Wikipedia - Semafoare
 * 2) Wikipedia - Excludere mutuala
 * 3) MSDN: Exemplu de folosire a mutex-urilor in Windows
 * 4) MSDN: Exemplu de folosire a semafoarelor in Windows
 * 5) IPC în Linux

= Note =


 * 1) gasiti aici o explicatie
 * 2) puteti consulta The Little Book of Semaphores