死锁预防之银行家算法
第一章 概述
Dijkstra提出了一种能够避免死锁的调度算法,称为银行家算法。
它的模型基于一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,每个客户都有一个贷款额度,银行家知道不可能所有客户同时都需要最大贷款额,所以他只保留一定单位的资金来为客户服务,而不是满足所有客户贷款需求的最大单位。
这里将客户比作进程,贷款比作设备,银行家比作系统。
客户们各自做自己的生意,在某些时刻需要贷款。在某一时刻,客户已获得的贷款和可用的最大数额贷款称为与资源分配相关的系统状态。一个状态被称为是安全的,其条件是存在一个状态序列能够使所有的客户均得到其所需的贷款。如果忽然所有的客户都申请,希望得到最大贷款额,而银行家无法满足其中任何一个的要求,则发生死锁。不安全状态并不一定导致死锁,因为客户未必需要其最大贷款额度,但银行家不敢抱这种侥幸心理。
银行家算法就是对每一个请求进行检查,检查如果满足它是否会导致不安全状态。若是,则不满足该请求;否则便满足。
检查状态是否安全的方法是看他是否有足够的资源满足一个距最大需求最近的客户。如果可以,则这笔投资认为是能够收回的,然后接着检查下一个距最大需求最近的客户,如此反复下去。
如果所有投资最终都被收回,则该状态是安全的,最初的请求可以批准。
第二章 基本原理分析
一、死锁
(一)死锁概念:
指的是多个进程在运行过程中因为争夺资源而造成的一种僵局,当进程处于这种僵局状态时,若无外力作用,他们都将无法再向前推进的状态。
(二)死锁产生的原因:
竞争非可剥夺性资源
进程推进不当。
(三)产生死锁的必要条件
1、互斥条件 2、请求和保持条件 3、不可剥夺条件 4、环路等待。
(四)处理死锁的基本方法:
预防死锁:属于事前预防的策略,通过设置某些限制条件,去破坏产生死锁的四个必要条件或其中的几个条件。预防死锁比较容易实现,所以被广泛使用,但是由于施加的限制条件过于严格可能会导致系统资源利用率和系统吞吐量降低。
避免死锁:属于事前预防的策略,但它并不需要事先采取各种限制措施去破坏产生死锁的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的产生。但实现有一定的难度。目前较完善的系统中常用此法来避免死锁。
检测死锁:这种方法不需要事前采取任何限制措施,也不用检查是否进入不安全状态,而是允许系统在运行的过程中发生死锁。但是通过系统所设置的检测机构,及时的检测出死锁的发生,并精确的测出与死锁有关的进程和资源,然后,采取适当的措施,从系统中将已发生的死锁清楚掉。
解除死锁:这是与检测死锁相配套的一套措施。当检测到系统已经产生死锁时,须将进程从死锁中解放出来。通常用到的实施方法是撤销或挂起一些进程,以便收回一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源和吞吐量,但在现实上难度也最大。
预防死锁和避免死锁的区别:预防死锁和避免死锁实质上都是通过施加某种相知条件的方法,来预防发生死锁。两者的主要区别:为了预防死锁所施加的限制条件较为严格,这往往会影响到进程的并发执行,而避免死锁所施加的限制条件则较为宽松,有利于进程的并发执行。
二、为什么引入银行家算法:
预防死锁虽然可以预防死锁的产生,但是以牺牲进程的执行效率为代价,而死锁的避免由于施加的限制条件较弱,对进程的影响较小,有可能使用户获得满意的系统性能。在该方法中把系统状态分成安全状态和不安全状态,只要能使系统出于安全状态,便可避免死锁。因此,死锁的避免对防止系统进入不安全状态用重要意义。
最具代表性的避免死锁算法是Dijkstra的银行家算法。银行家算法是通过自己特有的算法,在每次奉陪给进程系统资源前,先试探性的“假设”分配资源给进程Pi,再通过安全性算法检测此次分配是否会导致系统进入不安全状态,如果分配后系统依然安全则系统将资源正是分配给进程Pi;如果此次分配导致系统进入不安全状态,则暂不分配资源给进程Pi。
通过这种机制,系统可以有效的避免死锁的产生,确保系统时时刻刻都处在安全状态。所以,要想深入了解避免死锁机制的,就必须掌握银行家算法的原理,及算法实现过程。
第三章 系统设计
一、使用数据结构的描述
(一)二维数组
表3.1:二维数组及其意义表
二维数组 | 意义 |
---|---|
Max_Need_Dev[Max][Num_Dev] | 所有进程所需要的设备的最大数目 |
Allocation_Dev [Max][Num_Dev] | 所有进程已经分配到的设备的数目 |
Need_Dev [Max][Num_Dev] | 所有进程还需要的设备的数目 |
current_Available_Dev[Max][Num_Dev] | 记录分配完之后,此时系统的可用资源数目 |
current_recycle_Dev[Max][Num_Dev] | 记录回收完之后,系统中各个设备的数目 |
(二)一维数组
表3.2:二维数组及其意义表
一维数组 | 意义 |
---|---|
system_Dev[Num_Dev] | 系统中所拥有的各类设备的数量 |
Available_Dev[Num_Dev] | 所系统中剩余的资源数量 |
finish [Max] | 存放所有进程是否已经运行完毕 |
quene[Max] | 假设不会出现死锁,那么用于存放安全队列。 即记录每个进程的下表。 |
二、使用程序结构的描述
表3.3:程序结构及其意义
程序 | 功能 |
---|---|
初始化init() | 输入进程数量、资源种类、资源可利用量、进程资源已分配量、进程最大需求量 |
allocation() | 用于进行银行家算法,进行安全性检查 |
比较conpare() | 用于判断当前可用设备的数目是否可以满足所传入的进程的要求 |
回收recycle | 若一个进程运行完毕,回收已经分配给它的设备 |
返回进程状态print() | 显示当前资源分配详细情况 |
当前安全性检查flag | 用于判断当前状态安全 |
主程序main() | 逐个调用初始化、显示状态、安全性检查、银行家算法函数,使程序有序的进行 |
三、银行家算法流程图
图3.1 银行家算法流程图
四、银行家算法流程
初始化:设置资源总量,每个进程所需要的最大资源量和已经分配的资源量。计算出此时系统中可用资源量和每个进程还需要的资源数。
接收请求:当一个进程请求一定数量的资源时,它必须向操作系统提交一个请求。如果该请求合理且确保分配后不会导致死锁,则操作系统将为该进程分配所需的资源。
分配资源:根据银行家算法的安全性原则来检查是否可以为该进程分配某类或者多类资源。如果分配后系统仍处于安全状态,则为该进程分配一个可用的资源并将其从系统中移除。
回收资源:当进程释放由它占用的资源时,操作系统将添加被释放的资源到可用资源池中,并重新计算可用资源量。
安全检查:通过模拟分配资源的过程来检查系统是否处于安全状态。如果系统处于安全状态,则可以继续为进程分配资源;否则,必须等待,直到系统处于安全状态。
进程完成:当进程完成运行并释放了其全部资源时,可以删除该进程。这些资源现在可以供其他进程使用。
第四章 系统功能实现
代码如下:
#include<stdio.h>
#include<stdlib.h>
#define Num_Dev 3 // 每个进程所需的设备的种类数量
#define Max 10 //可容纳的最多的进程数量
int Max_Need_Dev Max; //所有进程所需要的设备的最大数目
int Allocation_Dev Max; //所有进程已经分配到的设备的数目
int Need_Dev Max; //所有进程还需要的设备的数目
int current_Available_DevMax; //记录分配完之后,此时系统的可用资源数目(因为假如会产生安全队列,那么有多少个进程就会产生多少次这样的当前分配后的可用资源数目)
int current_recycle_DevMax; //记录回收完之后,系统中各个设备的数目