5.1 Принципи на конкуренцията.
Пример.
Процесите P1 и P2
използват обща "ехо" функция (напр. за отпечатване на екрана на натиснат
клавиш) - без и със защита на функцията. ОС може да прекъсва изпълнението
на функцията след всеки оператор.
char chin, chout;
void echo()
{
getchar(chin);
chout = chin;
putchar(chout);
}
От клавиатурата на P1
се
въвежда 'A', а от клавиатурата на P2 -
'B'. Възможни са следните сценарии за изпълнение
на процесите P1 и P2, които използват функцията
"ехо".
А. Без защита : ОС може да прекъсва изпълнението на функцията
след всеки оператор.
// PROCESS P1
void echo() { getchar(chin); ................ ................ chout = chin; putchar(chout); ................. } |
// PROCESS P2
void echo() { ............... getchar(chin); chout = chin; ............... ............... putchar(chout); } |
value of chin
... ... ... A B B B B |
value of chout
... ... ... ... B B B B |
т.е. P1(getchar)-> P2(getchar)-> P2(=)->
P1(=)-> P1(putchar)-> P2(putchar)->
и
двата процеса извеждат 'B'
-> BAD.
Б. Със защита: ОС не може
да прекъсва изпълнението на функцията.
P1(getchar)-> P2(echo, недостъпна!)->
P1(=)-> P1(putchar)-> P2(=)-> P2(putchar)->
и процесът
P1
извежда 'A', а процесът P2 - 'B'
-> OK.
Извод - управление на достъпа до съвместно използвана
функция!
** Задължения на ОС.
1. Следене на активните прцеси.
2. Отпускане на ресурси за активните процеси:
- процесорно
време;
- памет;
- файлове;
- I/O устройства.
3. Предпазване на данни и ресурси на всеки процес
от неправомерна намеса на друг процес.
4. Резултатът от работата на един процес трябва
да е независим от скоростта на изпълнението му, както и от скоростта на
изпълнение на другите процеси.
** Взаимодействие на процеси.
* Състезание между процесите за ресурси.
Взаимно изключване (ВИ) - mutual exclusion:
критичен
ресурс (КР) и критична секция (КС).
Пример: ресурс принтер.
/* program mutual_exclusion */
const int n; /* number of processes */
void P(int i)
{ while (true)
{ enter_critical(i);
<critical section>
exit_critical(i);
<remainder>
}
}
void main( )
{ parbegin(P(1), P(2), ..., P(n)); }
Какво лошо може да се случи?
Мъртва хватка (МХ) - deadlock.
Дадени са процеси P1 и
P2
и
ресурси R1 и R2. За да завършат, двата
процеса трябва да използват и двата ресурса. Ако процесът P1
заеме ресурс R1 и процес P2 заеме ресурс
R2,
тогава нито един от процесите не може да завърши - P1 чака
да бъде освободен ресурс R2, а P2 чака
да бъде освободен ресурс R1 - МХ.
Гладна смърт (ГС) - starvation.
Дадени са процеси P1,
P2
и P3 и ресурс R. И трите процеса използват
дадения ресурс. Нека P1 и
P2 да се изпълняват
периодично, т.е. заемат ресурса за определено време, след което го освобождават.
При сценарии
P1(R)-> P2(R)-> P1(R)-> P2(R)-> P1(R)->
...
P3 чака да бъде освободен ресурса и това чакане може
да продължи неопределено време - ГС.
* Кооперация между процеси чрез поделяне, съгласуване на данните.
Два процеса, като използват общи променливи a
и
b,
поддържат връзката a = b.
int a=1, b=1;
PROCESS P1
... a = a + 1; b = b + 1; ... |
PROCESS P2
... b = 2 * b; a = 2 * a; ... |
** Изисквания за ВИ:
1. Само един процес е в КС секция относно даден
КР.
2. Процес извън КС може да прекъсва, без това да
влияе на останалите процеси.
3. Всеки процес, чакащ за влизане в КС, трябва да
може да влезе в КС (с крайно време за чакане).
4. Ако няма процес в КС (относно КР), всеки процес,
който иска този КР, трябва да може да влезе в КС незабавно.
5. Няма ограничения за брой процеси, чакащи за КР
и няма ограничения за времето и честотата на използване на този ресурс.
6. Един процес може да остане в КС за крайно време.
5.2 Взаимно изключване: софтуерен подход.
** Алгоритъм на Декер.
Първи опит:
int turn;
/* =0 or =1 */
// PROCESS P0
... while(turn!=0) nothing(); <critical section> turn = 1; ... |
// PROCESS P1
... while(turn!=1) nothing(); <critical section> turn = 0; ... |
Втори опит:
bool flag[2]
= {false, false};
// PROCESS P0
... while(flag[1]) nothing(); flag[0] = true; <critical section> flag[0] = false; ... |
// PROCESS P1
... while(flag[0]) nothing(); flag[1] = true; <critical section> flag[1] = false; ... |
Трети опит:
bool flag[2];
// PROCESS P0
... flag[0] = true; while(flag[1]) nothing(); <critical section> flag[0] = false; ... |
// PROCESS P1
... flag[1] = true; while(flag[0]) nothing(); <critical section> flag[1] = false; ... |
Проблем - възможно e МХ при следния сценарии:
P0(flag[0] = true)-> P1(flag[1] = true)->
P1(while)-> P0(while)-> MX.
** Алгоритъм на Петерсон - решение на задачата за ВИ.
bool flag[2] = {false,false};
int turn;
void P0()
{ while (true) { flag[0] = true; turn = 1; while (flag[1] && turn==1) nothing(); <critical section> flag[0] = false; <remainder> } } |
void P1()
{ while (true) { flag[1] = true; turn = 0; while (flag[0] && turn==0) nothing(); <critical section> flag[1] = false; <remainder> } } |
flag[0] | flag[1] | turn | |
false | false | 0, 1 | Няма интерес за КС. |
false | true | 0, 1 | P0 няма интерес за КС, P1 в КС. |
true | false | 0, 1 | P0 в КС, P1 няма интерес за КС. |
true | true | 0 | P0 в КС, P1 чака за КС. |
true | true | 1 | P1 в КС, P0 чака за КС. |
5.3 Взаимно изключване: хардуера поддръжка.
** Непозволяване на прекъсвания - проблеми при многопроцесорни системи
и неефективно използване на ресурси..
** Специални машинни команди. Функцията testset
не реализирана хардуерно и връща стойност за един такт на процесора, т.е.
не може да се прекъсва.
bool testset(int &i)
{ if (i == 0)
{ i = 1; return true; }
return false;
}
const int
n; /* number of processes */
int bolt;
void P(int
k)
{ while(true)
{
do nothing();
while (!testset(bolt));
<critical section>
bolt = 0;
<remainder>
}
}
void main()
{ parbegin(P(1),P(2),...,P(n));
}
Когато променливата bolt
има стойност 1, има процес в КС и функцията testset
връща
стойност false - т.е. всички
процеси, които имат интерес за КС, чакат. Когато променливата
bolt
има стойност 0, няма процес в КС, функцията testset
връща
стойност false , като стойността
на bolt става 1. Процес, който
излиза от КС, променя стойността на bolt
на 0. Гарантирано е ВИ, няма опасност от МХ.
5.4 Семафори
Основен принцип: два или повече процеса се кооперират
в смисъл на прости сигнали така, че един процес може да бъде спрян на определено
място и да остане в това състояние, докато не получи съответен сигнал.
За да изпрати сигнал чрез семафора s,
процесът трябва да изпълни примитива (процедурата) signal(s).
За да получи сигнал от семафор s,
процесът трябва да изпълни wait(s)
и ако съответният сигнал още не е изпратен, да остане в състояние на чакане
(блокиран).
Дефинирани са 3 действия с променливата семафор:
1. Инициализира се с неотрицателно число.
2. Примитива wait(s)
намалява
стойността на s с 1. Ако стойността
стане отрицателна, процесът (изпълняващ wait)
се блокира.
3. Операцията signal(s)
увеличава
стойността на s с 1. Ако стойността
не е положителна, процесът (блокиран с wait)
се разблокира.
Примитивите wait и
signal
се
предполат неделими, т.е. те не могат да бъдат прекъсвани.
struct semaphore {
int count;
queueType queue;
};
void wait(semaphore s)
{ s.count--; if (s.count < 0) { place_this_process_in_s.queue; block_this_process; } } |
void signal(semaphore s)
{ s.count++; if (s.count <= 0) { remove_a_process_from_s.queue(P); place_the_process_on_ready_list(P); } } |
Пример ???.
Двоични семафори - семафорът има две състояния - отворен
(1) и затворен (0):
struct binary_semaphore {
int value; /* = 0, 1 */
queueType queue;
};
void waitB(binary_semaphore s)
{ if (s.value==1) s.value=0; else { place_this_process_in_s.queue; block_this_process; } } |
void signalB(binary_semaphore s)
{ if (s.queue.is_empty()) s.value=1; else { remove_a_process_from s.queue(P); place_process_on_ready_list(P); } } |
** Взаимно изключване - решение на задачата за ВИ със семафори.
/* program mutual_exclusion */
const int n; /* number of processes */
semaphore s = 1;
void P(int i)
{
while (true)
{
wait(s);
<critical section>
signal(s);
<remainder>
}
}
void main()
{
parbegin(P(1), P(2),..., P(n));
}
Стойността на променливата s.count
има
следния смисъл:
- s.count >= 0 е броят
на процесите, които могат да изпълнят примитива wait(s)без
да бъдат блокирани (и няма изпълнение на signal(s));
- s.count < 0 е броят
на процесите, чакащи в s.queue.
void
producer()
{ while (true) { /*p1*/ produce(); /*p2*/ waitB(s); /*p3*/ append(); /*КС*/ /*p4*/ n++; /*p5*/ if (n==1) signalB(delay); /*p6*/ signalB(s); } } |
void consumer()
{ waitB(delay); while (true) { /*c1*/ waitB(s); /*c2*/ take(); /*КС*/ /*c3*/ n--; /*c4*/ signalB(s); /*c5*/ consume(); /*c6*/ if (n==0) waitB(delay); } } |
|
|
||||
|
|
|
|
||
|
|
|
|
|
p1->p2->p3->p4->p5->p6 |
|
|
|
|
|
|
|
|
|
|
|
c1->c2->c3->c4->c5-> |
|
|
|
|
|
p1->p2->p3->p4-> |
|
|
|
|
|
->c6 |
|
|
|
|
|
c1->c2->c3->c4->c5-> |
|
|
|
|
|
->c6 |
|
|
|
|
|
c1->c2-> !!! |
* Втори опит - решение:
int n;
binary_semaphore s = 1;
binary_semaphore delay = 0;
void producer()
{ while (true) { produce(); waitB(s); append();n++; if (n==1) signalB(delay); signalB(s); } } |
void consumer()
{ int m; /* a local variable */ waitB(delay); while (true) { waitB(s); take(); n--; m = n; signalB(s); consume(); if (m==0) waitB(delay); } } |
** Предимство на читателите.
/* program readers_and_writers */
int readcount;
semaphore x = 1, wsem = 1;
void reader()
{ while (true) { wait(x); readcount++; if (readcount==1) wait(wsem); signal(x); READUNIT(); wait(x); readcount--; if (readcount==0) signal(wsem); signal(x); } } |
void writer()
{ while (true) { wait(wsem); WRITEUNIT(); signal(wsem); } }
|
** Предимство на писателите.
/* program readers_and_writers */
int readcount, writecount;
semaphore x = 1, y = 1, z = 1, wsem =
1, rsem = 1;
void reader()
{ while (true) { wait(z); wait(rsem); wait(x); readcount++; if (readcount==1) wait(wsem); signal(x); signal(rsem); signal(z); READUNIT(); /* {КС} */ wait(x); readcount--; if (readcount==0) signal(wsem); signal(x); } } |
void writer()
{ while (true) { wait(y); writecount++; if (writecount==1) wait(rsem); signal(y); wait(wsem); WRITEUNIT(); /* КС */ signal(wsem); wait(y); writecount--; if (writecount==0) signal(rsem); signal(y); } } |
Състояния на системата: | Състояния на семафорите wsem, rsem и z: |
Само читатели в системата |
- wsem - 1
- няма опашки |
Само писатели в системата |
- rsem - 1
- опашка от писатели на wsem |
Много читатели четат, идва писател |
- wsem е затворен (от първия читател); rsem е отворен;
- писателят минава и затваря rsem; - писателят чака на wsem; - следващият идващ читател затваря z и чака на rsem; - всички други идващи читатели чакат на z; - последният читател, завършил четенето, отваря wsem; |
Последен писател пише, чакат читатели |
- wsem е затворен (от пишещия писател);
- опашките са както в предишния случай; - писателят свършва и отваря wsem; - писателят отваря rsem; - читателят, чакащ на rsem минава, отваря z и чете; |