OS之进程管理 --- 死锁
什么是死鎖
在正常操作模式下,進程按如下順序來使用資源:
- 申請:進程請求資源
- 使用:進程對資源進行操作
- 釋放:進程釋放資源
當一組進程中的每一個進程度在等待一個事件,而這事件只能有一組進程的另一個進程引起,那么這組進程就處于死鎖狀態。
死鎖的特征
我們來看一個例子:互斥鎖的死鎖
采用互斥鎖的多線程Pthreads程序可能發生死鎖。函數pthread_mutex_init()將一個互斥鎖初始化為未加鎖。函數pthread_mutex_lock()和pthread_mutex_unlock()分別獲得和釋放互斥鎖,當一個線程試圖獲得一個已加鎖的互斥鎖時,它會堵塞,知道該互斥鎖的所有者調用pthread_mutex_unlock()
創建兩個互斥鎖:
創建兩個線程,即thread_one和thread_two,這些線程都能訪問這兩個互斥鎖,thread_one和thread_two分別運行在do_work_one()和do_work_two()中:
void *do_work_one(void *param) {pthread_mutex_lock(&first_mutex);pthread_mutex_lock(&second_mutex);pthread_mutex_unlock(&second_mutex);pthread_mutex_unlock(&first_mutex);pthread_exit(0); }void *do_work_two(void *param) {pthread_mutex_lock(&second_mutex);pthread_mutex_lock(&first_mutex);pthread_mutex_unlock(&first_mutex);pthread_mutex_unlock(&second_mutex);pthread_exit(0); }這個例子中因為兩個線程獲取和釋放互斥鎖的順序不同,有可能造成死鎖。注意:即使有可能死鎖,但是不一定會發生。
必要條件
如果一個系統中下面四個條件同時成立,那么就會引起死鎖:
- 互斥:至少有一個資源必須處于非共享模式,即一次只有一個進程可以使用。
- 占有并等待:一個進程應占有至少一個資源,并等待另一個資源,而該資源為其他進程所占有。
- 非搶占:資源不能被搶占,即資源只能被進程完成任務后自愿釋放。
- 循環等待:有一組進程 {P0P_0P0?,P1P_1P1?,…,PnP_nPn?},P0P_0P0?等待的資源為P1P_1P1?占有,P1P_1P1?等待的資源為P2P_2P2?所占有,…,Pn?1P_{n-1}Pn?1?等待的資源被PnP_nPn?所占有。
強調:只有這四個條件同時成立時才會出現死鎖。
資源分配圖
資源分配圖是一種有向圖,該圖包括一個節點集合V和一個邊集合E。節點集合V可以分為兩種類型:P={P1P_1P1?,P2P_2P2?,…,PnP_nPn?}(系統中所有活動進程的集合)和R={R1R_1R1?,R2R_2R2?,…,RmR_mRm?}(系統中所有資源類型的集合).
從進程PiP_iPi?到資源類型RjR_jRj?的有向邊記為PiP_iPi?→\rightarrow→RjR_jRj?,他表示進程PiP_iPi?已經申請了資源類型RjR_jRj?的一個實例,并且正在等待這個資源;RjR_jRj?→\rightarrow→PiP_iPi?表示資源的一個實例已經分配給進程PiP_iPi?了。有向邊PiP_iPi?→\rightarrow→RjR_jRj?稱為申請邊,RjR_jRj?→\rightarrow→PiP_iPi?稱為分配邊。
具體的資源分配圖詳解:資源分配圖
根據資源分配圖的定義,可以證明:如果分配圖沒有環,那么系統沒有進程死鎖。如果分配圖中存在環,那么可能存在死鎖。
如果每個資源類型剛好有一個實例,那么有環就意味著已經出現死鎖。如果環上每個類型只有一個實例,那么就出現了死鎖。換上的進程就死鎖。在這種情況下圖中的環就是死鎖存在的充分且必要條件。
如果每個資源類型有多個實例,那么有環并不意味這已經出現了死鎖,在這種情況下,圖中的環就是死鎖存在的必要不充分條件。
死鎖的處理方法
一般來說,處理死鎖的問題有三種:
- 通過協議來預防或避免死鎖,確保系統不會進入死鎖狀態。
- 可以允許系統進入死鎖狀態,然后檢測并加以恢復
- 可以忽視這個問題,認為死鎖不可能在系統內發生。
第三種方法被大多數操作系統所采用,包括Linux和Windows。
死鎖預防
互斥
互斥條件必須成立,也就是說,至少一個資源應該是非共享的。實際當中,操作系統中一定需要互斥資源,所有通過解決互斥來打破死鎖的四個條件是不合理的。
持有且等待
當每個進程申請一個資源的時候,它不能占有其他資源。
一種可以采用的協議:每個進程在執行前申請并獲得所有的資源。即要求進程申請資源的系統調用在其他系統調用之前完成。
另外一種協議:允許進程僅在沒有資源時才可申請資源。一個進程可申請一些資源并使用他們,在申請更多的其他資源之前,它應該釋放現在已經分配的資源。
兩者的差異:第一種要求在進程執行前申請執行所需的所有的資源,在執行過程中不能夠在申請,執行完成后釋放所占有的所有資源;第二種要求先分配給進程當前可以執行的資源數量,進程執行過程中如果缺少資源進請求,在請求時先釋放自己占有的資源。
缺點:
- 資源利用率比較低
因為許多資源坑你已經分配,但是很長時間沒有使用 - 可能發生饑餓
一個進程如需要多個常用資源,可能必須永久等待,因為在它所需要的資源中至少有一個已經分配給其他進程。
無搶占
打破“不能搶占已分配的資源”,為了保證這個條件不成立,采用協議:如果一個進程持有資源并申請另外一個不能立即分配的資源(也就是說,這個進程應該等待),那么他現在分配的資源都可被搶占。可以理解為這些資源都被隱式的釋放了。被搶占資源添加到進程等待的資源列表中。只有當進程獲得其原有資源和申請的新資源時,他才可以重新執行。
循環等待
確保循環等待條件不成立的一個方法是:對所有資源類型進行完全排序,而且要求每個進程按遞增順序來申請資源。
假設資源類型的集合是R={R1R_1R1?, R2R_2R2?,…,RmR_mRm?},為每個資源類型分配一個唯一整數,這樣可以比較兩個資源以確定他們的先后順序。
我們采用如下協議:每個進程只能按遞增順序申請資源。即一個進程開始可申請任何數量的資源類型RiR_iRi?的實例。換句話說,要求當一個進程申請資源類型RjR_jRj?時,他應該先釋放所有資源RiR_iRi?(F(RjR_jRj?) ≤\leq≤ F(RiR_iRi?))。如果需要同一類型的多個實例,那么應該一起申請。
死鎖避免
死鎖避免算法動態的檢查資源分配狀態,以便確保遵化你等待條件不能成立。
安全狀態
如果系統能夠按一定順序來為每個進程分配資源,仍然避免死鎖,那么系統的狀態就是安全的。更正式的說,只有存在一個安全序列,系統才處于安全狀態。
進程序列<P1P_1P1?, P2P_2P2?, … , PnP_nPn?>在當前分配狀態下為安全序列是指:對于每個PiP_iPi?,PiP_iPi?仍然可以申請資源數小于當前可用資源加上所有進程PjP_jPj?(j < i)所占有的資源。在這種情況下,進程PiP_iPi?需要的資源即使不能立即可用,那么PiP_iPi?可以等待直到所有PjP_jPj?釋放資源。當他們完成后,PiP_iPi?可得到需要的所有資源完成給定任務,返回分配的資源,最后終止。當PiP_iPi?終止時,Pi+1P_{i+1}Pi+1?可得到他需要的資源,如此進行,如果沒有這樣的序列存在,那么系統狀態就是非安全的。
安全狀態不是死鎖狀態,相反,死鎖狀態是非安全狀態。然而不是所有的非安全狀態都能導致死鎖狀態。非安全狀態可能導致死鎖。只有在安全狀態下,操作系統就能避免非安全(和死鎖)狀態。在非安全狀態下,操作系統不能阻止進程申請資源,因而可能死鎖。進程行為控制了非安全狀態。
資源分配圖算法
除了申請邊和分配邊,引入需求邊,需求邊PiP_iPi?→\rightarrow→RjR_jRj?表示進程PiP_iPi?可能在將來某個時候申請資源RjR_jRj?。當進程PiP_iPi?申請資源時RjR_jRj?時,需求邊PiP_iPi?→\rightarrow→RjR_jRj?變成了申請邊,類似當進程PiP_iPi?釋放RjR_jRj?時,分配邊RjR_jRj?→\rightarrow→PiP_iPi?變成了需求邊PiP_iPi?→\rightarrow→RjR_jRj?。
現在假設進程PiP_iPi?申請資源RjR_jRj?,只有在將申請邊PiP_iPi?→\rightarrow→RjR_jRj?變成分配邊RjR_jRj?→\rightarrow→PiP_iPi?并且不會導致資源分配圖形成環時,才能允許申請。
如果沒有環存在,那么資源的分配會使系統處于安全狀態。如果有環存在,那么分配會導致系統處于非安全狀態,這種情況下,進程PiP_iPi?應該等待資源申請。
銀行家算法
設n為系統進程的數量,m為資源類型的種類。定義數據結構:
Available:長度為m的向量,表示每種資源的可用實例數量,如果Available[j]=k,那么資源類型RjR_jRj?有k個可用實例。
Max:n×\times×m矩陣,定義每個進程的最大需求。如果Max[i][j]=k,那么進程PiP_iPi?最多可申請源類型RjR_jRj?的k個實例
Allocation:n×\times×m矩陣,定義每個進程現在分配的每種資源類型的實例數量,如果Allocation[i][j]=k,那么進程PiP_iPi?現在已分配了資源類型RjR_jRj?的k個實例。
Need:n×\times×m矩陣,表示每個進程還需要的剩余資源。如果Need[i][j]=k,那么進程PiP_iPi?還可能申請k個資源類型RjR_jRj?的實例。
注意:Need[i][j]=Max[i][j] - Allocation[i][j]。
安全算法
通過安全算法以求出系統是否處于安全狀態,描述如下:
a. Finish[i] == false
b. NeediNeed_iNeedi? ≤\leq≤Work
如果沒有這樣的i存在,那么轉到第4步
Finish[i]=true
返回第2步
這個算法可能需要m ×\times× n2n^2n2數量級的操作,以確定系統狀態是否安全。
資源請求算法
現在描述是否安全允許請求的算法:
設RequestiRequest_iRequesti?為進程PiP_iPi?的請求向量。如果RequestiRequest_iRequesti?[j]==k,那么進程PiP_iPi?需要的資源類型RjR_jRj?的實例數量為k,當進程PiP_iPi?做出這一資源請求時,采取如下動作:
Available = Available - RequestiRequest_iRequesti?
AllocationiAllocation_iAllocationi? = AllocationiAllocation_iAllocationi? + RequestiRequest_iRequesti?
NeediNeed_iNeedi? = NeediNeed_iNeedi? - RequestiRequest_iRequesti?
如果新的資源分配狀態是安全的,那么交易完成且進程PiP_iPi?可分配到需要的資源。然而,如果新狀態不安全,那么進程PiP_iPi?應等待RequestiRequest_iRequesti?并恢復到原來的資源分配狀態。
死鎖檢測
如果一個系統既不采用死鎖預防算法也不采用死鎖避免算法,那么死鎖可能出現,在這種情況下,系統可以提供:
- 一個用來檢查系統狀態從而確定是否出現死鎖的算法
- 一個用來從死鎖狀態恢復的算法
每個資源類型只有單個實例
如果所有資源類型只有單個實例,那么可以通過等待圖來作為死鎖檢測算法。
等待圖就是從資源分配圖中,刪除所有資源類型節點,合并適當邊,從而得到等待圖
當且僅當在等待圖中有一個環,系統死鎖。為了檢測死鎖,系統需要維護等待圖,并周期調用用于搜索圖中環的算法。
注意:等待圖算法不適用于每種資源類型可有多個實例的資源分配系統
每種資源類型可有多個實例
定義如下數據結構:
Available:長度為m的向量,表示各種資源的可用實例數量
Allocation:n×\times×m矩陣,表示每個進程的每種資源的當前分配數量
Request:n×\times×m矩陣,表示當前每個進程的每種資源的當前請求,如果Request[i][j]=k,那么PiP_iPi?現在正在請求資源類型RjR_jRj?的k個實例。
可以看出該算法和銀行家算法類似,可以進行比較理解,該算法的描述如下:
a. Finish[i]=false
b. RequestiRequest_iRequesti? ≤\leq≤ Work
如果沒有這樣的i,則轉到第4步
Finish[i] = true
轉到第2步
死鎖恢復
進程終止
通過終止進程來消除死鎖,有兩種方法,都是通過允許系統回收終止進程的所有分配資源:
- 終止所有死鎖進程
這種方法代價很大。這些死鎖進程可能已經計算了較長時間,這些部分計算的結果也要放棄,并且以后可能還要重新計算 - 一次終止一個進程,知道消除死鎖循環為止
這種方法的開銷相當大,因為每次終止一個進程,都要調用死鎖檢測算法,以確定是否仍有進程處于死鎖。
資源搶占
通過資源搶占來消除死鎖,我們不斷搶占一些進程的資源以便給其他進程使用,直到死鎖循環被打破為止。如果要采用搶占來處理死鎖,需要考慮三個問題:
- 選擇犧牲進程
- 回滾
被搶占的進程不能繼續正常執行,我們應將該進程回滾到某個安全狀態,以便從該狀態重啟進程。 - 饑餓
即如何保證資源不會總是從同一進程中被搶占
參見:《操作系統概念》(第九版)
轉載于:https://www.cnblogs.com/lishanlei/p/10707691.html
總結
以上是生活随笔為你收集整理的OS之进程管理 --- 死锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt高级——QTestLib单元测试框架
- 下一篇: 分布式之redis