【操作系統】第十一章:死鎖和進程間通信

在這裏插入圖片描述
一個進程需要一個被其他進程佔用的資源,而另一個進程需要這一個進程已經擁有的資源。這種交叉就會產生所謂的死鎖,誰都無法進一步執行。
死鎖的原因是因爲進程的併發執行導致的。共享資源如果不加以某種約束和規則,就容易出現死鎖問題。

死鎖

系統模型

死鎖裏有需求方(進程)和需要的資源。資源比較抽象,可以是CPU也可以是內存單元,也可以是一些IO外設,甚至是一個進程中的數據結構共享的變量。

資源個數有N個,進程的資源有幾種訪問情況:
1.需要資源:首先要確保有足夠多的空閒資源,然後向空閒資源去申請。申請成功與否取決於空閒資源能否滿足需求。
2.請求得到滿足,拿到資源,資源從空閒狀態變成被使用狀態,一旦處於被使用狀態,其他進程就無法在這個時間點使用該資源。如果
3.進程使用資源的時間一定是有限的,當進程用完資源之後需要釋放資源。釋放後的資源恢復到空閒狀態。
在這裏插入圖片描述
在這裏插入圖片描述

資源的特徵:
1.首先可重複使用【free-use-free】
當進程獲得某個資源後,這個時間段內其他進程就不能使用了。沒有特殊情況下,OS不應該對擁有資源的進程暴力處理。
2.進程獲取資源後,使用一段時間就會被釋放,只有這樣才能讓資源被多個進程重複地使用
3.資源的類型(包括具體的物理層資源)和很抽象的資源(文件,數據庫,數據結構)都可以用資源的形式表示。如果出現某種情況,一個進程擁有一部分資源同時請求其他資源,且此時另一個進程也在進行同樣的操作,就有可能出現死鎖。因爲兩者都hold住了一部分資源。


資源的使用:
首先資源有創建和銷燬的資源。比如內存,如果將內存看作資源,創建內存和釋放內存就是對應的屬性(由內存管理單元完成)。
I/O緩衝區的種種都是一種資源,使用完後需要釋放,而且申請資源時有可能拿不到資源。
資源拿不到一般會處於等待狀態,使用資源的過程中存在進程管理和調度的過程。

資源分配圖

在這裏插入圖片描述

一個進程訪問一個資源有一個link,這些邊表示的兩個點。第一個點的集合是進程集合,用大P表示;第二個點的集合是資源的集合,用R表示。資源的集合包含所有資源的類型。
申請關係:一個進程訪問某個資源時,會在P和R之間設置一條有向邊,來表明Pi需要Rj的資源。
分配關係:當一個資源分配給一個進程後,進程可以使用這個資源,用Ri到Pj來表示當前資源被這個進程使用。
兩個不同集合之間的連接關係就可以形成一個圖。
在這裏插入圖片描述
由於資源還有個數的問題,在這個例子中,資源的個數設置爲4,意味着這個資源類型有四種、進程申請資源時可以指定這需要這種類型的資源多少個。資源指向進程表示當前資源有多少個分配給了所指進程。
在這裏插入圖片描述
分析:

序號 P R
1 擁有R2的一個資源,請求使用R1的一個資源 分配給P1和P2各一個資源
2 擁有R1的資源,請求使用R3的一個資源 分配給R2,導致P1的請求無法被滿足,P1被阻塞;
3 擁有R3的資源 分配給了P3,導致P2的請求無法被滿足,P2被阻塞
4 未使用 未使用

這個圖裏,第一階段:P1和P2暫時拿不到資源,這些資源受到P3限制。由於【資源的特徵】使用一段時間後會釋放,也就意味着R3在一定時間段後就會釋放,因爲P3在執行且沒有約束P3執行的,此後P2就被喚醒並執行,然後釋放R1喚醒P1,繼續執行。
第二階段:增加一條邊,就會發生死鎖。令P3也指向R2,,就會出現死鎖了。一旦出現死鎖則資源分配圖中一定有一個有向環。
因爲P1會等待P2,P2等待P3,P3也需要等待P1。相互等待,進入死鎖。這裏的大環:是P1等P2,P2等P3,P3等P1;小環是P2等P3,P3等P2。
在這裏插入圖片描述
在看一個例子:
這裏雖然有向圖成環,但是因爲這裏有P2和P4且沒有約束P2和P4執行的,所以一開始P3和P1進入相互等待狀態是確實的。
【注意互相等待不是死鎖!死鎖是指無限互相等待無法繼續執行,而這裏只是一段時間內互相等待,然後可以正常執行。】
但是根據有限時間內必然釋放資源這一特徵,P2和P4執行完畢後退出會釋放出R2和R1的資源,此時對於相互等待的兩個進程而言,有了新加入的資源可以調用,導致不需要繼續等待可以成功執行。也就說不會鎖死,會處於一段時間的死鎖狀態。
即便R1和R2中的資源種類只有一個是兩個,也就說允許該圖中R1和R2的資源類只有1個實例,另一個至少爲2,則該圖不會鎖死。(默認資源調用使用過1個資源,如果使用複數個資源仍然會進入死鎖)

死鎖一定會令資源分配圖存在一個有向環;但是存在有向環不一定會出現死鎖
資源分配圖中每個資源類只有一個實例則出現死鎖,如果每個資源類有幾個實例則可能死鎖


死鎖的特徵

在這裏插入圖片描述

互斥:出現死鎖之後,在一個時間段內只能有一個進程使用資源。A進程使用資源則B進程絕不會在同一時刻使用該資源
持有並等待:進程至少有一個資源在等待其他進程持有的額外資源。
無搶佔:資源一旦被某個進程持有,除非進程主動釋放資源,否則其他進程無法搶佔使用該資源
循環等待:進程和資源的連接關係形成了環。
只有這四個條件都滿足,才有可能出現死鎖。這是死鎖的必要條件,而不是充分條件。
在這裏插入圖片描述
以此來分析上圖。
左圖:滿足4個條件。右圖:P2/P4持有但是並不等待,不會出現死鎖

死鎖處理

在這裏插入圖片描述
約束性:死鎖預防最強,死鎖恢復最弱。
:::::::預防>避免>檢測>恢復
在這裏插入圖片描述

如果讓檢測隨意申請和釋放資源,難以避免會出現死鎖,如果確保系統永遠不進入死鎖狀態,那麼對性能將會有非常大的限制,所以需要由一定的辦法來處理死鎖。同時,也有鴕鳥算法,假裝沒有死鎖來進行處理,因爲判斷死鎖出現和恢復死鎖需要的開銷比較大,且確保不出現死鎖又會令系統性能大大受限,所以實際OS中也經常採用這種方法。

死鎖預防

讓死鎖不會出現。死鎖不會出現,也就意味着打破死鎖出現的某一個條件就可以。前面所說的一旦出現死鎖,4個必要條件一定存在,只要把其中的一個打破也就意味着死鎖不會出現了。
在這裏插入圖片描述

1.互斥:把共享資源變得不互斥,操作起來比較麻煩,而且會導致產生一些進程執行不確定性
2.佔用並等待:
不拿一部分資源,變成拿所有資源。我需要的資源要麼全部拿到要麼一點都不拿,確實不會出現死鎖。但是這種情況下,一個進程執行過程中,可能不同階段需要的資源數不多,但是整個過程中需要很多資源,佔用所有資源並開始執行的話,其他進程長時間等待引發飢餓現象,系統的利用率會很低。
在這裏插入圖片描述
3.不搶佔:爲了可以搶佔他人資源。因爲資源是互斥的,所以無法搶佔他人資源,這種情況下爲了獲取自身需要的資源,殺死現在持有所需資源的進程,暴力獲取資源。手段過於暴力
4.循環等待:對所有資源進行排序,要求進程按照資源順序來進行申請,打破環狀結構,根據資源ID的順序來申請資源。這種方法在嵌入操作系統裏用的比較多,傳統OS裏用得不多。

死鎖避免

在這裏插入圖片描述
約束性比預防差。並不是保證一定不出現死鎖,而是當進程運行過程中會申請資源,在申請時就會判斷獲得資源會不會出現死鎖,如果會,則不會分配資源給發起申請的進程。
1.確定進程需要的某個類型的資源的最大數量
2.用一種方法來限制提供分配資源的數量,提供的數量一定不會大於進程的最大需求。進程說要最多要10個資源,如果申請(2,4,5)三次超過10,則第三次請求將無法實現。
3.死鎖避免算法會動態檢查資源分配狀態,和死鎖預防的防止成環不同(只要遵循方法一定不出現死鎖),通過算法來確保不出現環形等待的情況。即如果分配資源給進程,可能出現環形等待時,就不會分配這個資源。 【判斷條件是是否成環而不是是否形成死鎖,約束性弱於預防】
在這裏插入圖片描述
安全狀態:針對所有進程,存在一個時間序列。這個序列表示進程的結束順序,第一個進程執行完畢第二進程再執行結束……按照這個序列執行下來一定會正常執行。只要按照這個序列執行的話,需要的資源都能得到滿足,有限時間內一定執行結束。
在這裏插入圖片描述
系統的狀態:安全狀態,不安全狀態(包含死鎖)。死鎖避免就是確保系統不進入不安全狀態。

銀行家算法

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

m是資源類型數量,不是資源數量!每一個資源類型還有一個量。這裏通過設計一個矩陣來表示:進程最多需要某一類資源的個數。

總需求空間:
Max[i,j]=kMax[i,j]=k
PiRjkP_i這個進程總共需要R_j類資源k個

剩餘空間:隨着運行過程中會動態變化的向量。Available表示剩餘空閒量。是長度爲m的向量。
Available[j]=kAvailable[j]=k
kRj有k個類型的R_j資源當前可用。
一旦進程不斷使用,RjR_j會不斷減少。

Allocation[i,j]=knmAllocation[i,j]=k, n*m矩陣
PiRjkP_i這個進程當前擁有R_j類資源k個

Need[i,j]=knmNeed[i,j]=k, n*m矩陣,未來需要量
PiRjkP_i這個進程當前還需要R_j類資源k個來完成任務

變量間的關係:
Need[i,j]=Max[i,j]Allocation[i,j]Need[i,j]=Max[i,j]-Allocation[i,j]
在這裏插入圖片描述
Work是當前系統中可用資源,一直在變。finish代表當前進程是否結束,F未結束,可能在sleep;T表示的擁有所需要的所有資源,可以正常執行並結束,一段時間後釋放資源。

一開始所有進程都是false,都在申請資源中,申請未必成功

e
Needi是一個向量,Pi所需要的各種類型的資源對比當前可用資源Work這個向量,如果Work所有項均大於Needi則說明進程Pi獲得了他想要的所有資源,可以開始執行,此時可以執行,將finish數字狀態變更爲true。因爲執行完畢了,所以要把資源釋放,也就說將現在持有的資源加入到當前可用資源的數組中。
如果所有進程最後變爲ture狀態,意味着該安全序列已完成,只要按照這個安全序列來執行進程,一定會處於安全狀態下完成所有進程。
但是,如果有一個進程的finish值爲false,則說明這系統不安全,一旦系統不安全,就不應該把資源分配給發出請求的進程。


在這裏插入圖片描述
實例1:
在這裏插入圖片描述
判斷是否安全:我們要判斷是否有一個執行序列能夠使進程都可以正常結束。
首先V:
1.要找到一個Need<V的向量,發現T2<Need
2.T2正常結束,V={6,2,3}(T2的所需資源釋放到可用資源中)
3.此時T1和T3都是可以滿足的,這裏選擇哪一個都可以,因爲我們 要找的就是一個能夠滿足安全的時間序列
4.資源都可以滿足,T3和T4也可以隨意挑一個
5.找到一個安全執行序列,2134

在這裏插入圖片描述


接下來分析一下這個不安全序列的例子
在這裏插入圖片描述如果這個時候T1發出請求,請求爲1 0 1的資源分配。如果這時候將1 0 1資源分配給T1,就會發生:
在這裏插入圖片描述
此時V無法滿足任何一個線程的需求來繼續執行,說明這種排序方式是不安全的,跳回到上一步。

死鎖檢測

在這裏插入圖片描述
進一步放寬條件,允許系統進入死鎖狀態,到了某一個階段會判斷一下是否當前出現死鎖。如果判斷出是死鎖,就啓動恢復機制。這裏將死鎖檢測的階段放到了系統運行中而不是每次發出請求時。所以折中情況下系統有可能處於死鎖狀態下,我們需要查出死鎖現象。

判斷方法1:進程等待圖
在這裏插入圖片描述
某一個進程需要一個資源且需要的資源被另一個進程擁有,則會在擁有這個資源的進程之間做一個有向連線,將資源分配圖變成進程等待圖,然後看這張圖有沒有環,如果有就可能死鎖了。

判斷方法2:死鎖檢測算法
在這裏插入圖片描述
和銀行家算法的死鎖避免算法類似。系統會週期性執行這個算法來檢測是否會處於死鎖。
在這裏插入圖片描述
算法複雜度比較大,這就是爲什麼會有鴕鳥算法來忽略死鎖問題的原因之一。對於銀行家算法來說,它需要提前知道每一個進程需要的最大資源個數是多少,這一點對於一般程序而言信息很難獲得且開銷比較大,使得銀行家算法在實際操作系統裏幾乎沒有用到。所以死鎖檢測的過程更多是調試OS和APP時會用到,真正的跑某個系統特別是單機系統一般不會用死鎖檢測算法。
在這裏插入圖片描述
由於0和2的需求量是0,所以可以先執行其中的任意一個。執行完畢後,回收已分配資源。執行一圈下來發現,所有進程都可以順利執行完畢.
在這裏插入圖片描述
執行GIF:
在這裏插入圖片描述


下面在看一個死鎖的例子,對上述例子微調
在這裏插入圖片描述
發現T0執行後,即便回收T0的資源,剩餘的線程也沒有任何一個可以被滿足需求,此時就出現了死鎖。死鎖包含所以沒有正常執行完畢的進程或線程,這裏是T1~ T4.


在這裏插入圖片描述
在用這個算法的時候,因爲開銷比較大,所以需要考慮頻度問題。過大影響系統性能,過小可能效果不如預期。以及哪些進程需要被殺死或者回滾,本着公平的原則爲什麼要殺死A而不是B等都是需要專門研究的。大多數情況下,死鎖檢測還是用於開發階段。

死鎖恢復

在這裏插入圖片描述
如果整個系統sleep或忙等,我們爲了打破這種狀態,需要死鎖恢復。
1.殺死所有死鎖進程。過於暴力
2.殺死其中一個進程,本着公平的原則需要設立一個規則。基於規則不同,殺死順序不同。
3.死鎖是一種少見的現象,如果一直死鎖說明系統有問題。通過重啓或者殺死可以在長時間段內有效解決問題。
4.回滾也存在飢餓問題。

進程間通信IPC

通信

在這裏插入圖片描述
進程之間要相對保持獨立,一個進程不能隨便訪問另一個進程的內存地址空間。另一方面,進程之間可能需要協作完成某些大的任務,這時候需要有一些數據傳遞和交互,所以在保存進程獨立性的同時也要進行通信。
IPC有很多種機制,最基本的有發送和接受兩種功能。如果進程之間想要收發消息,他們之間需要建立一個通信的個管道,這個管道有很多種情況,可以基於內存、硬件、邏輯資源等。
在這裏插入圖片描述
間接通信和直接通信。
在這裏插入圖片描述
直接通信必須明確收發兩方的正確性。同時要建立鏈路,一般需要OS支持,因爲鏈路打破了進程之間的隔離,沒有OS的支持幾乎無法做到。
在這裏插入圖片描述
把消息發送到OS指定的共享區域,並且指定區域接受。對於發送方而言不需要關注誰收,接收方而言也不用關注誰送。
操作:
在這裏插入圖片描述


在這裏插入圖片描述
阻塞發送:發送者在發送消息後進入等待,直到接收者成功收到
阻塞接受:接收者在請求接收消息後進入等待,直到成功收到一個消息
非阻塞發送:發送者在消息發送後,可立即進行其他操作
非阻塞接收:沒有消息發送時,接收者在請求接收消息後,接收不到任何消息


信號/管道/消息隊列/共享內存

把消息採取某種手段將他緩存起來。
緩存最主要的目的是提高效率,來避免發送方和接收方之間的不匹配(可能某些時刻發的快收的慢,或者收得快發的慢)。如果把臨時不能處理的數據放在一塊緩存區會提高效率。
在這裏插入圖片描述
1.0容量。如果是直接發送,必須等待接收方接收。類比於阻塞發送方式/同步發送方式,如果立刻返回,由於緩存爲0則有可能數據丟失,接收方收不到消息。
2.有限容量:更多的實際情況,緩存有限,如果緩存已滿發送方必須等待;如果緩存已空必須等待。
3.無限:發送方不需等待,接收方如果沒有數據還是需要等或者返回一個錯誤信息。


信號Signal

在這裏插入圖片描述
對比於硬件中斷。信號相當於軟件中斷,打斷當前運行的應用程序,發出通知信息去處理其他事情。

接收到信號後:
1.退出執行。2.忽略信號,不處理。
3.指定一個專門的信號處理函數。一旦信號來了,特殊函數會響應對應的處理,由程序員管理。
信號很靈活但是不適合用來傳遞數據,因爲信號是個很小的位比特,不是數據交換而是通知作用,效率高,異步打斷機制。處理完信號要求的任務後,一般會回到被打斷的程序重新或繼續執行。
在這裏插入圖片描述
1.應用程序如果要針對某個信號做單獨處理的話,則程序執行開始時要註冊一個針對某一類信號的處理函數。然後作爲系統調用發給OS,OS看到後,當產生對應信號時會調用此專門的信號處理函數來執行。
2.由OS來完成。當OS收到信號時或者處理信號時,會處於內核態。當從內核態返回到用戶態去執行信號處理函數時,要把系統調用返回的用戶空間的堆棧進行修改,使得本來應該返回到調用系統調用的下一條語句處改到信號處理函數的入口。同時再把信號處理函數執行後要執行的地址作爲後面棧針的返回地址。這樣一旦從OS內核返回到應用程序時,會根據留的棧信息跳到信號處理函數的入口去執行,這時候回到用戶態,函數執行完畢後,會繼續返回到被打斷的地方。
爲了實現信號機制需要修改應用程序的堆棧,這一點一般應用程序不會這麼做,木馬病毒會這樣做來完成一些特定的功能。

管道

在這裏插入圖片描述

管道是用於數據交換的。早期管道機制:從一個程序結束後接入另一個程序的開始,如此可以令多個小程序形成一個整體實現大的功能。程序本身並不會在編寫的時候考慮這些問題,而是由管道來完成這些操作的。
在這裏插入圖片描述
Shell收到了一條命令(ls|more),會發現是兩個命令連接到一起,這兩個命令對應兩個進程。ls的輸出並沒有輸出到屏幕上,而是輸出到管道里,這個管道是內存中的一塊緩存,ls現在是往管道內部寫,more來接收。
管道 具有雙重身份,對於ls他認爲是在往屏幕上輸出,對於more他認爲從屏幕上的數據在給他輸入。管道實際是內核中的一塊緩存。管道的緩存是有限的,如果more來不及填滿緩存填滿後,會出現阻塞。同樣more也會因爲管道內無數據而阻塞。
ls和more能協同是因爲有個共同的父進程shell,子進程可以繼承父進程的一些資源,這裏面管道是一種文件形式存在的,通過這種方式就能完成這種功能。

消息隊列

在這裏插入圖片描述
管道的缺點:管道是父進程幫子進程接的通道,如果進程之間沒有父子關係,管道就無法工作了。管道里的數據是一種字節流,沒有結構化的表示方式,需要接收方解析數據,額外開銷。

而這些缺點消息隊列可以克服,可以將不相干的進程通過消息隊列來傳遞數據;傳輸的數據是結構化的數據而不是字節流。

相同點:緩存有容量限制,有阻塞情況發生。

共享內存

在這裏插入圖片描述
是一種直接通信的方式,兩個進程都可以訪問到一個特殊的內存空間。一個進程寫入這個內存空間,另一個進程可以馬上看到。中間不需要做發送和接受操作。直接讀寫內存就可以完成數據共享,相對於前面的幾種方式而言,傳輸的量最大,速度最快,不需要OS介入。只是最開始時需要創建一塊共享區域。方便高效沒有多餘拷貝。
不足:必須同步互斥機制來保證數據的安全性。多個進程同時寫會出現意想不到的錯誤。需要內存管理的充分支持(頁表等相應機制)
在這裏插入圖片描述


HINT)TCP協議棧實現在內核態,在網絡原理裏會講,在操作系統課中不展開講解。


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