You are on page 1of 13

第八章 死結

在多元程式規劃環境中,許多行程可能要爭著使用一些有限資源。但是在一個
行程要求資源的時候,若這些資源那時並非現成可用的,那麼它就要進入等候狀態。
又由於它們所要求的資源可能已被其它在等候的行程所把持,因此將會使得這進入
等候狀態的行程永遠無法改變其狀態。這現象就稱為死結 (deadlock) 。我們在第
七章介紹號誌時已經討論過死結。

8.1 系統模型
在正常運作的模式之下,一個行程只能依據下列的順序來使用資源:

1. 要求:若此要求不能立即被認可 (例如,這資源正被其它的行程所使用),則行
程必須等候以獲得此資源。
2. 使用:行程能夠在此資源上運作 (例如,若這資源為印表機,則行程可把資源印
於印表機上)。
3. 釋放:行程釋放資源。

8.2 死結的特性
很明顯地,死結是不受歡迎的。在一個死結中,行程永遠不會完成執行而且系
統的資源被束縛住,也禁止了其它工作的開始。我們在此將描述死結的特性,以幫
助爾後討論解決死結問題的各種方法。

8.2.1 必要條件
下列四狀況在系統中同時成立時即為死結問題發生的充要條件。

1. 互斥 (mutual exclusion):至少有一資源必須是不可共用的型式,換言之,一次
只有一個行程可使用此資源。若有另一行程想使用這資源,則必須延遲至此資源
被釋放後才可以。
2. 佔用與等候 (hold and wait):必須存在一個至少已佔用一個資源且正等候其它
行程已佔用另外資源之行程。
3. 不可搶先 (no preemption):資源無優先權;因此,一個資源只能被佔用它的行
程在完成工作目標之後,才被釋放。
4. 循環式等候 (circular wait):必須存在一等候行程的集合{P0, P1, …, Pn},其中
P0 等候的資源已被 P1 佔用,P1 所等候的資源已被 P2 佔用,…,Pn-1 所等候的資
源已被 Pn 佔用,而 Pn 所等候的資源已被 P0 佔用。

58
我們再一次地強調只有在此四個條件皆成立時,才會發生死結現象。循環式等
候之條件隱含了佔用與等候之條件,所以這四個條件並不是完全獨立的。無論如何,
我們將看出 (見第 8.4 節) 分別考慮這些條件的用處。

8.2.2 資源配置圖
死結現象可用一個叫作系統資源配置圖 (system resource-allocation graph) 的
有向圖詳細說明。此圖由一組頂點 V 及一組邊 E 所組成。頂點所成的集合 V 又可
區分為兩個不同的集合,就是系統中所有正在執行之行程的集合 P={P1, P2, …, Pn},
與系統中所有資源型式的集合 R={P1, P2, …, Pm}。

若行程 Pi 至資源型式 Rj 間存在一有向邊 Pi → R,


j 意指行程 Pi 已要求資源型式

Rj 中之一例證,且正在等候此資源。若資源型式 Rj 至行程 Pi 間存在一有向邊 Rj →


Pi,意指資源型式 Rj 中之一例證已被行程 Pi 佔用。Pi → Rj 之有向邊稱為要求邊
(request edge),而 Rj →Pi 之有向邊稱為分配邊 (assignment edge)。

由資源佔用圖的定義來看,它可很容易的顯示出,若在此圖中無循環 (cycle) 出
現,則在此系統中並無死結的行程。反之,若在此圖中含有一個循環,則發生一個
死結。

若每種資源型式兄含有一個例證,則一個循環表示發生一個死結。若只有一集
合;資源型式產生循環,而這些資源都只有單一的例證,則亦產生死結。在此循環
中的每一個行程都產生了死結。故在此狀況下,圖中的循環為死結存在與否的充要
與必要條件。

59
但是若每種資源型式含有多種例證,則此循環未必表示發生死結。故在此狀況
下,這圖中所產生的循環只是發生死結的必要條件,而非充分條件。

總括來說,若資源配置圖中無循環出現,則系統就不會進入死結狀態。反之,
若有循環形成,則此系統並不一定會進入死結狀態。這在處理死結問題上是一個非
常重要的觀點。

8.3 處理死結的方法
在理論上,有三種不同處理死結的方法:
⚫ 我們可以使用某一協議,以防止或避免系統絕不會進入死結狀態。
⚫ 我們可以允許系統進入死結狀態,偵測出來再想辦法恢復之。
⚫ 我們可以忽視此問題,假裝系統從沒發生過死結。這種方法被大部份的作業系
統所採用,包括了 UNIX。

為了確保死結永不發生,我們可以使用預防死結法或是避免死結法。預防死結
(deadlock prevention) 是一組可以確保死結必要條件 (8.2.1 節) 至少有一項不會發
生的方法。這些方法藉由限制對資源的要求來避免死結發生。我們在 8.4 節討論這
些方法。

反之,避免死結 (deadlock avoidance) 則要求作業系統預先能取得,一個行程


在它的生命期將會要求及使用那些資源。有了這些資訊之後,我們可以決定,此行
程的每一要求是否該等待。每一項要求都會使系統考慮現有的可用資源,已分配給
其它行程的資源,以及將來的要求和其它行程會釋放的資源;作業系統可以據此決
定現在的要求是否可以繼續,或是必須延遲。我們將在 8.5 節討論這方面的技術。

如果一個系統沒有使用預防死結和避免死結的演算方法,則此系統可能會發生
死結。在這種情況下,系統可以提供一個演算法來檢查其狀態,以判斷是否有死結
發生,另外還要一套演算法來恢復死結(如果死結真的發生時)。我們在 8.6 節和 8.7
節討論這些問題。

雖然這種方法似乎不是一種解決死結的可行方法,但是確實使用在某些作業系
統上。在許多系統上,死結很少發生(譬如,一年一次);因此,使用這種方法就比
較經濟,而使用死結預防法,死結避免法,或是死結偵測和恢復方法則比較費時。
另外,有些時候系統是處於凍結狀態,而非死結狀態。譬如,一個即時行程以最高
優先順序執行 (或其它行程在不可搶先排班行程下執行),而此行程卻不再把控制權
交還給作業系統。因此,系統必須對於非死結的情況有人為的恢復方法,而且可能
只使用這種技術來恢復死結。

60
8.4 預防死結
8.4.1 互斥
對不可共用的資源型式來說互斥 (mutual exclusion) 的條件必定成立。舉例來
說,印表機不可能同時給多個行程共用。反之,可共用的資源並不需以互斥的方式
存取,因而不會產生死結。唯讀檔案 (read-only file)就是一個良好的共用資源的例
證。若許多行程企圖同時去開啟一個唯讀檔案,則它們可同時取得此檔案。故任何
一行程並不需等候可共用的資源。一般說來,藉由排除此互斥條件預防死結的產生,
是不可能做到的。許多資源天生就是不可共用的。

8.4.2 佔用與等候
為了確保在系統中佔用與等候條件不成立,我們必須保證一個行程在要求一項資
源時,不可以佔用任何其它的資源。故可產生一個協定,那就是每一個行程在執行
之前必須先要求並取得所需的資源。而這項功能的製作方式可以將資源要求及配置
的系統呼叫,列在其它的系統呼叫之前優先處理。

或者,可規定每一個行程只有在其未佔用資源的狀況下才能夠要求資源。使得
每一行程雖然可能需要要求許多的資源並使用它們,但是在這行程要求其它資源之
前必須先釋放其所佔用的所有資源。

但是這兩種協定有兩個主要的缺點。第一,由於在大部份時間內許多資源可能
只被分配而未使用,故資源的使用率 (resource utilization) 非常低。譬如在我們所
舉的例子中,我們可以釋放磁帶機和磁碟檔案,然後再要求磁碟檔案和印表機,但
是只有當我們確信在這期間我們的資料能保存在磁碟檔案上的時候才能這樣作。如
果沒有把握,那麼不論使用那一種協定,我們都必須在一開始就提出對所有資源的
需求。

第二,可能產生飢餓現象。當一個行程需用的許多資源,可能因為其中至少有
一 項資源一直被其它行程所佔用,而使這行程將永遠處於等候的狀態。

8.4.3 不可搶先
第三個必要條件是已經配置出去的資源不可被別的行程搶先佔用。要確定這種
情況不會發生,就必須使用以下的協定方式。如果有一行程已經佔用某些資源,而
它還需求一個無法立即取得的資源 (也就是該行程必須等待),則其原本所持有的資
源應該可以由他人搶先使用。也就是說,這些資源事實上是已被釋放的。這些搶先

61
的資源就被加到行程正在等候的資源表上。這行程只有當它重新獲得原有的那些資
源以及正要求的那個新資源之後,才能重新開始。

這種協定的做法通常適用於資源狀態能輕易的被保存的情況之下,譬如 CPU
暫存器及記憶體空間。一般說來,它不適用於印表機、磁帶機等這一類的資源。

8.4.4 循環式等候
第四個也是最後一個死結的條件是循環式等候的條件。為了確保循環式等候的
條件不成立,我們對所有的資源型式強迫安排一個線性的順序。而行程要求資源時
必須依數字大小,遞增地提出要求。

因此,我們現在即可循下列協定以預防死結:每一個行程只能以遞增的順序去
要求資源。也就是說,若一個行程在起始時要求一資源型式 Ri 的任一數目的例證,
則稍後此行程所能要求之另一新的資源型式 Rj 的例證,必定有 F(Rj) > F(Ri) 的關
係。如果相同資源型式的數個例證被需要時,則必須對所有的這些資源發出一個單
獨的要求。若以上述所定義的函數為例,若有一行程欲同時使用磁帶機與印表機,
則其必先要求磁帶機,然後才可要求印表機。

8.5 避免死結
避免死結的另一種方法是要求有關於資源需求的額外資訊。舉例來說,在一個
有一台磁帶機和一台列表機的系統中,我們可能知道在前述二種資源被釋放之前行
程 P 將首先需求磁帶機。然後是列表機。在另一方面,行程 Q 將首先要求列表機,
然後是磁帶機。在備有每個行程需求和釋放約完整序列之知識時,針對每項需求我
們能夠決定那個行程應該等待。每項需求均要系統考慮目前可用的資源、目前分配
給每個行程的資源,以及每個行程未來的需求和釋放狀況,以決定目前的需求是否
能被滿足或者必須等待以避免未來可能發生的死結。

8.5.1 安全狀態
若此系統能以某種順序將其資源分配給各行程且仍能避免死結者,則稱狀態為
安全 (safe) 狀態。更嚴格地說,一個系統只有在安全序列 (safe sequence) 存在時才
算處於安全狀態。對每一個 Pi 及 j < i 的狀況下,若所有目前可用的資源能滿足 Pi
所要求的資源與 Pj 所佔用的資源之總和,稱此行程之序列之< P1, P2, …, Pn > 為目前
分配狀態下的一個安全序列。在此狀況下,若 Pi 所需資源仍未可用,則 Pi 將一直等
候到所有 Pj 完成為止。當它們已經完成後,Pi 便能得到其所需之所有資源,完成其
預定工作,然後釋放其佔用的所有資源且結束其工作。當 Pi 結束時,Pi+1 即可得到

62
其需要的所有資源,依此類推。如果此種序列不存在,則稱此系統處於不安全的狀
態。

安全狀態就不是死結狀態。相反地,死結狀態就是不安全的狀態。然而,並非
所不安全的狀態均是死結。一個不安全的狀態可能導致一個死結。只要 狀態是安
全的,作業系統便能避免不安全 (和死結) 的狀態。在一個不安全的狀態 下,作
業系統不能夠防範因行程需求資源而發生死結的情況,此時命運完全由行程的行為
來控制。

由這安全狀態的概念,吾人可定義避免的演算方法以確定系統永不會發生死
結。其觀念是確保系統將一定維持在安全的狀態下。開始時系統本來是在安全狀態
下。每當一個行程要求一個目前已經可用的資源時,系統必須決定是否要立刻把這
資源分配給這行程,還是要這行程進入等候狀態。只要這項分配使系統進入安全狀
態時才允許這要求。

注意,在這方法中,如果一個行程需求一個目前可用的資源,它可能仍然需要
等候。因此,資源的利用率可能低於不用避免死結演算法的情況。

8.5.2 資源配置圖演算法
除了要求與分配邊之外,我們在此介紹另一種稱為申請邊 (claim edge)。這申
請邊 Pi → Rj 表示出行程 Pi 可能在未來需要求資源 Rj。此種邊與要求邊類似,都具
方向性,但以虛線表之。當 Pi 要求資源 Rj 時,則需把申請邊 Pi → Rj 轉換成要求邊。
同理,在 Pi 釋放資源 Rj 時,必須將此分配邊 Rj → Pi 再轉換成為要求邊 Pi → Rj。
我們必須注意這些在系統中前置的資源。因此,在行程 Pi 開始執行前,所有的申請
邊均應已出現在資源配置圖上。故只有在伴隨著行程 Pi 的所有邊要求是申請邊的狀
況下,才可把申請邊 Pi → Rj 加入圖中。

假設行程 Pi 要求資源 Rj,又若這要求邊 Pi → Rj 轉換成分配邊 Rj → Pi 時並未


在資源配置圖中產生循環,此要求將被允許。注意在此是使用循環偵測 (cycle
detection) 的方法以檢查系統的安全性。在此圖中用這方法需要 n2 次運算,其中 n
為系統中行程的數目。

若無循環產生,則配置此資源將可使系統處於安全狀態。否則,將使系統進入
不安全狀態,而將使得行程 Pi 必須等待,以滿足其要求。

63
吾人將探討圖 8.5 的資源配置圖,以描述此運算法。假設 P2 要求 R2。雖然在此
時 R2 為可用,但是我們仍不能把它配置給 P2,因為這動作將在圖中產生循環(圖
8.6),而表示這系統處於不安全的狀態。要不然在 P1 求 R2 之後,就將立刻產生死結。

8.5.3 銀行家演算法
資源配置圖演算法並不適用於有多個例證的資源所形成的資源配置系統。接下
來我們所敘述的避免死結演算方法可以用在這種系統上,但是這種方法比資源配置
圖演算法效率低。這種演算法就是眾所周知的銀行家演算法。此名稱之選用是因為
這種演算法可用在銀行系統中,以確保銀行分配其可用的現金方式,不會使它不能
夠滿足所有顧客的要求。

當一個新的行程進入一系統時,它必須聲明可能需要的每一種資源型式的例證
的最大數量。此數量不能夠超過該系統中資源的總數。當一個使用者要求一組資源
時,就必須決定系統是否會因為這些資源的被佔用而脫離安全狀態。若是仍在安全
狀態下,這些資源就可被佔用;否則,這行程必須等候,直到其它的行程釋放了足
夠的資源。

在施行銀行家演算法時必需維護一些資料結構,這些資料結構可把資源配置系
統的狀態編碼。若令 n 為系統中的行程數目,而 m 為資源型式的數量,則吾人共需
下述的資料結構:

64
⚫ Available (可用的):為一長度為 m 的向量,可表示出每種型式的可用資源
量。若 Available [ j ] = k,即表示資源型式 Rj 中有 k 例證為可用。
⚫ Max (最大值):為一 n × m 的矩陣,可定義出各行程的最大需求量。
若 Max [ i, j ] =A,則表示出行程 Pi 至多可要求 k 個例證之資源型式 Rj。
⚫ Allocation (佔用;分配):為一 n × m 的矩陣,可定義現在每一行程所佔用
之各型式資源的數量。若 Allocation [ i, j ] = k,則行程 Pi 現已佔用資源型
式 Rj 中之 k 個例證。
⚫ Need (需求):為一 n × m 的矩陣,可表示出每一行程剩餘的需求量。
若 Need [ i, j ] = k,則表示行程 Pi 還需要再佔用資源型式 Rj 中之 k 個例證,
以完成其工作。注意,Need [ i, j ] = Max [ i, j ] - Allocation [ i, j ]。

8.5.3.1 安全演算法
利用下述演算法將可確定系統是否處於安全狀態:

這個演算法需要 m × n2 次運算以判斷系統是否處於安全狀態。

8.5.3.3 一個例子
考慮某一系統具有 5 個行程 {P0, P1, …, P4} 以及三種資源型式 {A, B, C}。資
源型式 A 有 10 個例證,資源型式 B 有 5 個例證,而資源型式 C 有 7 個例證。假定
在時間 T0 時該系統狀態如下:

65
矩陣 Need 的內容被定義為 Max-Allocation 如下:

我們聲言該系統此時是在安全的狀態。其實,序列 < P1, P3, P4 , P2, P0 > 能滿足


安全準則。現在假定行程 P1 要求資源型式 A 的一個額外例證和資源型式 C 的兩個
例證,所以 Request1 = (1, 0, 2)。為了決定這個要求是否能夠被立刻答應,我們首先
查知 Request ≦ Available (也就是(1, 0, 2) ≦ (3, 3, 2))為真。然後我們假定這個要
求已經實現並且到達下列的新狀態:

我們必須決定這新的系統狀態是否安全。因此我們執行我們的安全演算法,並
且發現序列 < P1, P3, P4 , P0, P2 > 滿足我們的安全條件。所以我們可以立刻答應行程
P1 的要求。

66
8.6 死結的偵測
8.6.1 具單一例證的資源型式
若所有資源均只含有一個例證,那麼我們可以使用資源配置圖的變形 (叫做等
候圖,wait-for graph) 定義出一種偵測死結的演算法。等候圖是把資源配置圖中的
資源型式端點移去,並除去相對應的邊而得到的。

更嚴格的說,在等候圖中自 Pi 至 Pj 的邊意味著 Pi 必須等候釋放其所需之資源。


對某項資源 Rq 而言,若且唯若在相應的資源配置圖中含有兩邊 Pi → Rq 與 Rq → Pj
則邊 Pi → Pj 必存在等候圖中。例如在圖 8.7 的例子中,吾人將可發現到一資源配
置與其所相應之等候圖。

如前述,若且唯若等候圖中含有循環,則系統必有死結。故為了偵測死結,這
系統必須維護整個等候圖,與週期性地使用一演算法以尋找圖中的循環。

尋找圖中之循環的演算法共需 n2 的運算,其中 n 為圖中的端點數量。

8.6.2 具有多個例證的資源型式
等待圖的方法並不適用於每個資源型式皆具有多個例證的資源配置系統。接下
來我們所敘述的死結偵測方法可以用在這種系統上。這種演算法所需要的時變資料
結構與銀行家演算法 (8.5.3 節)所需的非常類似:

67
⚫ Available:是一長度為 m 的向量,可表示每種資源型式的可用數量。
⚫ Allocation:為一 n × m 的矩陣,可定義出每一行程所佔用之各種資源型式
的數量。
⚫ Request:為一 n × m 的矩陣,可表示出每一個行程的現在需求。若 Request
[ i, j ] = k ,則表示出行程 Pi 還要資源型式 Rj 中之 k 個 instances。

在 8.5.3 節中已定義了兩個向量間小於的關係。為簡化符號,吾人再度以向量
Allocation 與 Request 分別表示矩陣 Allocationi 與 Requesti 的列向量。以下所討論的
偵測演算法將簡單的對所有的未完成行程,探討所有可能之配置序列。其方法如下
述(請與 8.5.3 節的銀行家演算法作一比較)

這個演算法需要 m × n2 次運算來檢查系統是否處於死結的狀態。

考慮一個具有 5 個行程 {P0, P1, …, P4}和三種資源型式 {A, B, C} 的系統。資


源型式 A 有 7 個例證,資源型式 B 有 2 個例證,而資源型式 C 有 6 個例證。假定
在時間 T0 時我們有下列資源分配狀態:

68
我們聲言此系統並非在死結狀態中。其實,如果我們執行我們的演算法,我們
將發現序列 < P0, P2, P3 , P1, P4 > 將使得對所有的 i 而言 Finish [ i ] = true 。

現在假定行程 P2 對型式 C 的一個例證做一額外的要求。則 Request 之矩陣,被


修改如下。

我們聲言系統現在是死結狀態。雖然我們能夠重新要求行程 P0 所佔用的資源,
但是可用資源的數目不足以實現其它行程要求。因此一個死結便存在了,包括行程
P1, P2, P3 及 P4。

8.6.3 偵測演算法之使用
我們應在何時使用偵測演算法?這答案取決於兩個因素:

1.多久會產生一次死結?
2.有多少個行程受死結發生的影響?

如果對每一項要求都求助於死結偵測演算法,當然在計算的時間方面將是可觀
的浪費。另一方面較不浪費的作法是以較低的週期求助死結偵測演算法,譬如一個
小時一次,或者每當 CPU 的利用率跌到百分之四十以下時。(死結終將損傷系統的
性能並將造成 CPU 利用率的低落。) 如果偵測演算法在任意的時間點上被呼用,
則在資源圖中可能會有許多的循環,此時,我們通常不知道這麼多陷入死結的行程
中究竟是那一行程造成了死結。

8.7 自死結恢復
8.7.1 行程的終止
為了藉取消一行程來消除死結,有兩種方法可以利用。在二種方法中系統皆重
新利用已分配給被終止的行程所有資源。

69
⚫ 取消所有死結中的行程:顯然這樣會終止死結循環,但是代價很大,因為這
些行程可能已經計算很長的一段時間,而這部份的計算結果必須被拋除,並
且稍後可能要重新計算。
⚫ 一次取消一個行程直到死結循環被消除為止:此方法需要可觀的超額費用,
因為在每個行程被取消後,必須求助於死結偵測演算法決定是否有任何行程
仍然在死結中。

8.7.2 資源的優先權
為了利用資源優先權來消除死結,我們逐步地從行程中奪取某些資源的優先權,
並且將這些資源給其它行程,直到死結循環被中止。
若需使用優先權以處理死結,則需考慮下列三項事情:

1. 選擇犧牲者 (select a victim):就是決定哪一個資源與哪一個行程需先行奪取之?


在行程被終止時,我們必須決定優先權次序以減少費用。影響費用的因素可能包
括死結行程在其執行期間所佔用的資源以及時間浪費的多寡。
2. 回撤 (rollback) :如果我們從一行程處優先取得一資源,我們對這行程有那些
事要做呢?很明顯地,該行程已無法再正常執行;它缺少了某些必要的資源。我
們必須將其撤回到某一安全狀態,再從這狀態重新開始。一般說來決定什麼是安
全的狀態是很困難的。最簡單的方法就是全部撤回(total rollback):將行程中斷,
以後再重新開始。然而較有效率的做法是將該行程撤回到能解開死結的狀態。換
句話說此方法要求系統必須將每一個行程執行中的狀態儲存起來。
3. 飢餓 (starvation):我們要如何才能確定飢餓的情形不會發生呢?也就是說,如何
才能保證資源不會總是從同一行程處優先取得?

70

You might also like