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. h8j20jk
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 semunA int val; struct semid_ds *stat; ushort *array;
Sctl_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)
A int status, semid; semid=semget(semkey, 1, SEMPERM | IPC_CREAT); status=semctl(semid, 0, SETVAL, 1); return semid;
S p(int semid)
A struc sembuf pbuf; pbuf.sem_num=0; pbuf.sem_op=-1; pbuf.sem_flg=SEM_UNDO; semop(semid, &pbuf, 1); return 0;
S
v(int semid)
A struct sembuf vbuf; vbuf.sem_num = 0; vbuf.sem_op=1; vbuf.sem_flg=SEM_UNDO; semop(semid, &vbuf, 1); return 0;
S
handlesem(int semid)
A int pid=getpid(); printf("Procesul %d inainte de regiunea critica\n",pid); p(semid); printf("Procesul %d in regiunea critica\n", pid); sleep(10); printf("Procesul %d paraseste regiunea critica\n",pid); v(semid); printf("Procesul %d terminat\n",pid); exit(0);
S
main()
A key_t semkey=0x200; int semid; semid=initsem(semkey); if(fork() == 0) handlesem(semid); if(fork() == 0) handlesem(semid); if(fork() == 0) handlesem(semid);
S
/* 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_bufA int d_nread; char d_bufaSIZi;
S;
main()
A static int semid, shmid1, shmid2; int pid; struct databuf *buf1, *buf2; semid=semget(SEMKEY, 2, 0600 | IPC_CREAT |IPC_EXCL); semctl(semid, 0, SETVAL, 0); semctl(semid, 1, SETVAL, 0); shmid1=shmget(SHMKEY1, sizeof(struct databuf), 0600 | IPC_CREAT |IPC_EXCL); shmid2=shmget(SHMKEY2, sizeof(struct databuf), 0600 | IPC_CREAT |IPC_EXCL); buf1=(struct databuf *)shmat(shmid1, 0, 0); buf2=(struct databuf *)shmat(shmid2, 0, 0); if((pid=fork()) == 0)
A
/* in procesul fiu */ reader(semid, buf1, buf2); shmctl(shmid1, IPC_RMID, 0); shmctl(shmid2, IPC_RMID, 0); semctl(semid, 0, IPC_ RMID, 0); exit(0);
S
/* in procesul parinte *
writer(semid, buf1, buf2);
S
struct sembuf p1=A0, -1, 0S; struct sembuf p2=A1, -1, 0S; struct sembuf v1=A0, +1, 0S; struct sembuf v2=A1, +1, 0S;
writer(int semid, struct databuf *buf1, struct databuf *buf2)
A for(;;)A buf1->d_nread=read(0, buf1->d_buf, SIZ); semop(semid, &v1, 1); semop(semid, &p2, 1); if(buf1->d_nread <= 0) return 0; buf2->d_nread=read(0, buf1->d_buf, SIZ); semop(semid, &v2, 1); semop(semid, &p1, 1); if(buf2->d_nread <= 0) return 0;
S
reader(int semid, struct databuf *buf1, struct databuf *buf2)
A sleep(5); for(;;)A semop(semid, &p1, 1); semop(semid, &v2, 1); if(buf1->d_nread <= 0) return 0;
write(1, buf1->d_buf, buf1->d_nread); semop(semid, &p2, 1); semop(semid, &v1, 1); if(buf2->d_nread <= 0) return 0;
write(1, buf2->d_buf, buf2->d_nread);
S