實現臨界區互斥的基本方法
----------軟體實現方法:
在進入區設定和檢查一些標誌來表明是否有程序在臨界區中,如果已有程序在臨界區,則在進入區通過迴圈檢查進行等待,程序離開臨界區後則在退出區修改
標誌。
(1)、演算法一:單標誌法。(違背“空閒讓進”原則)
該演算法設定一個公用整型變數 turn ,用於指示被允許進入臨界區的程序編號,即,若turn = 0 ,則允許p0 程序進入臨界區。該演算法可確保每次只允許一個
程序進入臨界區。但兩個程序必須交換進入臨界區,如果某個程序不再進入臨界區了,那麼另一個程序也將無法進入臨界區(違背“空閒讓進”)。這樣很容易
造成資源利用不充分。
假如,p0順利進入臨界區並從臨界區離開,那麼此時臨界區是空閒的,但p1 並沒有進入臨界區的打算,turn = 1 一直成立,p0 就無法再次進入臨界區了
(一直被 while 死迴圈困住)
p0 程序:
while(turn != 0); //進入區
critical section ; //臨界區
turn = 1; //退出區
remainder section; //剩餘區
p1程序:
while(turn != 1); //進入區
critical section; //臨界區
turn = 0; //退出區
remainder section; //剩餘區
(2)、演算法二:雙標誌法先檢查。(違背“忙則等待”原則)
該演算法的基本思想是在每一個程序訪問臨界區資源之前,先檢視一下臨界區資源是否正被訪問,若正被訪問,該程序需等待;否則,程序才進入自己
自己的臨界區。為此,設定一個數據 flag[ i ] ,如第i個元素值為 FALSE ,表示Pi程序未進入臨界區,值為 TRUE ,表示Pi程序進入臨界區。
Pi 程序:
while(flag[ j ]); //進入區
flag[ i ] = TRUE; //進入區
critical section; //臨界區
flag[ i ] = FALSE; //退出區
remainder section; //剩餘區
Pj程序:
while(flag[ i ]); //進入區
flag[ j ] = TRUE; //進入區
critical section; //臨界區
flag[ i ] = FALSE; //退出區
remainder section; //剩餘區
優點:不用交替進入,可連續使用。
缺點:Pi 程序和 Pj程序可能同時進入臨界區。
檢查和修改操作不能一次進行。
(3)、演算法三:雙標誌法後檢查(會導致“飢餓”現象)
演算法二是先檢查對方程序狀態標誌後,再置自己的標誌,由於在檢查和放置中可插入另一個程序到達時的檢測操作,會造成兩個程序在分別檢查
後,同時進入臨界區。為此,演算法三採用先設定自己標誌為 TRUE 後,再檢測對方狀態標誌,若對方標誌位 TRUE,則程序等待;否則進入臨界區。
Pi程序:
flag[ i ] =TRUE; //進入區
while(flag[ j ]); //進入區
critical section; //臨界區
remainder section; //剩餘區
Pj程序:
flag[ j ] =TRUE; //進入區
while(flag[ i ] ); //進入區
critical section; //臨界區
remainder section; //剩餘區
當兩個程序幾乎同時都想進入臨界區時,它們分別將自己的標誌值 flag 設定為 TRUE ,並且同時檢測對方的狀態(執行 while 語句),發現對方
也要進入臨界區,於是雙方互相謙讓,結果誰也進不了臨界區,從而導致“飢餓”現象。
(4)、演算法四:Peterson's Algorithm(皮特森演算法:單標誌法和雙標誌法後檢查的結合)
為了防止兩個程序為進入臨界區而無限期等待,又設定變數 turn ,,每個程序在設定自己標誌後再設定 turn 標誌。這時,再同時檢測另一個程序
狀態標誌和不允許進入標誌,這樣可以保證當程序同時要求進入臨界區,只允許一個程序進入臨界區。
Pi 程序:
flag[ i ] = TRUE; turn = j; //進入區
while(flag[ j ] && turn == j); //進入區
critical section; //l臨界區
flag[ i ] = FALSE ; //退出區
remainder section; //剩餘區
Pj 程序:
flag[ j ] = TRUE; turn = i; //進入區
while(flag[ i ] && turn == i); //進入區
critical section; //臨界區
flag[ j ] = FALSE; //退出區
remainder section; //剩餘區
具體如下:考慮程序 Pi ,一旦它設定 flag[ i ] = TRUE ,表示它想要進入臨界區,同時 turn = j ,此時如果程序 Pj 已經在臨界區中,則符合程序Pi 的while
迴圈條件,則Pi 不能進入臨界區。而如果 Pj 程序沒要進入臨界區,即 flag[ j ] = FALSE ,迴圈條件不符合,則 Pi 程序可以順利進入,反之亦然,本演算法
的基本思想是:演算法一和演算法三的結合。利用 flag 解決臨界資源的互斥訪問,而利用 turn 解決“飢餓”現象。
----------硬體實現方法:
計算機提供了特殊的硬體指令,允許對一個字中的內容進行檢測和修正,或者是對兩個字的內容進行交換等。通過硬體支援實現臨界區問題的低階方法或
稱為 元方法。
(1)、中斷遮蔽方法:
當一個程序正在使用處理機執行它的臨界區程式碼時,要防止其他程序再進入其臨界區訪問的最簡單的方法是:禁止一切中斷髮生,或稱之為遮蔽中斷、
關中斷。
因為CPU 只在發生中斷時引起程序切換,這樣遮蔽中斷就能保證當前執行程序將臨界區程式碼順利地執行完,從而保證互斥的正確實現,然後再執行開
中斷。其典型模式為:
....
關中斷;
臨界區;
開中斷;
.....
這種方法限制了處理機交替執行的能力,因此執行的效率將會明顯降低。對核心來說,當它執行更新變數或列表的幾條指令期間關中斷是很方便的,但是
將關中斷的權利交給使用者則很不明智,若一個程序關中斷之後不再開中斷,則系統可能會因此終止。
(2)、硬體指令方法:設立原子操作指令
TestAndSet 指令:這條指令是原子操作,即執行該程式碼時不允許被中斷。其功能是:讀出指定標誌後把該標誌設定為真。指令功能的描述如下:
boolean TestAndSet(boolean *lock){
boolean old;
old = *lock;
*lock = true;
return old;
}
可以為每個臨界資源設定一個共享布林變數 lock ,表示資源的兩種狀態:true 表示正被佔用,初值為 false 。在程序訪問臨界資源之前,利用
TestAndSet 檢查和修改標誌 lock ;若有程序在臨界區,則重複檢查,直到程序退出。利用該指令實現程序互斥的演算法描述如下:
while TestAndSet(&lock);
程序的臨界區程式碼段;
lock = false;
程序的其他程式碼;
Swap 指令:該指令的功能是交換兩個字(位元組)的內容。其功能描述如下:
Swap(boolean *a , boolean *b){
boolean temp;
temp = *a;
*a = *b;
*b = temp;
}
應為每個臨界資源設定一個共享布林變數 lock ,初值為 false;在每個程序中再設定一個區域性布林變數 key ,用於與 lock 變換資訊。在進入臨界區之前
先利用 Swap 指令交換 lock 與 key 的內容,然後檢查 key 的狀態;有程序在臨界區時,重複交換和檢查過程,直到程序退出。利用Swap 指令實現程序互斥的
演算法描述如下:
key = true;
while(key != false);
Swap(&lock , &key);
程序的臨界區程式碼段;
lock = false;
程序的其他程式碼;
硬體方法的優點:適用於任意數目的程序,不管是單處理機還是多處理機;簡單、容易驗證其正確性。可以支援程序內有多個臨界區,只需為每個臨界區
設立一個布林變數。
硬體方法的缺點:程序等待進入臨界區時要耗費處理時間,不能實現讓權等待。從等待程序中隨機選擇一個進入臨界區,有程序可能一直選不上,從而
導致“飢餓”現象。
注意:以上對 TestAndSet 和 Swap 指令的描述僅僅是功能實現,並非軟體實現定義,事實上它們是由硬體邏輯直接實現的,不會被中斷。
補充:以上的程式碼實現與我們平時在編譯器上寫的程式碼意義不同,以上的程式碼實現是為了表述程序實現同步和互斥的過程,並不是說計算機內部實現
同步互斥就是這些程式碼。
----------訊號量:利用PV 操作實現互斥