Operációs Rendszerek 9.B: Versenyhelyzetek

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 9

Operációs rendszerek 9.

B
Versenyhelyzetek
Konkurens folyamatok közötti kommunikáció: (saját szavakkal ezt tudom elképzelni)
Olyan esetben beszélünk konkurens folyamatokról, amikor ezen folyamatok megosztott
memóriáért versenyeznek. Mivel fontos a megfelelő sorrend betartása (ha egy folyamat egy
másik által előállított eredményre vár, akkor csak azután próbálja meg kiolvasni, mikor a
másik már előállította), ezért elkerülhetetlen a kommunikáció. Ez megvalósítható egy
változón keresztül, de üzenet küldésével is.

Versenyhelyzet:
Ha kettő vagy több processzus olvas vagy ír megosztott adatokat és a végeredmény attól
függ ki és mikor fut. Versenyhelyzetre a megoldás a megelőzés kölcsönös kizárással: egy
módszer, amely biztosítja hogy ha egy processzus használ valamely megosztott változót,
fájlt, akkor a többi processzus „tartózkodjon” ettől a tevékenységtől.

Kritikus szekció (CS) fogalma:


Az a folyamaton belüli kódrész, mely hozzáfér a megosztott memóriához.

Kritikus szekció sikeres megvalósításának a feltételei:


1. Ne legyen két processzus egyszerre a saját kritikus szekciójában (kölcsönös kizárás)
2. Semmilyen előfeltétel ne legyen a sebességekről vagy a CPU-k számáról
3. Egyetlen, a kritikus szekcióján kívül futó processzus sem blokkolhat más
processzusokat (haladás)
4. Egyetlen processzusnak se kelljen örökké arra várni, hogy belépjen a kritikus
szekciójába (korlátosság)

Konkurens programozás alapjai


Megszakítások tiltása:
A legegyszerűbb megoldás az, hogy minden folyamat letiltja az összes megszakítást mikor
belép a kritikus területre és engedélyezi, épp mielőtt befejezi a futását. Ebben az esetben a
processzor nem fog átváltani más processzre, így megvizsgálhatja és módosíthatja a
megosztott memóriát anélkül, hogy más folyamat beavatkozásától kéne tartania.
Ez a megközelítés több szempontból sem jó:
 ha egy felhasználói folyamat nem engedélyezi újra a megszakításokat, akkor a
rendszer működésképtelenné válik
 több processzoros rendszerben a megszakítás csak arra a CPU-ra vonatkozik, amelyik
végrehajtotta a tiltó utasítást, a többi CPU továbbra is hozzáférhet a megosztott
memóriához
Zárolásváltozó:
Kezdeti értéke 0. Mielőtt egy folyamat belépne a kritikus szekcióba, megvizsgálja a változó
értékét. Ha 0, akkor 1-re állítja és belép a kritikus szekcióba, ellenkező esetben tevékenyen
(folyamatosan tesztelünk egy változót egy bizonyos érték megjelenéséig) várakozik, amíg a
változó nem lesz 0.
while(TRUE) {
while(lock == 1);
lock = 1;
critical();
lock = 0;
non_critical();
}
Ez sem megfelelő megoldás, mivel az ütemezés miatt előfordulhat, hogy két processz kerül a
kritikus szekciójába. Az egyik látja, hogy a változó 0, de mielőtt beállítaná, egy másik kerül
ütemezésre, amely előbb állítja át és lép be a CS-be, majd ahogy az első processz fut újra,
szintén átállítja 1-re – már korábban megvizsgálta, hogy 0 a változó – és belép. Az újbóli
vizsgálat sem jelent megoldást, mert azután is bekövetkezhet ez az eset.

Szigorú alternáció:
Process0 Process1
while(TRUE) { while(TRUE) {
while(turn != 0); while(turn != 1);
critical(); critical();
turn = 1; turn = 0;
non_critical(); non_critical();
} }
P0 belép a kritikus szekcióba, végrehajtja – eközben a P1 tevékenyen várakozik -, majd
átállítja a turn-t 1-re, így a P1 is beléphet a kritikus szekcióba, végrehajtja, majd a turn-t 0-ra
állítja. Ezen a pontot mindkét folyamat a nem kritikus szekciójában van.
Azonban ha P1 befejezi és újrakezdi a ciklust, akkor be tud lépni a CS-be. Gyorsan
végrehajtja, turn-t 1-re állítja, majd a nem kritikus szekciót is egyből befejezi, a ciklus
újraindul, viszont nem tud belépni a CS-be, mert még a másik folyamatra vár – ami még
mindig a nem kritikus szekcióban van, hogy az belépjen a CS-be.
Tehát ha a két folyamat nem egyforma sebességgel halad, akkor előfordulhat, hogy feltartják
egymást, ami viszont megszegi a 3. feltételt. Emellett a folyamatok a tevékeny várakozás
közben foglalják a CPU idejét, továbbá 2 folyamatnál többre alkalmazva még rosszabb lenne
a helyzet.
Peterson módszere:
#define FALSE 0
#define TRUE 1
#define N 2 // folyamatok száma
int turn; // melyik processzus fut
int interested[N]; // ki érdekelt

void enter_region(int process) {


int other;
other = 1 - process;
interested[process] = TRUE;
turn = process;
while(turn == process && interested[other] == TRUE) ;
}
void leave_region(int process) {
interested[process] = FALSE;
}
Ebben az esetben nem tudnak egyszerre belépni a kritikus szekcióba, mert csak az utolsó írás
(turn változóba) fog számítani, így ha egyszerre is próbálkoznak, a while-ba akkor is belép az
egyik. Ennek a módszernek a hátránya, hogy a várakozó folyamat szintén CPU időt használ.

Test and Set Lock utasítás:


Több számítógépnek, de elsősorban többprocesszorosnak tervezetteknek van egy TSL
RX,LOCK utasítása, amely beolvassa a LOCK memóriaszó tartalmát az RX regiszterbe, és
ezután egy nem nulla értéket ír erre a memóriacímre. A szó kiolvasása és a tárolási művelet
garantáltan nem választható szét, az utasítás befejezéséig más processzor nem érheti el a
memóriaszót.
Amikor a LOCK 0, bármelyik folyamat beállíthatja 1-re a TSL utasítással, ezután olvashatja
vagy írhatja a megosztott memóriát, majd végül visszaállítja a LOCK-ot 0-ra egy MOVE
utasítással.
Szintén az a hibája, hogy a processzek tevékenyen várakoznak, hogy belépjenek a kritikus
szekciójukba.

Prioritás inverzió:
Van két processz, processz H magas prioritással, processz L alacsonnyal. Az ütemezési
szabályok szerint a magasabb prioritású processz mindig megkapja a CPU-t, ha igényli. Egy
bizonyos pillanatban legyen L a kritikus szakaszban, ezalatt H váljon futásra kész állapotúvá, a
busy waiting ciklusa előtt kevéssel. Megkapva H a CPU-t, tesztel és vár ciklusban, és mivel
magasabb a prioritása, nem engedi szóhoz jutni L-t, hogy az kijusson a kritikus szakaszából,
felszabadítva H-t a várakozó ciklusból. Nyilvánvaló holtponthelyzet alakult ki.
Gyártó-fogyasztó probléma:

1. A jelzett ponton task switch (a tároló vizsgálata után, a sleep előtt)


2. consumer fut egyszer, majd mivel azt gondolhatja a count=N-1-nél, hogy a producer
aludhat, küld egy wakeup-ot, ez viszont elveszik, mivel a producer nem aludt
3. a consumer elfogyasztja az összes elemet, majd sleep-el
4. a producer visszakapja a vezérlést és sleep-el
5. kialakul a DEAD LOCK – avagy holtpont

Szemaforok:
Egész változókban számoljuk az ébresztések számát (semaphore). Elemi műveletekből áll,
mely garantálja a versenyhelyzetek elkerülését, ha egy szemafor művelet elkezdődik, más
process nem tudja elérni a szemafort addig, míg be nem fejeződik vagy nem blokkolódik.
Két művelet:
 down (sleep megfelelője): megvizsgálja hogy a szemafor értéke nagyobb-e mint
nulla?
o Ha igen, akkor csökkenti az értéket és folytatja
o Ha nulla, akkor a processzust elaltatja mielőtt befejeződne
 up (wakeup megfelelője): a szemafor értékét növeli
o Ha egy vagy több processzus aludna ezen a szemaforon, akkor egyet kiválaszt
és megengedi hogy a down befejeződjön. Így olyan szemaforon végrehajtva az
up-ot, amelyen a processzek aludtak, a szemafor még mindig 0 lesz, de eggyel
kevesebb processz fog aludni.
Az olyan szemaforokat, amelynek kezdőértéke 1, és arra szolgálnak, hogy biztosítsák, hogy
kettő vagy több folyamat közül egyidőben csak egy léphessen kritikus szekciójába, bináris
szemafornak nevezzük. Ha minden folyamat pontosan azelőtt hajt végre egy down-t, mielőtt
belép a kritikus szekciójába, és pontosan utána egy up-ot, miután kilép, akkor a kölcsönös
kizárás is biztosítva van.
Fontos, hogy azonos számú down és up kell, emellett megfelelő sorrendben kell kiadni őket.
typedef int semaphore;
semaphore mutex=1, empty=N, full=0;

void producer(void) void consumer(void)


{ {
while(true) while(true)
{ {
i=produce_item(); down(&full);
down(&empty); down(&mutex)
down(&mutex); i=remove_item();
enter_item(i); up(&mutex);
up(&mutex); up(&empty);
up(&full); consume_item(i);
} }
} }

Mutex (Mutual Exclusion = Kölcsönös kizárás):


Két állapot, ezért egy bit elegendő a reprezentálásához:
 nem zárolt (0) – egy processz a mutex_lock eljárással zárolhatja
 zárolt (bármi más) – a hívók blokkolódnak, amíg a kritikus szekcióban lévő processz
nem végez, és meg nem hívja a mutex_unlock eljárást. Ha több folyamat is
blokkolódva van a mutexen, akkor az egyik véletlenszerűen szerzi meg a zárolást.

Monitor:
A monitor magasabb szintű szinkronizációs mechanizmus: eljárások, változók, adatstruktúrák
speciális formájú gyűjteménye. A proceszek hívhatják a monitor eljárásait, de nem férnek a
monitor belső adatstruktúráihoz (information hiding), továbbá biztosított az is, hogy egy
időben csak egy processz használhat egy monitort. A fordító biztosítja a monitorba való
belépés-re a kölcsönös kizárást (szokásosan egy bináris szemaforral), és így a programozónak
ezzel már nem kell foglakoznia. Ha egy procesz hív egy monitorban lévő eljárást, az első
néhány instrukció ellenőrzi, vajon más processz pillanatnyilag aktív-e a monitorban. Ha igen,
a hívó blokkolódik, míg a másik elhagyja a monitort.
void producer(void) void consumer(void)
{ {
while(true) while(true)
{ {
i = produce_item(); i = remove();
enter(i); consume_item(i);
} }
} }

void enter(item i) void remove()


{ {
CS_enter(mutex); CS_enter(mutex);
if (cnt==N) if (cnt==0)
wait(full); wait(empty);
cnt++; cnt--;
enter_item(i); i = remove_item();
if (cnt==0) if (cnt==N-1)
signal(empty); signal(full);
CS_leave(mutex); CS_leave(mutex);
} }

5 filozófus problémája:
Öt filozófus ül egy asztal körül, mindegyiknek van egy soha ki nem ürülő tányér spagettije és
mindegyik tányér mellett - valójában a tányérok között - van egy villa. Spagettit enni viszont
csak két villával lehet. A filozófusok felváltva esznek vagy gondolkodnak.
Amikor egy filozófus megéhezik, megpróbálja megszerezni a tányérja melletti bal és jobb
villát, bármilyen sorrendben, de egymás után. Ha sikerül két villát szereznie, akkor eszik egy
darabig, majd leteszi a villákat és folytatja a gondolkodást.
Tételezzük fel, hogy mind az öt filozófus egyszerre felveszi a baloldali villáját, ezután egyik
sem vehet jobboldali villát: bekövetkezik a holtpont helyzet.
Úgy módosítunk a programon, hogy miután felvette a filozófus a bal villát, ellenőrzi, vajon
elérhető-e a jobb villa, s ha nem leteszi a bal villát. Ebben az esetben előfordulhat, hogy
minden filozófus egyszerre hajtja végre az algoritmust, tehát folyamatosan fel és leteszik a
villát. Az ilyen helyzetet, amelyben minden processz korlátlan ideig folytatja a futást, de
érdemben nem halad előre, éhezésnek nevezzük.
Egy lehetséges megoldás lehet, hogy a villa felvételétől a villa letételéig megvédjük a
folyamatot egy bináris mutex-szel. Elméletileg megfelelő, azonban a gyakorlatban csak 1
filozófus ehet, pedig 5 villa esetén 2 is ehetne egyszerre.
A megoldás az, hogy nyilvántartja a filozófusok állapotát. Egy filozófus csak akkor ehet, ha
egyik szomszédja sem eszik. Holtpontmentes, tetszőleges számú filozófusra esetén is
megengedi a maximális párhuzamosságot.
#include "prototypes.h";
#define N 5 // number of philosophers
#define LEFT (i-1)%N // number of i's left neighbor
#define RIGHT (i+1)%N // number of i's right neighbor
#define THINKING 0
#define HUNGRY 1
#define EATING 2

typedef int semaphore;


int state[N); // array to keep track of everyone's state
semaphore mutex = 1; // mutual exclusion for critical regions
semaphore s[N]; // one semaphore per philosopher

void philosopher(int i) {
while (TRUE) {
think();
take_forks(i); // acquire two forks or block
eat();
put_forks(i); // put both forks back on table
}
}

void take_forks(int i) {
down(&mutex); // enter critical region
state[i] = HUNGRY;
test(i); // try to acquire 2 forks
up(&mutex); // exit critical region
down(&s[i]); // block if forks were not acquired
}

void put_forks(int i) {
down(&mutex);
state[i] = THINKING;
test(LEFT); // see if left neighbor can now eat
test(RIGHT); // see if right neighbor can now eat
up(&mutex); // exit critical region
}

void test(int i) {
if (state[i]==HUNGRY &&
state[LEFT]!=EATING &&
state[RIGHT]!=EATING) {
state[i] = EATING;
up(&s(i));
}
}
Író-olvasó probléma:
A probléma esetén elfogadható, hogy több processz egyidejűleg olvasson az adatbázisból, de
ha egy process aktualizálja (írja) az adatbázist, akkor azt más processznek nem szabad
elérnie, még az olvasóknak sem.
Ebben a megoldásban az első olvasó, aki hozzáfér az adatbázishoz végrehajt egy down-t a
szemaforon, a következő olvasók csupán egy számlálót növelnek. Ha egy olvasó kilép, akkor
csökkenti a számlálót, az utolsó kilépő egy up-ot hajt végre a szemaforon, lehetővé téve a
blokkolt írónak, hogy belépjen.
Azonban ha állandóan érkeznek új olvasók (átfedés van köztük), akkor az író process örökké
blokkolva lesz. Erre a megoldás az, hogy ha egy író már vár, akkor az olvasókat nem engedjük
be, hanem felfüggesztődnek az író mögött, így az írónak csak azt kell megvárni, hogy az
előtte érkezett olvasók végezzenek.
typedef int semaphore;
semaphore mutex = 1;
semaphore db = 1;
semaphore rc = 0;

void reader(void)
{
while(true)
{
down(&mutex);
rc = rc + 1;
if (rc == 1)
down(&db);
up(&mutex);
read_data_base();
down(&mutex);
rc = rc – 1;
if (rc == 0)
up(&db);
up(&mutex);
use_data_read();
}
}

void writer(void)
{
while(true)
{
think_up_data();
down(&db);
write_data_base();
up(&db);
}
}
Alvó borbély probléma:
Borbélyüzlet, egy vagy több borbély és ugyanannyi borbélyszék van benne. Ezen kívül van N
szék a várakozó vendégeknek. Ha nincs vendég, a borbély alszik a borbélyszékében. Ha jön
egy vendég, felkelt egy borbélyt, az hajat vág. Ha vendég jön, de minden borbélyszék (ezzel
borbély) foglalt, akkor ha van üres várakozószék, akkor leül és várakozik. (Nem feltétlenül
érkezési sorban szolgálják majd ki.) Ha a várakozószékek is foglaltak, akkor azonnal elhagyja
a borbélyüzletet.
#include "prototypes.h"
#define CHAIRS 5
typedef int semaphore;
semaphore customers = 0;
semaphore barbers = 0;
semaphore mutex = 1;
int waiting = 0;

void Barber(void) {
while (TRUE) {
down(customers);// go to sleep if # of customers is 0
down(mutex); // acquire access to 'waiting'
waiting = waiting - 1;// decrement # of waiting customers
up(barbers); // one barber is now ready to cut hair
up(mutex); // release 'waiting'
cut_hair(); // cut hair (outside critical region)
}
}
void Customer(void) {
down(mutex); // enter critical region
if (waiting < CHAIRS) { // if there are free chairs
waiting = waiting + 1; // incr # of waiting customers
up(customers); // wake up barber if necessary
up(mutex); // release access to 'waiting'
down(barbers); // sleep if # of free barbers is 0
get_haircut(); // be seated and be serviced
} else {
up(mutex); // shop is full; do not wait
}
}

You might also like