作業系統學習三:程序排程與死鎖 以及銀行家演算法避免死鎖 .NetCore實現
前言
這是作業系統學習的第三篇啦,關於程序排程有很多內容,作業系統在排程程序的時候最容易遇到的問題就是死鎖了, 銀行家演算法 是一個典型的避免死鎖演算法。
死鎖的概念
先來了解一下死鎖的基本概念: 一組競爭系統資源或相互通訊的程序相互的“永久”阻塞。若無外力作用,這組程序將永遠不能繼續執行。
看下面兩幅圖片,左邊是可能產生死鎖的狀態,四輛汽車(程序)要競爭同一個資源(通過路口),如果系統排程不當,就會陷入死鎖狀態,如右圖 (每輛車佔據一個車道(資源),所需車道(資源)被另一輛車佔據。) 。

1543925673525.png
產生死鎖的原因
產生死鎖的原因主要有兩點:
-
資源數 < 要求該種資源的程序數(資源競爭)
-
程序的推進順序不恰當
如下圖:A、B分別代表某種資源,假設都只有一個,程序P先佔用了資源A,接著程序Q佔用了資源B,後面程序P再想要資源B就拿不到,程序Q想要資源A也拿不到,系統就陷入死鎖狀態了。

再看看下面這個圖,同樣說明了程序推進順序不恰當導致的結果:
- (1)、(2) 、(4) 、(5)正常執行
- (3) 、(6)發生死鎖

修改了程序的推進順序之後,不會陷入死鎖,可以正常執行,如下圖:

產生死鎖的條件
- 互斥條件 :程序所競爭的資源必須被互斥使用。
互斥是資源的固有屬性,不可禁止。
-
請求保持條件:當前已擁有資源的程序仍能申請新的資源,當被阻塞時,對已獲得的資源保持不放。
-
不剝奪條件:程序已獲得的資源只能在使用完時自行釋放,而不能被搶佔。
-
環路條件:存在一個至少包含兩個程序的迴圈等待鏈,鏈中的每個程序都正在等待下一個程序佔有的資源。
-
前面三個條件是必要條件, 環路條件 是必要條件。
-
第一個 互斥 是資源的固有屬性,沒辦法禁止的。
-
只要破壞後面3個條件中的任意條件,就可以預防或者避免死鎖。
銀行家演算法
這是仿照銀行發放貸款時採取的控制方式而設計的一種死鎖避免演算法。其特點是所有客戶的信用額度可以超過銀行的全部資本。
銀行家演算法的目標:**所有客戶的信用額度之和可以超過銀行的全部資本,這就是槓桿。 **
主要思想
銀行家演算法的主要思想如下:
- 當一個使用者對資金的最大的需求量(即信用額度)不超過銀行家現有的資金時就可以接納該使用者。
- 使用者可以分期貸款,但貸款的總數不能超過最大需求量。
- 當銀行家現有的資金不能滿足使用者的尚需貸款時,對使用者的貸款可推遲支付,但總能使使用者在有限的時間裡得到貸款。
- 當用戶得到所需的全部資金後,一定能在有限的時間裡歸還所有資金。 (客戶信用良好,都能安期還款)
舉例
假定某銀行的全部流動資金為10000元。客戶A申請4000元的信用額度,客戶B申請6000元的信用額度,客戶C申請10000元的信用額度,由於都沒有超過銀行的流動資金,予以批准。客戶D申請12000元的信用額度,銀行拒絕。假定A、B、C來銀行提出下列貸款申請:
- A要求貸款2000元
- B要貸款4000元
- C要貸款3000元
銀行策略如下:
-
只要銀行還有錢就發放貸款
如果採用這種策略,發放貸款後銀行剩下1000元,將無法滿足任何一個客戶的信用額度,從而造成銀行呆賬。
-
需要考慮發放貸款後銀行面臨的風險(如果銀行無法滿足所有客戶的信用額度,將導致無法收回貸款)
如果採用這種策略,先發放A和B的貸款後銀行剩下4000元,推遲支付C的貸款,剩下的4000元可以滿足A或B客戶的信用額度。
進一步理解
我們把作業系統看作是銀行家,作業系統管理的資源相當於是銀行家管理的資金,當程序提出資源請求時,系統檢查:
Available Max Allocation Need
銀行家演算法執行的流程如下:
- 進行資源預分配
- 實施安全檢測
- 安全:真正資源分配
- 不安全:回到預分配前狀態

1543927204284.png
演算法實現
下面是銀行家演算法整個實現過程的流程圖:

實現相關資料結構的說明:
1. 可利用資源向量Available ,它是一個含有m個元素的陣列,其中的每一個元素代表一類可利用的資源的數目,其初始值是系統中所配置的該類全部可用資源數目。其數值隨該類資源的分配和回收而動態地改變。如果 Available[j]=k
,表示系統中現有j類資源k個。
2. 最大需求矩陣Max,這是一個n×m的矩陣,它定義了系統中n個程序中的每一個程序對m類資源的最大需求。如果 Max[i][j]=k
,表示程序i需要j類資源的最大數目為k。
3. 分配矩陣Allocation,這是一個 n×m
的矩陣,它定義了系統中的每類資源當前分配到每一個程序的資源數。如果 Allocation[i][j]=k
,表示程序i當前已經分到j類資源的數目為k個。 Allocation[i]
表示程序i的分配向量。
4. 需求矩陣Need,這是一個n×m的矩陣,用以表示每個程序還需要的各類資源的數目。如果 Need[i][j]=k
,表示程序i還需要j類資源k個,才能完成其任務。 Need[i]
表示程序i的需求向量。
上述三個矩陣間存在關係: Need[i][j]=Max[i][j]-Allocation[i][j];
演算法過程說明
Request是程序i的請求向量。 Request[j]=k
表示程序i請求分配j類資源k個。當程序i發出資源請求後,系統按下述步驟進行檢查:
1. 如果 Request ≤Need[i]
,則轉向步驟2;否則,認為出錯,因為它所請求的資源數已超過它當前的最大需求量。
2. 如果 Request ≤Available
,則轉向步驟3;否則,表示系統中尚無足夠的資源滿足程序i的申請,程序i必須等待。
3. 系統試探性地把資源分配給程序i,並修改下面資料結構中的數值:
Available = Available - Request Allocation[i]= Allocation[i]+ Request Need[i]= Need[i] - Request
4. 系統執行安全性演算法,檢查此次資源分配後,系統是否處於安全狀態。如果安全才正式將資源分配給程序i,以完成本次分配;否則,將試探分配作廢,恢復原來的資源分配狀態,讓程序i等待。
安全性測試演算法
1. 設定兩個向量。
Work
:它表示系統可提供給程序繼續執行的各類資源數目,它包含m個元素,開始執行安全性演算法時, Work = Available
Finish
:它表示系統是否有足夠的資源分配給程序,使之執行完成,開始Finish[i]=false;當有足夠資源分配給程序i時,令 Finish[i]=true
2. 從程序集合中找到一個能滿足下述條件的程序。
Finish[i]= = false Need[i]≤work
如找到則執行步驟3;否則,執行步驟4;
3. 當程序i獲得資源後,可順利執行直到完成,並釋放出分配給它的資源,故應執行
Work = work + Allocation[i] Finish[i]=true 轉向步驟2
4. 若所有程序的 Finish[i]
都為 true
,則表示系統處於安全狀態;否則,系統處於不安全狀態。
實現程式碼
好啦,關於概念和原理都講清楚了,接下來貼上程式碼。
程式碼裡已經寫了詳細的註釋了,雖然是面向過程的寫法,但是結構也很清晰。
首先定義程序類
namespace OperatingSystemExperiment.Exp3 { public class ProcessExp3 { public int Id; /// <summary> /// 程序最大(各類)資源需求數:信用額度 /// </summary> public int[] Max; /// <summary> /// 已分配給程序的資源:貸款 /// </summary> public int[] Allocation; /// <summary> /// 程序還需要的資源:信用額度 - 貸款 /// </summary> public int[] Need; public ProcessExp3(int id) => this.Id = id; public void EvaluateNeedResource() { Need = new int[Max.Length]; for (var i = 0; i < Max.Length; i++) { Need[i] = Max[i] - Allocation[i]; } } } }
演算法實現程式碼:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace OperatingSystemExperiment.Exp3 { public class Main { private int _resourceClassesCount = 0; private int _processCount = 0; private List<ProcessExp3> _processes = new List<ProcessExp3>(); /// <summary> /// 系統全部可分配資源:銀行流動資金 /// </summary> private List<int> _resource = new List<int>(); /// <summary> /// 系統剩餘可分配資源 /// </summary> private List<int> _available = new List<int>(); private Main() { // 獲取當前系統資源分配狀態 LoadAllAvailableResource(); LoadProcessMaxResource(); var continueFlag = true; while (continueFlag) { mainLoop: // 獲取各程序已分配資源 LoadAllocationResource(); // 評估每個程序還需要的資源 EvaluateNeedResource(); // 列印各資料結構當前值 PrintStatus(); Console.Write("請輸入要操作的程序號:"); if (!int.TryParse(Console.ReadLine(), out var procId)) { Console.WriteLine("\n請輸入數字!"); if (QueryExit()) Environment.Exit(0); else goto mainLoop; } if (procId < 0 || procId >= _processes.Count) { Console.WriteLine("\n不存在程序號為 {0} 的程序!", procId); if (QueryExit()) Environment.Exit(0); else goto mainLoop; } Console.Write("請輸入資源請求向量:"); var request = Console.ReadLine(); var requestVector = Array.ConvertAll(request?.Split(' '), int.Parse); // 檢查資源請求是否合理 var proc = _processes[procId]; Console.WriteLine("銀行家演算法檢驗中..."); for (var i = 0; i < requestVector.Length; i++) { if (requestVector[i] > proc.Need[i]) { Console.WriteLine("分配失敗!資源型別 {0},請求數量 {1},超過程序所需數量 {2}", i, requestVector[i], proc.Need[i]); if (QueryExit()) Environment.Exit(0); else goto mainLoop; } if (requestVector[i] > proc.Max[i]) { Console.WriteLine("分配失敗!資源型別 {0},請求數量 {1},超過程序最大資源數量 {2}", i, requestVector[i], proc.Need[i]); if (QueryExit()) Environment.Exit(0); else goto mainLoop; } } // 儲存當前狀態 var tempAvailable = new List<int>(_available); var tempAllocation = (int[]) proc.Allocation.Clone(); var tempNeed = (int[]) proc.Need.Clone(); // 資源預分配 for (var i = 0; i < _resourceClassesCount; i++) { proc.Allocation[i] += requestVector[i]; proc.Need[i] -= requestVector[i]; } if (SecurityEvaluate()) { Console.WriteLine("正在為程序 {0} 分配資源", proc.Id); // 寫入資源分配檔案 var writer = new StreamWriter(Path.Combine( Environment.CurrentDirectory, "Exp3", "input", "Allocation_list.txt"), false); foreach (var p in _processes) { // 使用LinQ語句,構造輸出行 var line = p.Allocation.Aggregate("", (current, allocation) => current + (allocation + " ")); writer.WriteLine(line.Trim()); } Console.WriteLine("已經儲存新的分配狀態!"); writer.Close(); } else { Console.WriteLine("恢復試分配前的狀態。"); // 恢復預分配之前的狀態 _available = tempAvailable; proc.Allocation = tempAllocation; proc.Need = tempNeed; } continueFlag = !QueryExit(); } } /// <summary> /// 安全性演算法 /// </summary> private bool SecurityEvaluate() { var work = _available; var finish = new bool[_processCount]; var found = false; // 判斷標誌 var finishCount = 0; // 滿足條件的程序數目 var safeQueue = new List<int>(); while (finishCount < _processCount) { for (var procId = 0; procId < _processCount; procId++) { var proc = _processes[procId]; if (!finish[procId]) { for (var resId = 0; resId < work.Count; resId++) { Debug.WriteLine("安全性測試,procId={0} resId=(1)", procId, resId); if (proc.Need[resId] > work[resId]) { Debug.WriteLine("NotFound! procId={0} resId={1}", procId, resId); found = false; } else { Debug.WriteLine("Found! procId={0} resId={1}", procId, resId); found = true; } } } if (found) { // 模擬釋放資源 for (var t = 0; t < work.Count; t++) { work[t] += proc.Allocation[t]; } // 儲存程序號 finish[procId] = true; finishCount++; // 加入安全佇列 safeQueue.Add(procId); // 重置狀態 found = false; } } } Console.WriteLine("安全序列如下:"); // 列印安全序列 var output = ""; foreach (var procId in safeQueue) { output += "P" + procId + ","; } Console.WriteLine(output.TrimEnd(',')); if (finish.Any(flag => !flag)) { Console.WriteLine("未通過安全性測試!"); return false; } Console.WriteLine("已經通過安全性測試!"); return true; } private bool QueryExit() { Console.Write("是否退出(y/n)?"); var option = Console.ReadLine()?.ToLower(); return option == "y"; } /// <summary> /// 評估程序所需資源 /// </summary> private void EvaluateNeedResource() { foreach (var p in _processes) { p.EvaluateNeedResource(); // 計算系統還剩下多少資源 for (var i = 0; i < _resourceClassesCount; i++) { _available[i] -= p.Allocation[i]; } } } /// <summary> /// 打印出當前狀態 /// </summary> private void PrintStatus() { Console.WriteLine("-------------------------銀行家演算法-------------------------"); Console.WriteLine("系統程序數量:{0};資源種類數量:{1}", _processCount, _resourceClassesCount); Console.WriteLine("可用資源向量 Available:"); foreach (var i in _resource) { Console.Write("{0} ", i); } Console.WriteLine("\n最大需求矩陣 Max:"); foreach (var p in _processes) { foreach (var t in p.Max) { Console.Write("{0} ", t); } Console.WriteLine(""); } Console.WriteLine("已分配矩陣 Allocation:"); foreach (var p in _processes) { foreach (var t in p.Allocation) { Console.Write("{0} ", t); } Console.WriteLine(""); } Console.WriteLine("需求矩陣 Need:"); foreach (var p in _processes) { foreach (var t in p.Need) { Console.Write("{0} ", t); } Console.WriteLine(""); } } /// <summary> /// 載入系統所有可用的資源數量 /// </summary> private void LoadAllAvailableResource() { var reader = new StreamReader(Path.Combine(Environment.CurrentDirectory, "Exp3", "input", "Available_list.txt")); var line = reader.ReadLine(); // 獲取各型別資源數量 _resourceClassesCount = int.Parse(line?.Trim()); line = reader.ReadLine(); _resource.AddRange(Array.ConvertAll(line?.Split(' '), int.Parse)); _available.AddRange(Array.ConvertAll(line?.Split(' '), int.Parse)); reader.Close(); } /// <summary> /// 載入所有程序以及他們需要的最大資源 /// </summary> private void LoadProcessMaxResource() { var reader = new StreamReader(Path.Combine(Environment.CurrentDirectory, "Exp3", "input", "Max_list.txt")); var line = reader.ReadLine(); var index = 0; _processCount = int.Parse(line?.Trim()); while (!reader.EndOfStream) { line = reader.ReadLine(); var tempProc = new ProcessExp3(index++) { Max = Array.ConvertAll(line?.Split(' '), int.Parse) }; _processes.Add(tempProc); } reader.Close(); } /// <summary> /// 獲取所有程序已經分配的資源 /// </summary> private void LoadAllocationResource() { var reader = new StreamReader(Path.Combine( Environment.CurrentDirectory, "Exp3", "input", "Allocation_list.txt")); var index = 0; do { var line = reader.ReadLine()?.Trim(); _processes[index++].Allocation = Array.ConvertAll(line?.Split(' '), int.Parse); } while (!reader.EndOfStream); reader.Close(); } private void EvaluateProcessNeedResource() { } public static void Run() { new Main(); } } }
PS:這個程式碼裡面沒有包括 Program
類,要執行程式碼的話,需要加上 Program
類,在 Main
方法裡呼叫 Exp3.Main.Run();
執行結果
## 系統程序數量:5;資源種類數量:3 可用資源向量 Available: 10 5 7 最大需求矩陣 Max: 7 5 3 3 2 2 9 0 2 2 2 2 4 3 3 已分配矩陣 Allocation: 2 3 2 2 0 0 3 0 2 2 1 1 0 0 2 需求矩陣 Need: 5 2 1 1 2 2 6 0 0 0 1 1 4 3 1 請輸入要操作的程序號:0 請輸入資源請求向量:1 1 1 銀行家演算法檢驗中... 安全序列如下: P0,P1,P2,P3,P4 已經通過安全性測試! 正在為程序 0 分配資源 已經儲存新的分配狀態!
重新分配之後的狀態:
-------------------------銀行家演算法------------------------- 系統程序數量:5;資源種類數量:3 可用資源向量 Available: 10 5 7 最大需求矩陣 Max: 7 5 3 3 2 2 9 0 2 2 2 2 4 3 3 已分配矩陣 Allocation: 3 4 3 2 0 0 3 0 2 2 1 1 0 0 2 需求矩陣 Need: 4 1 0 1 2 2 6 0 0 0 1 1 4 3 1
About

Learn more on my WeChat Official Account:DealiAxy
Every post was in my blog: ofollow,noindex">blog.deali.cn