background image

10.    Synchronizacja użycia zasobów, Semafory 

10.1  Podstawy synchronizacji, kanoniczne operacje synchronizacyjne 
Rodzaje współpracy procesów współbieżnych: 
 
Współbieżność kooperacyjna  
Procesy współpracują ze sobą w ramach jednej aplikacji która ma realizować pewne 
zadanie. Synchronizacja polega na zapewnieniu koordynacji procesów 
   
Współbieżność konkurencyjna  
We wspólnym środowisku działa wiele procesów lub wątków dzieląc zasoby tego 
środowiska. System operacyjny musi zapewnić koordynację w dostępie do 
wspólnych zasobów. 
 
W obydwu przypadkach wyodrębnić można dwie kanoniczne operacje 
synchronizacyjne Suspend i Resume: 
 
Suspend(Queue K) – zawieszenie procesu bieżącego w kolejce K 
 
Kroki procedury Suspend 
Stan procesu bieżącego zapamiętywany jest w jego deskryptorze. 

1.  Deskryptor usuwany jest z kolejki procesów gotowych i umieszczany w kolejce 

K. 

2.  Uruchamiana jest procedura szeregująca która wybiera do wykonania pewien 

proces z kolejki procesów gotowych. 

 

 

Ready

Next

PD

Next

PD

NIL

PD

Kolejka procesów gotowych

Proces bieżący

K

Next

PD

Next

PD

NIL

PD

Kolejka procesów zawieszonych

Kolejka

Suspend(K)

Wskaźnik na

kolejkę

procesów
gotowych

PD - pola

deskryptora

procesu

 

background image

 
Resume(Queue K)  - Wznowienie pierwszego procesu z kolejki K 
 
Kroki procedury Resume 

1.  Deskryptor pierwszego procesu z kolejki K jest przenoszony z tej kolejki do 

kolejki procesów gotowych. 

2.  Uruchamiana jest procedura szeregująca która wybiera do wykonania pewien 

proces z kolejki procesów gotowych. 

 
 
 

Ready

Next

PD

Next

PD

NIL

PD

Kolejka procesów gotowych

Proces bieżący

K

Next

PD

Next

PD

NIL

PD

Kolejka procesów zawieszonych

Kolejka

Wskaźnik na

kolejkę

procesów
gotowych

Resume(K)

 

 
 
Nieraz niezbędna jest procedura Resume(K,P) – wznowienie procesu P (P – PID 
procesu) z kolejki K. 
 

background image

 

10.2   Problem producenta i konsumenta 
 
Zagadnienie kontroli użycia jednostek zasobu  
W systemie istnieje pula N jednostek zasobu pewnego typu. Procesy mogą pobierać 
z puli zasoby i je zwracać. Gdy brak jest zasobu a pewien proces będzie próbował go 
pobrać ulega on zablokowaniu. Proces zostanie odblokowany gdy inny proces zwróci 
jednostkę zasobu. 
 
1.  W systemie istnieją dwie grupy procesów – producenci i konsumenci oraz bufor 

na elementy. 

2. Producenci produkują pewne elementy i umieszczają je w buforze. 
3. Konsumenci pobierają elementy z bufora i je konsumują . 
4.  Producenci i   konsumenci  przejawiają swą aktywność w nie dających się określić 

momentach czasu. 

5.  Bufor ma pojemność na N elementów. 
 
 

BUFOR  N - elementów

Zajęte

Wolne

P1

P2

Pk

Producenci

K1

K2

Kl

Konsumenci

 

Należy prawidłowo zorganizować pracę systemu.  
1. Gdy są wolne miejsca w buforze producent może tam umieścić swój element. 

Gdy w buforze brak miejsca na elementy producent musi czekać. Gdy wolne 
miejsca się pojawią producent zostanie odblokowany. 

2.  Gdy w buforze są jakieś elementy konsument je pobiera. Gdy brak elementów w 

buforze konsument musi czekać. Gdy jakiś element się pojawi, konsument  
zostanie odblokowany. 

 
Bufor zorganizowany może być na różnych zasadach.  
1.  Kolejka FIFO (bufor cykliczny). 
2.  Kolejka LIFO (stos). 
 
Umieszczanie / pobieranie elementu z bufora jest sekcją krytyczną.  
 
 
 

background image

10.3  Problem czytelników i pisarzy 
W systemie istnieją dwie grupy procesów – czytelnicy i pisarze. Czytanie może się 
odbywać współbieżnie z innymi procesami natomiast pisanie, w celu zapewnienia 
spójności danych, musi się odbywać na zasadzie wyłączności.  
 

Wielu czytelników

C1

C2

CN

P

Jeden pisarz

Baza danych

 

- Czytelnik 

może wejść do czytelni gdy jest ona pusta lub gdy są tam inni 

czytelnicy 

- Pisarz 

może wejść do czytelni gdy jest ona pusta 

 
 
Problem jest uogólnieniem problemu dostępu wielu procesów do bazy danych. 
 
 

10.4 Problem 

śpiącego fryzjera (sleeping barber problem) 

 

W zakładzie fryzjerskim znajduje się poczekalnia na N miejsc oraz fotel fryzjerski.  
Gdy przychodzi klient to sprawdza czy są wolne miejsca w poczekalni. Jeżeli tak to 
zajmuje miejsce, jeżeli nie to przyjdzie on później. Gdy nikogo nie ma i fryzjer śpi to 
budzi go. 
 
Z kolei fryzjer patrzy czy są jacyś klienci. Gdy tak to bierze na fotel jednego z 
czekających. Gdy nie to zasypia. 
 

background image

 

10.5 Rozwiązanie problemu producenta i konsumenta za pomocą 
komunikatów  
 
Struktura rozwiązania: 

1. Zarządzanie buforami leży w gestii procesu administratora 
2.  Procesy producentów i konsumentów komunikują się z procesem 

administratora. 

 

Bufor

P1

P2

Pk

Producenci

K1

K2

Kl

Konsumenci

Admini-

strator

Komunikaty

Komunikaty

 

 
Postać komunikatu 
 

#define PROD 1

// Producent

#define KONS 2

// Konsument

#define SERV_NAME "/pserver"

// Nazwa administratora

typedef struct

{

int

type;

/* typ procesu

*/

int

pnr ;

/* numer procesu

*/

pid_t pid;

/* pid procesu

*/

char text[SIZE]; /* tekst komunikatu */

} msg_type;

Proces producenta i konsumenta

 

 

main(int

argc,char *argv[])

{

// Lokalizacja administratora
spid = qnx_name_locate(0,SERV_NAME,sizeof(msg),NULL);

for(i=0;i<10;i++) {

msg.type = PROD;
sprintf(msg.text," %s %d - %d",typ_str,nr,i);
res = Send(spid,&msg,&rmsg,sizeof(msg),sizeof(msg));
....

}
exit(0);

}

 

background image

Administrator zasobu 

 

Problemy: 

1. Jak wstrzymać procesy producentów gdy brak miejsca w buforze 
2. Jak wstrzymać procesy konsumentów gdy brak elementów w buforze 

 
Rozwiązanie: 

1.  Procesom które mają być wstrzymane nie udziela się odpowiedzi Reply. 
2. Aby można było tak wstrzymany proces potem odblokować należy 

przechować jego pid. 

 
 
Struktury danych 
 

#define MAX_PROC 8

// Maks. liczba oczekujacych procesow

#define BSIZE

2

// Dlugosc bufora komunikatów

 

// Definicja bufora komunikatow ----
char buffer[BSIZE+1][SIZE]; // Bufor
int bptr;

// Indeks

// Bufor zablokowanych procesow producenta
msg_type prod_buf[MAX_PROC];

// Bufor

int prod_ptr;

// Liczba czek. producentów

// Bufor zablokowanych procesow konsumenta
msg_type kons_buf[MAX_PROC];

// Bufor

int kons_ptr;

// Liczba czek. konsumentów

 
 

background image

 

Proces administratora buforów 

 

main () {

// Rejestracja nazwy administratora
id = qnx_name_attach(0,SERV_NAME);
do {

pid = Receive(0,&msg,sizeof(msg));
if(msg.type == PROD) { // Producent

// Testowanie zajetosci bufora
if(bptr < BSIZE ) { // Jest miejsce – kom. do bufora

wstaw_do_bufora(&msg);

// Mozna zwolnic proces producenta

res = Reply(pid,&msg,sizeof(msg));
// Czy jakis konsument czeka na komunikat ?
if(kons_ptr > 0) { // Tak czeka

pobierz_z_bufora(&msg);
// Usu

ń

konsumenta z kolejki

kons_ptr--;
pid = kons_buf[kons_ptr].type;
// Odblokuj konsumenta
res = Reply(pid,&msg,sizeof(msg));

}

} else { // Brak miejsca w buforze

// Zablokuj producenta w kolejce
msg.pid = pid;
prod_buf[prod_ptr] = msg;
prod_ptr++;

}

}
if(msg.type == KONS) { // Konsument

if(bptr > 0) { // Jest komunikat - mozna pobrac

....
// Czy jakis prod. czeka na miejsce w buforze ?
if(prod_ptr > 0) { // Tak czeka

// Zwalniamy producenta
....

}

} else {

// Brak elementów w buforze

// Zablokuj konsumenta w kolejce

}

} while(1);

}

Szkic działania administratora buforów 

background image

 

10.6 Semafory 
Semafor jest obiektem abstrakcyjnym służącym do kontrolowania dostępu do 
ograniczonego zasobu. Semafory są  szczególnie przydatne w środowisku gdzie 
wiele procesów lub wątków komunikuje się przez wspólną pamięć. 
 
 
 
Definicja semafora. 
Semafor S jest obiektem abstrakcyjnym z którym związany jest licznik L zasobu 
przyjmujący wartości nieujemne. Na semaforze zdefiniowane są  atomowe operacje 
sem_init, sem_wait i sem_post. Podano je w poniższej tabeli. 
 
 
Operacja Oznaczenie 

Opis 

Inicjacja 
semafora S 

sem_init(S,N)  Ustawienie licznika  semafora S na początkową 

wartość N  . 

Pobranie 
jednostki 
zasobu 

sem_wait(S)   Gdy licznik L semafora S jest dodatni  (L > 0) zmniejsz 

go o 1 (L = L – 1). 
Gdy licznik L semafora S jest równy zero (L= 0) 
zablokuj proces bieżący. 

Zwrot jednostki 
zasobu 

sem_post(S) 

Gdy istnieje jakiś proces oczekujący na semaforze S 
to odblokuj jeden z czekających procesów. Gdy brak 
procesów oczekujących na semaforze S zwiększ jego 
licznik L o 1  (L=L+1). 

Definicja operacji wykonywanych na semaforze 
Uwaga!  
1. Semafor nie jest liczbą całkowitą na której można wykonywać operacje 
arytmetyczne . 
2. Operacje na semaforach są operacjami atomowymi.  
 
 

sem_wait(S) {

if(

Licznik L sem. S dodatni

){

Zmniejsz licznik sem. (L=L-1)

} else {

Zawie

ś

proces bie

żą

cy

}

}

sem_post (S) {

if(

Istnieje proc. czekający na zasób

) {

Odblokuj jeden z tych procesów

} else {

Zwi

ę

ksz licznik sem. (L=L+1)

}

}

 
Niezmiennik semafora 
Aktualna wartość licznika  L semafora S spełnia następujące warunki: 
 
1. Jest nieujemna czyli: L >= 0 
2. Jego wartość wynosi: L= N - Liczba_operacji(sem_wait) + 
Liczba_operacji(sem_post).  (N jest wartością początkową  licznika).  
 
 

background image

Semafor binarny 
W semaforze binarnym wartość licznika przyjmuje tylko dwie wartości: 0 i 1.  
 
Rodzaje semaforów 
Wyróżniamy następujące rodzaje semaforów: 
 
1.  Semafor ze zbiorem procesów oczekujących (ang. Blocked- set Semaphore) – 
Nie jest określone który z oczekujących procesów  ma być wznowiony. 
 
2.  Semafor z kolejką procesów oczekujących (ang. Blocked- queue Semaphore) – 
Procesy oczekujące na semaforze umieszczone są w kolejce FIFO. 
 
Uwaga!  
Pierwszy typ semafora nie zapewnia spełnienia warunku zagłodzenia. 

background image

10.7   Zastosowania semaforów 

10.7.1  Implementacja wzajemnego wykluczania 

semaphore S;
sem_init(S,1);

// Deklaracja semafora
// Inicjacja semafora

Proces1 {

do {

Sekcja_lokalna;
sem_wait(S);
Sekcja_krytyczna;
sem_post(S);

} while(1);

}

Proces2 {

do {

Sekcja_lokalna;
sem_wait(S);
Sekcja_krytyczna;
sem_post(S);

} while(1);

}

Przykład zastosowania semafora do ochrony sekcji krytycznej 
 

blokada

sem_wait(s)

użycie zasobu

sem_post(s)

sem_wait(s)

użycie zasobu

sem_post(s)

odblokowanie

P1

P2

L=1

L=0

L=0

L=1

 

 
Przebieg operacji blokowania 

Wait(S)

Wait(S)

Post(S)

Post(S)

Sekcja

lokalna

Sekcja

lokalna

Sekcja

krytyczna

Sekcja

krytyczna

Proces P1

Proces P2

 

Rys. 1   Implementacja wzajemnego wykluczania za pomocą semafora – Ilustracja za 
pomocą sieci Petriego 

background image

 
 
 

10.7.2  Rozwiązanie problemu producenta – konsumenta 

#define BufSize 8
RecType Buffer[BufSize];
semaphore Mutex;
semaphore Puste;
semaphore Pelne;
int

count;

// Bufor ma 8 elementów
// Bufor na elementy
// Ochrona bufora
// Wolne bufory
// Zajete bufory
// Wska

ź

nik bufora

// Kod w

ą

tku producenta ---------

producent(void) {

RecType x;
do {

...

produkcja rekordu x;
// Czekaj na wolny bufor
sem_wait(Puste);
sem_wait(Mutex);

// Umie

ść

element x w buforze

Buffer[count] = x;
count++;

sem_post(Mutex);
// Pojawił si

ę

nowy element

sem_post(Pelne);

} while(1);

}

// Kod w

ą

tku konsumenta -------

konsument(void) {

RecType x;
do {

// Czekaj na element
sem_wait(Pelne);
sem_wait(Mutex);
// Pobierz element x z bufora

x = Buffer[count];
count--;

sem_post(Mutex);
// Zwolnij miejsce w buforze
sem_post(Puste);
konsumpcja rekordu x;

...

} while(1);

}

main(void) {

count = 0;
sem_init(Puste,BufSize);
sem_init(Pelne,0);
sem_init(Mutex,1);
StartThread(producent,..);
..
StartThread(konsument,..);

..

}

// Inicjacja semafora Puste
// Inicjacja semafora Pelne
// Inicjacja semafora Mutex
// Start K w

ą

tków producenta

// Start L w

ą

tków konsumenta

Rozwiązanie problemu producenta – konsumenta za pomocą semaforów. 

background image

10.8 Rozwiązanie problem czytelników i pisarzy za pomocą semaforów 
 
Rozwiązanie z możliwością zagłodzenia pisarzy 

- Czytelnik 

może wejść do czytelni gdy jest ona pusta lub gdy są tam inni 

czytelnicy 

- Pisarz 

może wejść do czytelni gdy jest ona pusta 

Może się tak zdarzyć że zawsze jakiś czytelnik jest w czytelni co doprowadzi do 
zagłodzenia pisarzy. 
 

semaphore mutex ;

//

Kontrola dostępu do reader_count 

semaphore db;

//

Kontrola dostępu do czytelni

int reader_count;

//

Liczba czytelników w czytelni

Reader(){

while (TRUE) {

sem_wait(mutex);

//

Blokada dostępu do  reader_count 

reader_count = reader_count + 1;

//

Pierwszy czytelnik blokuje dostęp do czytelni  pisarzom 

if (reader_count == 1)

sem_wait(db);

//

Zwolnienie dostępu do reader_count 

sem_post(mutex);

read_db();

//

Czytelnik czyta

sem_wait(mutex);

//

Blokada dostępu do reader_count 

reader_count = reader_count - 1;

//

Gdy nie ma innych czytelników to wpuszczamy pisarzy

if (reader_count == 0)

Sem_post(db);

Sem_post(mutex); //

Zwolnienie dostępu do reader_count 

}

Writer() {

while (TRUE) {

create_data();

//

Pisarz zastanawia się 

sem_wait(db);

//

Pisarz czeka na dostęp do czytelni 

write_db();

//

Pisarz w czytelni - pisze 

sem_post(db);

//

  Pisarz zwalnia dostęp do czytelni

}

}

main()

{

sem_init(mutex,1);
sem_init(db,1);
....

}

Problem czytelników i pisarzy – rozwiązanie za pomocą semaforów 

background image

Rozwiązanie z możliwością zagłodzenia czytelników 

-  Czytelnik musi czekać gdy są w czytelni lub czekają jacykolwiek pisarze 

Rozwiązanie poprawne 

- Wpuszczać na przemian czytelników i pisarzy 
-  Gdy wchodzi jeden z czytelników, to wpuszcza on wszystkich czekających 

czytelników 

 
Rozwiązanie poprawne nie dopuszcza do zagłodzenia czy to czytelników czy też 
pisarzy. 

#define PLACES 8

//

Liczba wolnych miejsc w czytelni 

semaphore wolne ;

//

Liczba wolnych miejsc w czytelni 

semaphore wr;

//

Kontrola dostępu do czytelni

Reader()
{

while (TRUE) {

sem_wait(wolne);

//

Czekanie na miejsca w czytelni 

read_db();

//

Czytelnik w czytelni - czyta

sem_post(wolne);

//

Zwolnienie miejsca w czytelni 

}

Writer() {

while (TRUE) {

create_data();

//

Pisarz zastanawia się 

sem_wait(wr);

//

Zablokowanie dostępu dla pisarzy 

  

//

Wypieranie czytelników z czytelni   

for(j=1;j<=places;j++)

 

 

sem_wait(wolne);

 

write_db();

//

Pisarz w czytelni – pisze 

           

//

 Wpuszczenie czytelników

for(j=1;j<= PLACES;j++)

 

 

sem_post(wolne);

sem_post(wr);

//

  Odblokowanie pisarzy

}

}

main()

{

sem_init(wolne,PLACES);
sem_init(wr,1);
....

}

Rozwiązanie z ograniczoną liczbą miejsc w czytelni 
 
 

background image

10.9 Rozwiązanie problemy śpiącego fryzjera za pomocą semaforów. 
 
 

#define MIEJSC 8

//

Liczba wolnych miejsc w poczekalni 

semaphore klient ;

 

semaphore strzyge;

 

semaphore fryzjer;

int czekaj

ą

cy;

//

Liczba czekających klientów

void klient(int numer) {

while(1) {

sem_wait(mutex);
if(czekajacy < MIEJSC) {

czekajacy++;
sem_post(klient);
sem_post(mutex);
sem_wait(fryzjer);
sem_post(strzyge);

} else

sem_post(mutex);

}

}

void fryzjer(void) {

while(1) {

sem_wait(mutex);

sem_wait(klient);
sem_wait(mutex);
czekajacy--;
sem_post(fryzjer);
sem_post(mutex);
sem_wait(strzyge);

}

}

main(void) {

sem_init(klient,0);
sem_init(fryzjer,0);
sem_init(strzyge,0);
sem_init(mutex,1);

}

 
 

background image

wait(klient)

K1

L=1

Fryzjer

post(klient)

wait(fryzjer)

wait(strzyge)

post(fryzjer)

post(strzyge)

wait(fryzjer)

strzyżenie K1

post(klient)

wait(fryzjer)

K2

post(fryzjer)

wait(strzyge)

post(strzyge)

strzyżenie K2

wait(fryzjer)

 

Rys. 2 Problem śpiącego fryzjera 

background image

 

10.10  Przykład implementacji semaforów 
Kolejka Q będzie wskaźnikiem na deskryptor procesu. Oznaczmy ten typ jako: 

typedef Queue *DeskryptorProcesu

 

Do implementacji semafora wystarczą dwie operacje: 
1. 

Suspend(Queue Q)

   –    Zawieszenie procesu bieżącego w kolejce Q  

2. 

Resume(Queue Q)

     –   Odblokowanie pierwszego procesu z kolejki Q. 

 
 Typ 

Semaphore

 będzie zdefiniowany jako wskaźnik na strukturę zawierającą: 

-  Licznik L semafora. 
- Kolejkę 

waiting

 procesów zablokowanych na danym semaforze. 

 

typedef

structure {

int L;

//

Licznik semafora 

Queue waiting;

//

Kolejka proc. oczek na semaforze

} SigRec;

typedef *SigRec Semaphore; // Semafor

Inicjacja semafora 
Operacja inicjacji semafora S - sem_init(S,N): 
  
1. Alokacja pamięci na strukturę semafora. 
2. Inicjacja pól struktury *S. 
3. Inicjacja licznika L semafora na wartość początkową N. 
 

void sem_init(Semaphore S, int N)
{

S = malloc(sizeof(SigRec));

// Alokacja pamięci

S->L = N;

// Inicjacja licznika na N

S->Sem_waiting = NULL;

// Inicjacja kolejki  

}

S

L

Waiting

 

 Rys. 3 Semafor jest wskaźnikiem na strukturę 

background image

 
Procedura sem_wait 
Kroki procedury sem_wait(S)podane są poniżej: 
 
1. Zablokować przerwania. 
2.  Zmniejsz licznik L semafora S o 1. 
3.  Gdy licznik L < 0 to: 
- usunąć proces bieżący z kolejki procesów gotowych 
- umieścić deskryptor usuniętego procesu w kolejce S->Sem_waiting semafora S 
- przekazać sterowanie do systemu operacyjnego  
4. Odblokować przerwania. 

 

Operacja musi sem_wait być atomowa. Zablokowanie przerwań jest metodą ochrony 
sekcji krytycznej. Pseudokod procedury podano poniżej. 

void sem_wait(Semaphore S)
{

Zablokuj_przerwania;
(S->L)--;
if(S->L < 0) { // Czekaj

// Zawie

ś

proces bie

żą

cy w kolejce semafora

Suspend(S->waiting);

}
Odblokuj_przerwania;

}

Przykład 1 Pseudokod procedury semaforowej sem_wait(S) 
 

Ready

Next

PD

Next

PD

NIL

PD

Kolejka procesów gotowych

Proces bieżący

S

Next

PD

Next

PD

NIL

PD

Kolejka procesów czekających na zasób

Semafor

L

Waiting

Suspend(S)

Wskaźnik na

kolejkę

procesów
gotowych

PD - pola

deskryptora

procesu

Rys. 4  Implementacja operacji semaforowej sem_wait(S) 
 
 

background image

 
Procedura sem_post 
Kroki procedury sem_post(S)podane są poniżej: 
 
1. Zablokować przerwania. 
2. Zwiększ licznik L semafora S o 1. 
3.  Gdy licznik L <= 0 to: 
 - Usunąć pierwszy proces  z kolejki semafora 
 - Umieścić deskryptor usuniętego procesu w kolejce procesów gotowych. 
 - Przekazać sterowanie do procedury szeregującej systemu operacyjnego  
4. Odblokować przerwania. 

 

Operacja musi sem_post być atomowa.  Pseudokod procedury podano poniżej. 

void sem_post(Semaphore S)
{

Zablokuj_przerwania;
(S->L)++;

if(S->L <= 0) {

// Są procesy oczekujące na semaforze

// Odblokuj jeden z procesów czekających w kolejce semafora  

Resume(S->waiting);

}
Odblokuj_przerwania;

}

Przykład 2 Pseudokod procedury semaforowej sem_post(S) 
 
 
 

Ready

Next

PD

Next

PD

NIL

PD

Kolejka procesów gotowych

Proces bieżący

S

Next

PD

Next

PD

NIL

PD

Kolejka procesów czekających na zasób

Semafor

L

Waiting

Wskaźnik na

kolejkę

procesów
gotowych

Resume(S)

Rys. 5  Implementacja operacji semaforowej sem_post(S) 

background image