作業系統 第二章 程序的描述與控制(下半部分:演算法理論篇))
阿新 • • 發佈:2021-01-04
同步機制
硬體同步機制
- 關中斷
在進入鎖測試之前關閉中斷,直到完成鎖測試並上鎖才能開啟中斷。 - 利用Text-and-Set指令實現程序互斥
lock=FALSE時候資源空閒,lock=TRUE的時候資源正在被使用。
/* TS指令的一般性描述 */ boolean TS(boolean *lock) { boolean old; old = *lock; *lock = TRUE; return old; } /* 利用TS指令實現互斥 */ do{ … while TS(&lock); // 使用的時候就一直在這個true的迴圈裡走 critical section; // lock = FALSE; // 當操作開啟鎖的時候, remainder section; } while(TRUE)
- 使用Swap指令實現程序互斥
/* swap指令的一般性描述 */ void swap(boolean *a, boolean *b) // 交換 { boolean temp; temp = *a; *a = *b; *b = temp; } /* 利用swap指令實現互斥 */ Do { key = TRUE // 讓key=True do { swap(&lock, &key); // 交換lock和key 也就是讓lock=true } while (key != FALSE); // 如果key不是false,證明交換前lock不是false,也證明當初是鎖著的,那就一直迴圈鎖死。什麼時候解鎖了再說。 critical section; lock = FALSE; remainder section; } while(TRUE)
軟體方式
- 訊號量機制
- 整型訊號量
- 記錄型訊號量
- AND型訊號量
- 訊號量集
整型訊號量
- wait(s)和signal(s)兩個操作
// S表示資源數目的整型量S
/*wait(s)操作*/
wait(S){
while (S≤0){ /*do no-op*/ };
S = S – 1; // 表明有一個需要資源
}
/* signal(s)操作*/
signal(S){
S++; //讓訊號量 +1 表明資源數增加了一個
}
// 這種方式無法做到讓權等待
記錄型訊號量
// 引入程序阻塞機制,解決忙等問題 // 增加了對阻塞程序的記錄 /*記錄型的資料結構*/ typedef struct{ int value; // 資源數目 struct process_control_block *list; // 等待程序連結串列 } /*wait(s) 請求資源*/ wait(semaphore *S){ S->value--; // 資源數量 -1 表明當前需求了一個訊號量 if(S->value < 0){ block(S->list); // 如果資源數量少於0,那就把請求加入阻塞佇列 } } /*signal操作 釋放資源*/ signal(semaphore *S){ S->value++; // 釋放一個資源 if(S->value <= 0){ wakeup(S->list); // 如果是負數證明還有阻塞佇列,開啟一下 } }
記錄型訊號量特點
-
S.value的含義
S.value = 1,這時候wait(S)一下就進入了阻塞狀態,保證了只允許一個資源訪問,為互斥狀態。
且當S大於0,代表資源充足,S為負數,代表資源需求量。S為0,代表阻塞。 -
遵循讓權等待
-
資源訪問過程是原語,不存在執行一半這種情況。
AND型訊號量
- 基本思想:將多次對多個訊號量申請修改為一次,用一個語句完成,要麼一次獲得所有,要麼一個也得不到
- 目的:防止資源不同導致的死鎖。
訊號量集
- 一次需要N個某類資源,需要進行N次wait(S)操作,這樣效果低下,且容易出現數量問題死鎖,
- Swait(S,d,d)對訊號量S,允許申請d個,當自遠少於d個不予分配
- Swait(S,1,1),等價於一般的記錄型訊號量或互斥訊號量
- Swait(S,1,0)。低於0個不予分配,只能申請1個,相當於可控開關,可以做if語句用
訊號量的應用
利用訊號量實現程序的互斥
// 設有程式A和B,都需要同一個臨界區
semaphore mutex = 1; // 互斥訊號量,這裡表示的是mutex->value = 1;
Pa(){
while(1){
wait(mutex){};
// Pa in 臨界區
signal(mutex);
// Pa 其他操作
}
}
Pb(){
while(1){
wait(mutex){};
// Pb in 臨界區
signal(mutex);
// Pb 其他操作
}
}
利用訊號量實現前驅關係
p1(){
S1;
signal(s); // 釋放訊號量
}
p2(){
wait(s); // 必須等到p1釋放訊號量才能執行p2d的剩餘操作
S2;
}
void main(){
semaphore s;
s.value = 0;
cobegin() // 併發執行開始標記
p1();
p2();
coend
}
// 這裡可以根據前趨圖實現更加複雜的前驅關係,
p1(){S1;signal(a);signal(b);}
p2(){wait(a);S2;signal(c);signal(d);}
p3(){wait(c);S3;signal(e);}
p4(){wait(d);S4;signal(f);}
p5(){wait(b);S5;signal(g);}
p6(){wait(e);wait(f);wait(g);S6;}
main() {
semaphore a,b,c,d,e,f,g;
a.value=b.value=c.value=d.value=e.value=0;
f.value=g.value=0;
cobegin
p1();p2();p3();p4();p5();p6();
coend
}
經典的程序同步機制
生產者-消費者問題
- 描述:這是一個生產者生產訊息後,消費者消費的合作關係
- 消費者消費的空白緩衝塊由生產者生產
- 程序在佇列操作上是互斥的。
- 緩衝池是有大小限制的,也就是最多就生產X個
- 確定訊號量
- full:表示緩衝池中滿緩衝塊數量
- empty:表示緩衝池中空緩衝塊數量
- mutex,表示對緩衝池的互斥訪問
- 因此可以確定,緩衝區數量必然等於full+empty,mutex=1來執行互斥訪問。
int in=0,out=0;
item buffer[n];
semaphore mutex=1,empty=n,full=0;
// 生產者
void producer(){
do{
produce an item nextp;
.....
wait(empty); // 空白塊加工
wait(mutex); // 訪問臨界區
buffer[in] = nextp; // 處理臨界區
in = (in + 1)%n;
signal(mutex); // 結束訪問
signal(full); // 滿區塊 增加
}while(True);
}
void consumer(){
do {
wait(full); // 消費滿內容塊
wait(mutex); // 互斥訊號量
nextc=buffer[out]; // 緩衝塊處理 臨界區
out= (out+1) % n;
signal(mutex); //
signal(empty); // 增加空白塊
consume the item in nextc;
…
}while(TRUE)
}
void main(){
cobegin
producer();
consumer();
coend;
}
- P操作的順序很重要,不然會產生死鎖
利用AND訊號量解決生產者-消費者問題
int in=0,out=0;
item buffer[n];
semaphore mutex=,empty=n,full=0;
void producer(){
do{
producer an item nextp;
Swait(empty, mutex);
buttfer[in]=nextp;
in = (in+1)%n;
Ssignal(mutex,full);
}while(1); // 這裡其實就是把雙重申請換成了單獨S申請,其他的是一樣的
}
// 下略
哲學家進餐問題
-
五個哲學家,五根兒筷子
-
解決衝突的第一種方案,現制定順序,比如先拿左邊的筷子,
-
如果出現都拿了自己左手邊的,等待右手邊的筷子空閒下來,那就死鎖了,所以要想辦法改變一下,變成四個哲學家拿左邊的,這樣就能保證至少一個人能吃上飯,然後釋放。
-
但是這玩意程式碼咋寫…
-
程式碼略
reader & writer 問題
- 允許同時讀
- 不允許同時寫
- 不允許有讀有寫
- 設計:讀rmutex,寫wmutex
- 讀的時候加上寫鎖,寫的時候加上寫鎖和讀鎖
// reader開始讀
void reader(){
do{
wait(rmutex); // 讀的時候,上讀鎖
if(readcount==0)
wait(wmutex); // 第一個讀者開始的時候不允許其他人寫
readcount++;
signal(remutex); // 釋放臨界區(readcount)
.....
// 寫完了,需要destroy了
wait(rmutex); // 訪問讀者的臨界區
readcount --;
if(readcount==0)
signal(wmutex); // 最後一個讀者都沒有了這個時候允許寫
signal(rmutex);
}while(1);
}
void writer(){
do{
wait(wmutex); // 寫臨界區鎖開始開啟
........
write...
.......
signal(wmutex); // 臨界區鎖關閉
}while(1);
}
- 感悟
- 基本上mutex是用來表示臨界區的開啟關閉的,wait開啟臨界區,上臨界區鎖,signal關閉臨界區,上臨界區鎖…
- 記錄資料的,基本上可以算作是臨界區裡的,也就是這裡的readcount ++ - - 對它的操作必須是互斥的。