操作系統原理第七章:死鎖

1 死鎖的基本概念

死鎖 (Deadlock) 指的是計算機系統中多道程序併發執行時,兩個或兩個以上的進程由於競爭資源而造成的一種互相等待的現象(僵局),如無外力作用,這些進程將永遠不能再向前推進。

下面是一個現實中過橋的例子,如圖所示橋比較窄,只能同時通過一輛車,此時有兩輛車面對面都想要通過橋,而兩輛車互不相讓,導致都無法通過,這就是現實中“死鎖”的例子。我們可以把橋看作是資源,對於車來說是共享的,當死鎖發生時,要解決死鎖其中一種解決方法是讓其中某輛車後退,現假設讓右邊的車後退,則它後面的所有車都要後退,若每次都是右邊的車讓道的話,右邊有些車就一直不能通行,就會造成飢餓現象。
在這裏插入圖片描述
死鎖現象產生的本質就是供小於求,也就是資源滿足不了進程的需求,每一個進程如下的利用資源:

  • 申請 (Request): 如果申請不能立即被允許,那麼進程必須等待直到能獲取資源。(通過系統調用或者信號量來進行資源的申請和釋放);
  • 使用 (Use): 進程使用資源進行相關操作;
  • 釋放 (Release) :進程釋放資源。

除了對資源的競爭會產生死鎖外,P,V操作順序的不當也會產生死鎖,如現有進程 P0P_0P1P_1,他們都要申請信號量A和信號量B,他們的操作如下表:

時間 P0P_0 P1P_1
t1t_1 wait(A); wait(B);
t2t_2 wait(B); wait(A);

t1t_1 時刻,P0P_0P1P_1 分別申請了信號量A和信號量B,那麼在t2t_2時刻由於信號量B已經被P1P_1申請,所以P0P_0申請不到信號量B,同理P1P_1也申請不到信號量A,這時就發生了死鎖。

總結來說,產生死鎖的原因如下:

  • 競爭資源引起死鎖:當系統中供多個進程所使用的資源,不足以同時滿足它們的需要時,引起它們對資源的競爭而產生死鎖;
  • 進程推進順序不當引起死鎖:在多道程序系統中,併發執行的進程推進序列不可預測,有些推進順序,進程可以順利完成,有的推進順序會引起進程無限期地等待永遠不會發生的條件而不能向前推進,造成死鎖。

2 死鎖的必要條件

死鎖的產生是有必要條件的,當下面四個條件同時出現,死鎖將會發生:

  1. 互斥 (Mutual exclusion):一次只有一個進程可以使用一個資源,因爲有些資源的使用是必須獨佔的;
  2. 佔有並等待 (Hold and wait):一個至少持有一個資源的進程等待獲得額外的由其他進程所持有的資源,又叫請求與保持;
  3. 不可搶佔 (No preemption):一個資源只有當持有它的進程完成任務後才自由的釋放,也就是非剝奪。若可以搶佔的話,別的進程需要某個資源可以直接搶,就不存在死鎖問題了;
  4. 環路等待 (Circular wait):等待資源的進程之間存在環,舉個例子來說進程1要獲得進程2手中的資源才能執行結束,進程2要獲得進程3手中的資源才能運行結束,進程3要獲得進程1手中的資源才能運行結束,這樣就形成了一個等待環,誰也無法運行結束離開這個環。

死鎖指的是一組進程對一組資源使用時的某種狀態,那麼描述這個狀態有如下參數:

  • 資源類型:可以用 RR 來表示,如 R1R_1R2R_2R3R_3 表示CPU 週期,內存空間, I/O 設備;
  • 實例:每個資源可能有多個實例,用 WW 表示;
  • 進程:用 PP 來表示。

此時狀態就可以用資源分配圖來表示,在數據結構中,圖是由一組頂點的集合V和邊的集合E組成,在資源分配圖中有兩種類型的節點:

  • P:系統中全部的進程構成的節點;
  • R:系統中全部的資源構成的節點。

進程和資源之間的邊也存在着兩種邊:

  • 請求邊:即某個進程 PiP_i 要請求某個資源 RjR_j 的邊,箭頭是從進程指向資源;
  • 分配邊:即某個資源 RjR_j 已經被分配給某個進程 PiP_i 的邊,箭頭是從資源指向進程。

可以把進程表示爲圓點,把資源表示成方形,把資源中的實例表示爲更小的方形,如下圖所示:
在這裏插入圖片描述
上述兩種邊就可以表示爲如下形式:
在這裏插入圖片描述
有了上面的方法就可以方便的描述進程和資源的分配關係,如某時刻的關係如下圖所示:
在這裏插入圖片描述
從資源分配圖中,可以得出如下結論:

  • 如果圖沒有環,那麼不會有死鎖;
  • 如果圖有環,若每一種資源類型只有一個實例,那麼一定會死鎖。若每種資源類型有多個實例,可能死鎖。

3 死鎖預防

處理死鎖有忽略、預防、避免、檢測、解除五種措施,對於忽略,即假裝系統中從未出現過死鎖。這個方法被大部分的操作系統採用,包括 UNIX 中的鴕鳥策略。預防死鎖,避免死鎖則是確保系統永遠不會進入死鎖狀態。死鎖檢測和解除則是允許系統進入死鎖狀態,然後恢復系統。

鴕鳥策略:鴕鳥都遇到危險時會將頭埋在沙子裏,這裏比喻像鴕鳥一樣對死鎖視而不見,因爲處理死鎖的代價很大,而且常常給用戶帶來許多不便的限制,大多數用戶寧可在極偶然的情況下發生死鎖,也不願限制每個用戶只能創建一個進程、只能打開一個文件等等,於是不得不在方便性和正確性之間作出折衷。

3.1 抑制死鎖發生的必要條件

對於互斥條件來講,是不可以打破的,因爲在某些條件下,必須互斥訪問,詳細內容可以參考我的這篇文章 操作系統原理第六章:進程同步

對於佔有並等待條件,要打破這個條件的話必須保證進程申請資源的時候沒有佔有其他資源,要做到這一點有如下兩種方式:

  • 要求進程在執行前一次申請全部的資源,這樣的話後續就不會再有資源的申請了;
  • 沒有資源時,可以申請資源。在申請更多其它資源之前,需要釋放已有資源。

實際上這兩種方法都是代價不小的,比如第一種方法,進程使用資源不是一次性全部使用的,而是有次序的使用的,這樣就導致某些資源明明現在用不到,卻佔有着,而其他着急使用該資源的進程要等待。

對於非搶佔式,打破這個條件就是運行搶佔,搶佔的方式是如果一個進程的申請沒有實現,它要釋放所有佔有的資源,搶佔的資源放入進程等待資源列表中,只有進程能夠重新得到舊的資源和新申請的資源時,纔可以重新開始。

對於環路等待條件,打破這個條件可以將所有的資源類型放入資源列表中,並且要求進程按照資源表申請資源,也就是把每個資源編個號,所有進程對資源的請求必須嚴格按資源序號遞增的次序提出,如已經申請了1號資源CPU和2號資源打印機,那麼後面再申請資源必須按次序3、4、5等等。總有一個進程佔據了較高序號的資源,它繼續請求的資源必然是空閒的,可以一直向前推進。在資源分配圖中不可能出現環路,因而摒棄了“環路等待”條件,這種策略可以提高資源利用率,但在進程使用各類資源的順序與系統規定的順序不同時會造成資源浪費的情況。

總結來說上述預防死鎖的方法通過限制資源請求來打破死鎖的四個必要條件之一,從而預防死鎖的發生,但可能帶來副作用,如降低設備利用率和吞吐量,可能有進程飢餓等。

4 死鎖避免

死鎖避免不會去限制資源的使用,而是允許進程動態地申請資源,但在系統進行資源分配之前,先計算資源分配的安全性,若此次分配不會導致系統從安全狀態不安全狀態轉換,便可將資源分配給進程;否則不分配資源,進程必須阻塞等待。

安全狀態是指系統的一種狀態,在此狀態下,系統能按某種順序(例如 P1P_1P1P_1,…,PnP_n)來爲各個進程分配其所需資源,直至最大需求,使每個進程都可順序地一個個地完成。這個序列(P1P_1P1P_1,…,PnP_n)稱爲安全序列。 如果存在一個安全序列系統處於安全態,若某一時刻不存在一個安全序列,則稱系統處於不安全狀態。

死鎖屬於不安全狀態,是不安全狀態的一個子集,它們之間關係如下圖所示:
在這裏插入圖片描述
如果一個系統在安全狀態,就沒有死鎖;如果系統死鎖,則處於不安全狀態;如果一個系統處於不安全狀態,就有可能死鎖;那麼既然要避免死鎖,那就要避免系統進入不安全狀態。

避免死鎖有兩種常用方式:

  • 當資源有單個實例的時候,可以用之前提到的資源分配圖來實現,若圖中出現了環,這表示出現了死鎖,那麼可以檢測資源分配圖判斷是否有環來避免死鎖;
  • 當資源有多個實例的時候,使用銀行家算法,第五節會詳細介紹。

4.1 資源分配圖法

在第二節也提到過資源分配圖,在用資源分配圖法避免死鎖時,添加了一條新的邊叫做需求邊,即表示進程 PiP_i 可能會申請到資源 RjR_j,箭頭從進程指向資源,但是箭頭圖形爲虛線。當一個進程申請資源的時候,需求邊轉化爲請求邊;當資源被進程釋放的時候,分配邊轉化爲需求邊;當然系統中的資源必須被事先聲明。

用資源分配圖法來避免死鎖的過程如下,當進程 PiP_i 申請資源 RjR_j 時,把圖中的所有需求邊轉換爲分配邊,看圖中是否出現環路,只有不出現環路,才實施資源分配,如下圖:
在這裏插入圖片描述

4.2 銀行家算法

在現實世界中,銀行的利益方式之一是提供貸款,當銀行給某人提供貸款時,會考查他是否有能力償還貸款,以此來判斷貸出去的這筆錢是否是安全的。那麼銀行家算法就是藉助這個思想,當某個進程需要分配資源的時候,操作系統會判斷這個進程能不能把資源安全的歸還,如果可以的話操作系統就分配,否則不分配。

銀行家算法適用於多個實例的情況,每一個進程必須事先聲明使用的最大量,當一個進程請求資源 , 它可能要等待,當一個進程得到所有的資源,它必須在有限的時間釋放它們。


在實現銀行家算法時,需要定義如下的參數:

  • n 爲進程的數目,m 爲資源類型的數目;
  • Available:系統中有多少個可用資源,由於銀行家算法用在多實例中,則如果 available[ j ]=k,表示資源 RjR_jk 個實例有效;
  • Max:每個進程最多需要請求的資源個數,如果 Max[ i, j ]=k,那麼進程 PiP_i 可以最多請求資源 RjR_jk 個實例;
  • Allocation:每個進程已經分配了多少資源,如果 Allocation[ i, j ]=k,那麼進程 PiP_i 當前分配了 k 個資源 RjR_j 的實例;
  • Need:每個進程還需要多少資源,如果 Need[ i, j ]=k,那麼進程 PiP_i 還需要 k 個資源 RjR_j 的實例。上面幾個參數也存在這樣的關係:Need [ i, j ] = Max [ i, j ] – Allocation [ i, j ],表示還需要的等於最多需要的減去已經擁有的;
  • Request:進程當前要申請多少資源,如果 Request[ i, j ]=k,那麼進程 PiP_i 現在想申請 k 個資源 RjR_j 的實例。

銀行家算法具體的實現過程如下:

  1. Request <= Need,執行下一步,否則不執行,意思是進程當前想申請的資源必須是小於它實際所需要的,不能超出實際所需要的;
  2. Request <= Available,執行下一步,意思是當前申請的資源數量,系統是否可以滿足,若滿足不了,則需要等待,直到系統可以滿足爲止;
  3. 試着去給該進程分配資源,會執行如下操作 Available=Available-RequestAllocation=Allocation+RequestNeed=Need-Request
  4. 安全算法檢查,看系統是否處於安全狀態,如果安全就分配資源,否則不分配並讓之前執行的三個操作回到原來狀態。

在安全算法中,有兩個參數:

  • WorkWork=Available 表示當前系統中可提供資源的數量;
  • Finish:它爲一個布爾變量,描述進程是否執行結束,如 Finish[ i ]=false 表示進程 PiP_i 還沒有執行結束或還沒有執行。

在進程序列中,若進程 PiP_i 滿足如下條件:

  • Finish[ i ]=false,這個進程還沒執行完;
  • Need <= Work,進程 PiP_i 所需的資源數量是小於系統可用資源數量的,換句話說就是系統是可以滿足該進程的需求的。

則執行 Work=Work+AllocationFinish[ i ]=true,這兩句的意思是進程 PiP_i 執行完後把它的 Allocation 給釋放掉,然後標明 Finish 爲已經執行完。然後按照該步驟依次檢查所有的進程,如果最後所有進程的 Finish 都爲 true 則代表所有進程都能順利結束,那麼說明系統爲安全狀態。


銀行家算法舉例:現假設有五個進程 P0P_0 ~ P4P_4,三個資源類型A(10個實例),B(5個實例),C(7個實例),下表是在 T0T_0 時刻系統的狀態:

進程 Allocation(已分配) Max(總需求) Available(當前可分配)
A B C A B C A B C
P0P_0 0 1 0 7 5 3 3 3 2
P1P_1 2 0 0 3 2 2
P2P_2 3 0 2 9 0 2
P3P_3 2 1 1 2 2 2
P4P_4 0 0 2 4 3 3

要判斷當前是否處於安全狀態,要計算 Need,如下表:

進程 Need(還需的資源數)=Max-Allocation
A B C
P0P_0 7 4 3
P1P_1 1 2 2
P2P_2 6 0 0
P3P_3 0 1 1
P4P_4 4 3 1

此時使用安全算法來驗證是否有安全序列,初始條件下 Work = available = (3,3,2)Finish[ i ]=false (i=0..4)

進程 Work Need Allocation Finish
A B C A B C A B C T/F
P1P_1 3 3 2 1 2 2 2 0 0 T
P3P_3 5 3 2 0 1 1 2 1 1 T
P4P_4 7 4 3 4 3 1 0 0 2 T
P2P_2 7 4 5 6 0 0 3 0 2 T
P0P_0 10 4 7 7 4 3 0 1 0 T

所以存在安全序列(P1P_1P3P_3P4P_4P2P_2P0P_0),系統處於安全狀態。

注意:安全序列有時不是唯一的,但只要找到一個,就認爲系統是安全的。

5 死鎖的檢測

死鎖的檢測分爲兩種情況,一種是每一種資源類型只有一個實例,另一種是一個資源類型的多個實例。

5.1 每一種資源類型只有一個實例

通過把資源分配圖轉換成維護等待圖,來看維護等待圖中是否出現了環來看當前系統是否出現了死鎖。在維護等待圖中只有一種節點就是進程,若進程 PiP_i 指向進程 PjP_j,則表示 PiP_i 在等待 PjP_j,如下圖左邊爲資源分配圖,右邊爲轉換的維護等待圖:
在這裏插入圖片描述
可以發現維護等待圖中是存在環的,所以表明當前系統出現死鎖了。

5.2 一個資源類型的多個實例

這裏用到的算法叫做死鎖檢測算法類似於之前講過的安全算法,死鎖檢測算法定義瞭如下參數:

  • Available:每種資源可用的資源數量;
  • Allocation:每個進程已經分配的資源數量;
  • Request:進程請求的資源數量。
  • WorkWork = Available
  • Finish:這裏的Finish和之前的安全算法有所不同,這裏當 Allocation != 0Finish = false 否則 Finish = true ,也就是若某進程的 Allocation = 0 時說明這個進程沒有被分配到資源,也就這個進程是不參與死鎖的,所以 Finish = true

找到某進程滿足如下條件:

  • Finish = false
  • Request <= Work:當前請求的資源數量小於系統可分配資源的數量;

該進程能執行結束,執行結束後執行 Work = Work + AllocationFinish = true,也就是把資源給釋放掉,把所有的進程按如上操作遍歷結束後檢查會不會有一個進程的 Finish = false ,也就是某個進程無法結束,那麼就發生了死鎖,並且可以根據 Finish 的下標找到是哪一個進程發生了死鎖。


檢測死鎖算法的例子:現假設有五個進程 P0P_0 ~ P4P_4,三個資源類型A(7個實例),B(2個實例),C(6個實例),下表是在 T0T_0 時刻系統的狀態:

進程 Allocation(已分配) Request(當前請求) Available(當前可分配)
A B C A B C A B C
P0P_0 0 1 0 0 0 0 0 0 0
P1P_1 2 0 0 2 0 2
P2P_2 3 0 3 0 0 0
P3P_3 2 1 1 1 0 0
P4P_4 0 0 2 0 0 2

所以存在安全序列(P0P_0P2P_2P3P_3P1P_1P4P_4),當前是沒有死鎖的。

現在假設進程 P2P_2 請求 C 的一個實例,如下表:

進程 Request(當前請求)
A B C
P0P_0 0 0 0
P1P_1 2 0 1
P2P_2 0 0 1
P3P_3 1 0 0
P4P_4 0 0 2

此時可以歸還 P0P_0 所有的資源,但是資源不夠完成其他進程的請求,所以死鎖存在,包括進程 P1P_1P2P_2P3P_3P4P_4

5.3 處理死鎖

當系統出現死鎖,通常有如下三種方式:

  • 操作員人工處理
  • 進程終止
  • 資源搶佔

其中進程終止和資源搶佔是由操作系統來完成的,進程終止的方法如下:

  • 終止所有的死鎖進程;
  • 一次終止一個進程直到死鎖環消失;
  • 選擇終止順序,比如按照進程的優先級來終止,先終止優先級低的進程,或者按照進程計算了多少時間,還需要多長時間來選擇終止。

資源搶佔的方法如下:

  • 選擇一個犧牲品:最小化代價;
  • 回退:返回到安全的狀態,然後重新開始進程;

但是會造成飢餓,因爲同一個進程可能總是被選中,包括在回退時。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章