操作系统原理第七章:死锁
目錄
- 1 死鎖的基本概念
- 2 死鎖的必要條件
- 3 死鎖預防
- 3.1 抑制死鎖發生的必要條件
- 4 死鎖避免
- 4.1 資源分配圖法
- 4.2 銀行家算法
- 5 死鎖的檢測
- 5.1 每一種資源類型只有一個實例
- 5.2 一個資源類型的多個實例
- 5.3 處理死鎖
1 死鎖的基本概念
死鎖 (Deadlock) 指的是計算機系統中多道程序并發執行時,兩個或兩個以上的進程由于競爭資源而造成的一種互相等待的現象(僵局),如無外力作用,這些進程將永遠不能再向前推進。
下面是一個現實中過橋的例子,如圖所示橋比較窄,只能同時通過一輛車,此時有兩輛車面對面都想要通過橋,而兩輛車互不相讓,導致都無法通過,這就是現實中“死鎖”的例子。我們可以把橋看作是資源,對于車來說是共享的,當死鎖發生時,要解決死鎖其中一種解決方法是讓其中某輛車后退,現假設讓右邊的車后退,則它后面的所有車都要后退,若每次都是右邊的車讓道的話,右邊有些車就一直不能通行,就會造成饑餓現象。
死鎖現象產生的本質就是供小于求,也就是資源滿足不了進程的需求,每一個進程如下的利用資源:
- 申請 (Request): 如果申請不能立即被允許,那么進程必須等待直到能獲取資源。(通過系統調用或者信號量來進行資源的申請和釋放);
- 使用 (Use): 進程使用資源進行相關操作;
- 釋放 (Release) :進程釋放資源。
除了對資源的競爭會產生死鎖外,P,V操作順序的不當也會產生死鎖,如現有進程 P0P_0P0? 和 P1P_1P1?,他們都要申請信號量A和信號量B,他們的操作如下表:
| t1t_1t1? | wait(A); | wait(B); |
| t2t_2t2? | wait(B); | wait(A); |
在 t1t_1t1? 時刻,P0P_0P0? 和 P1P_1P1? 分別申請了信號量A和信號量B,那么在t2t_2t2?時刻由于信號量B已經被P1P_1P1?申請,所以P0P_0P0?申請不到信號量B,同理P1P_1P1?也申請不到信號量A,這時就發生了死鎖。
總結來說,產生死鎖的原因如下:
- 競爭資源引起死鎖:當系統中供多個進程所使用的資源,不足以同時滿足它們的需要時,引起它們對資源的競爭而產生死鎖;
- 進程推進順序不當引起死鎖:在多道程序系統中,并發執行的進程推進序列不可預測,有些推進順序,進程可以順利完成,有的推進順序會引起進程無限期地等待永遠不會發生的條件而不能向前推進,造成死鎖。
2 死鎖的必要條件
死鎖的產生是有必要條件的,當下面四個條件同時出現,死鎖將會發生:
死鎖指的是一組進程對一組資源使用時的某種狀態,那么描述這個狀態有如下參數:
- 資源類型:可以用 RRR 來表示,如 R1R_1R1?,R2R_2R2?,R3R_3R3? 表示CPU 周期,內存空間, I/O 設備;
- 實例:每個資源可能有多個實例,用 WWW 表示;
- 進程:用 PPP 來表示。
此時狀態就可以用資源分配圖來表示,在數據結構中,圖是由一組頂點的集合V和邊的集合E組成,在資源分配圖中有兩種類型的節點:
- P:系統中全部的進程構成的節點;
- R:系統中全部的資源構成的節點。
進程和資源之間的邊也存在著兩種邊:
- 請求邊:即某個進程 PiP_iPi? 要請求某個資源 RjR_jRj? 的邊,箭頭是從進程指向資源;
- 分配邊:即某個資源 RjR_jRj? 已經被分配給某個進程 PiP_iPi? 的邊,箭頭是從資源指向進程。
可以把進程表示為圓點,把資源表示成方形,把資源中的實例表示為更小的方形,如下圖所示:
上述兩種邊就可以表示為如下形式:
有了上面的方法就可以方便的描述進程和資源的分配關系,如某時刻的關系如下圖所示:
從資源分配圖中,可以得出如下結論:
- 如果圖沒有環,那么不會有死鎖;
- 如果圖有環,若每一種資源類型只有一個實例,那么一定會死鎖。若每種資源類型有多個實例,可能死鎖。
3 死鎖預防
處理死鎖有忽略、預防、避免、檢測、解除五種措施,對于忽略,即假裝系統中從未出現過死鎖。這個方法被大部分的操作系統采用,包括 UNIX 中的鴕鳥策略。預防死鎖,避免死鎖則是確保系統永遠不會進入死鎖狀態。死鎖檢測和解除則是允許系統進入死鎖狀態,然后恢復系統。
鴕鳥策略:鴕鳥都遇到危險時會將頭埋在沙子里,這里比喻像鴕鳥一樣對死鎖視而不見,因為處理死鎖的代價很大,而且常常給用戶帶來許多不便的限制,大多數用戶寧可在極偶然的情況下發生死鎖,也不愿限制每個用戶只能創建一個進程、只能打開一個文件等等,于是不得不在方便性和正確性之間作出折衷。
3.1 抑制死鎖發生的必要條件
對于互斥條件來講,是不可以打破的,因為在某些條件下,必須互斥訪問,詳細內容可以參考我的這篇文章 操作系統原理第六章:進程同步。
對于占有并等待條件,要打破這個條件的話必須保證進程申請資源的時候沒有占有其他資源,要做到這一點有如下兩種方式:
- 要求進程在執行前一次申請全部的資源,這樣的話后續就不會再有資源的申請了;
- 沒有資源時,可以申請資源。在申請更多其它資源之前,需要釋放已有資源。
實際上這兩種方法都是代價不小的,比如第一種方法,進程使用資源不是一次性全部使用的,而是有次序的使用的,這樣就導致某些資源明明現在用不到,卻占有著,而其他著急使用該資源的進程要等待。
對于非搶占式,打破這個條件就是運行搶占,搶占的方式是如果一個進程的申請沒有實現,它要釋放所有占有的資源,搶占的資源放入進程等待資源列表中,只有進程能夠重新得到舊的資源和新申請的資源時,才可以重新開始。
對于環路等待條件,打破這個條件可以將所有的資源類型放入資源列表中,并且要求進程按照資源表申請資源,也就是把每個資源編個號,所有進程對資源的請求必須嚴格按資源序號遞增的次序提出,如已經申請了1號資源CPU和2號資源打印機,那么后面再申請資源必須按次序3、4、5等等??傆幸粋€進程占據了較高序號的資源,它繼續請求的資源必然是空閑的,可以一直向前推進。在資源分配圖中不可能出現環路,因而摒棄了“環路等待”條件,這種策略可以提高資源利用率,但在進程使用各類資源的順序與系統規定的順序不同時會造成資源浪費的情況。
總結來說上述預防死鎖的方法通過限制資源請求來打破死鎖的四個必要條件之一,從而預防死鎖的發生,但可能帶來副作用,如降低設備利用率和吞吐量,可能有進程饑餓等。
4 死鎖避免
死鎖避免不會去限制資源的使用,而是允許進程動態地申請資源,但在系統進行資源分配之前,先計算資源分配的安全性,若此次分配不會導致系統從安全狀態向不安全狀態轉換,便可將資源分配給進程;否則不分配資源,進程必須阻塞等待。
安全狀態是指系統的一種狀態,在此狀態下,系統能按某種順序(例如 P1P_1P1?,P1P_1P1?,…,PnP_nPn?)來為各個進程分配其所需資源,直至最大需求,使每個進程都可順序地一個個地完成。這個序列(P1P_1P1?,P1P_1P1?,…,PnP_nPn?)稱為安全序列。 如果存在一個安全序列系統處于安全態,若某一時刻不存在一個安全序列,則稱系統處于不安全狀態。
死鎖屬于不安全狀態,是不安全狀態的一個子集,它們之間關系如下圖所示:
如果一個系統在安全狀態,就沒有死鎖;如果系統死鎖,則處于不安全狀態;如果一個系統處于不安全狀態,就有可能死鎖;那么既然要避免死鎖,那就要避免系統進入不安全狀態。
避免死鎖有兩種常用方式:
- 當資源有單個實例的時候,可以用之前提到的資源分配圖來實現,若圖中出現了環,這表示出現了死鎖,那么可以檢測資源分配圖判斷是否有環來避免死鎖;
- 當資源有多個實例的時候,使用銀行家算法,第五節會詳細介紹。
4.1 資源分配圖法
在第二節也提到過資源分配圖,在用資源分配圖法避免死鎖時,添加了一條新的邊叫做需求邊,即表示進程 PiP_iPi? 可能會申請到資源 RjR_jRj?,箭頭從進程指向資源,但是箭頭圖形為虛線。當一個進程申請資源的時候,需求邊轉化為請求邊;當資源被進程釋放的時候,分配邊轉化為需求邊;當然系統中的資源必須被事先聲明。
用資源分配圖法來避免死鎖的過程如下,當進程 PiP_iPi? 申請資源 RjR_jRj? 時,把圖中的所有需求邊轉換為分配邊,看圖中是否出現環路,只有不出現環路,才實施資源分配,如下圖:
4.2 銀行家算法
在現實世界中,銀行的利益方式之一是提供貸款,當銀行給某人提供貸款時,會考查他是否有能力償還貸款,以此來判斷貸出去的這筆錢是否是安全的。那么銀行家算法就是借助這個思想,當某個進程需要分配資源的時候,操作系統會判斷這個進程能不能把資源安全的歸還,如果可以的話操作系統就分配,否則不分配。
銀行家算法適用于多個實例的情況,每一個進程必須事先聲明使用的最大量,當一個進程請求資源 , 它可能要等待,當一個進程得到所有的資源,它必須在有限的時間釋放它們。
在實現銀行家算法時,需要定義如下的參數:
- n 為進程的數目,m 為資源類型的數目;
- Available:系統中有多少個可用資源,由于銀行家算法用在多實例中,則如果 available[ j ]=k,表示資源 RjR_jRj? 有 k 個實例有效;
- Max:每個進程最多需要請求的資源個數,如果 Max[ i, j ]=k,那么進程 PiP_iPi? 可以最多請求資源 RjR_jRj? 的 k 個實例;
- Allocation:每個進程已經分配了多少資源,如果 Allocation[ i, j ]=k,那么進程 PiP_iPi? 當前分配了 k 個資源 RjR_jRj? 的實例;
- Need:每個進程還需要多少資源,如果 Need[ i, j ]=k,那么進程 PiP_iPi? 還需要 k 個資源 RjR_jRj? 的實例。上面幾個參數也存在這樣的關系:Need [ i, j ] = Max [ i, j ] – Allocation [ i, j ],表示還需要的等于最多需要的減去已經擁有的;
- Request:進程當前要申請多少資源,如果 Request[ i, j ]=k,那么進程 PiP_iPi? 現在想申請 k 個資源 RjR_jRj? 的實例。
銀行家算法具體的實現過程如下:
在安全算法中,有兩個參數:
- Work:Work=Available 表示當前系統中可提供資源的數量;
- Finish:它為一個布爾變量,描述進程是否執行結束,如 Finish[ i ]=false 表示進程 PiP_iPi? 還沒有執行結束或還沒有執行。
在進程序列中,若進程 PiP_iPi? 滿足如下條件:
- Finish[ i ]=false,這個進程還沒執行完;
- Need <= Work,進程 PiP_iPi? 所需的資源數量是小于系統可用資源數量的,換句話說就是系統是可以滿足該進程的需求的。
則執行 Work=Work+Allocation,Finish[ i ]=true,這兩句的意思是進程 PiP_iPi? 執行完后把它的 Allocation 給釋放掉,然后標明 Finish 為已經執行完。然后按照該步驟依次檢查所有的進程,如果最后所有進程的 Finish 都為 true 則代表所有進程都能順利結束,那么說明系統為安全狀態。
銀行家算法舉例:現假設有五個進程 P0P_0P0? ~ P4P_4P4?,三個資源類型A(10個實例),B(5個實例),C(7個實例),下表是在 T0T_0T0? 時刻系統的狀態:
| A B C | A B C | A B C | |
| P0P_0P0? | 0 1 0 | 7 5 3 | 3 3 2 |
| P1P_1P1? | 2 0 0 | 3 2 2 | |
| P2P_2P2? | 3 0 2 | 9 0 2 | |
| P3P_3P3? | 2 1 1 | 2 2 2 | |
| P4P_4P4? | 0 0 2 | 4 3 3 |
要判斷當前是否處于安全狀態,要計算 Need,如下表:
| A B C | |
| P0P_0P0? | 7 4 3 |
| P1P_1P1? | 1 2 2 |
| P2P_2P2? | 6 0 0 |
| P3P_3P3? | 0 1 1 |
| P4P_4P4? | 4 3 1 |
此時使用安全算法來驗證是否有安全序列,初始條件下 Work = available = (3,3,2),Finish[ i ]=false (i=0..4):
| A B C | A B C | A B C | T/F | |
| P1P_1P1? | 3 3 2 | 1 2 2 | 2 0 0 | T |
| P3P_3P3? | 5 3 2 | 0 1 1 | 2 1 1 | T |
| P4P_4P4? | 7 4 3 | 4 3 1 | 0 0 2 | T |
| P2P_2P2? | 7 4 5 | 6 0 0 | 3 0 2 | T |
| P0P_0P0? | 10 4 7 | 7 4 3 | 0 1 0 | T |
所以存在安全序列(P1P_1P1?,P3P_3P3?,P4P_4P4?,P2P_2P2?,P0P_0P0?),系統處于安全狀態。
注意:安全序列有時不是唯一的,但只要找到一個,就認為系統是安全的。
5 死鎖的檢測
死鎖的檢測分為兩種情況,一種是每一種資源類型只有一個實例,另一種是一個資源類型的多個實例。
5.1 每一種資源類型只有一個實例
通過把資源分配圖轉換成維護等待圖,來看維護等待圖中是否出現了環來看當前系統是否出現了死鎖。在維護等待圖中只有一種節點就是進程,若進程 PiP_iPi? 指向進程 PjP_jPj?,則表示 PiP_iPi? 在等待 PjP_jPj?,如下圖左邊為資源分配圖,右邊為轉換的維護等待圖:
可以發現維護等待圖中是存在環的,所以表明當前系統出現死鎖了。
5.2 一個資源類型的多個實例
這里用到的算法叫做死鎖檢測算法,類似于之前講過的安全算法,死鎖檢測算法定義了如下參數:
- Available:每種資源可用的資源數量;
- Allocation:每個進程已經分配的資源數量;
- Request:進程請求的資源數量。
- Work:Work = Available;
- Finish:這里的Finish和之前的安全算法有所不同,這里當 Allocation != 0 則 Finish = false 否則 Finish = true ,也就是若某進程的 Allocation = 0 時說明這個進程沒有被分配到資源,也就這個進程是不參與死鎖的,所以 Finish = true 。
找到某進程滿足如下條件:
- Finish = false;
- Request <= Work:當前請求的資源數量小于系統可分配資源的數量;
該進程能執行結束,執行結束后執行 Work = Work + Allocation, Finish = true,也就是把資源給釋放掉,把所有的進程按如上操作遍歷結束后檢查會不會有一個進程的 Finish = false ,也就是某個進程無法結束,那么就發生了死鎖,并且可以根據 Finish 的下標找到是哪一個進程發生了死鎖。
檢測死鎖算法的例子:現假設有五個進程 P0P_0P0? ~ P4P_4P4?,三個資源類型A(7個實例),B(2個實例),C(6個實例),下表是在 T0T_0T0? 時刻系統的狀態:
| A B C | A B C | A B C | |
| P0P_0P0? | 0 1 0 | 0 0 0 | 0 0 0 |
| P1P_1P1? | 2 0 0 | 2 0 2 | |
| P2P_2P2? | 3 0 3 | 0 0 0 | |
| P3P_3P3? | 2 1 1 | 1 0 0 | |
| P4P_4P4? | 0 0 2 | 0 0 2 |
所以存在安全序列(P0P_0P0?,P2P_2P2?,P3P_3P3?,P1P_1P1?,P4P_4P4?),當前是沒有死鎖的。
現在假設進程 P2P_2P2? 請求 C 的一個實例,如下表:
| A B C | |
| P0P_0P0? | 0 0 0 |
| P1P_1P1? | 2 0 1 |
| P2P_2P2? | 0 0 1 |
| P3P_3P3? | 1 0 0 |
| P4P_4P4? | 0 0 2 |
此時可以歸還 P0P_0P0? 所有的資源,但是資源不夠完成其他進程的請求,所以死鎖存在,包括進程 P1P_1P1?,P2P_2P2?,P3P_3P3?,P4P_4P4?。
5.3 處理死鎖
當系統出現死鎖,通常有如下三種方式:
- 操作員人工處理
- 進程終止
- 資源搶占
其中進程終止和資源搶占是由操作系統來完成的,進程終止的方法如下:
- 終止所有的死鎖進程;
- 一次終止一個進程直到死鎖環消失;
- 選擇終止順序,比如按照進程的優先級來終止,先終止優先級低的進程,或者按照進程計算了多少時間,還需要多長時間來選擇終止。
資源搶占的方法如下:
- 選擇一個犧牲品:最小化代價;
- 回退:返回到安全的狀態,然后重新開始進程;
但是會造成饑餓,因為同一個進程可能總是被選中,包括在回退時。
總結
以上是生活随笔為你收集整理的操作系统原理第七章:死锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统原理第六章:进程同步
- 下一篇: 操作系统原理第八章:内存管理