一.引入
1.简介
银行家算法(Banker Algorithm)是一个避免死锁(Deadlock)的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法,它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行.
在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程
在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待,为实现银行家算法,系统必须设置若干数据结构
2.操作系统的安全性和不安全状态
要解释银行家算法,必须先解释操作系统的安全状态和不安全状态:
- 安全序列是指一个进程序列{P1,…,Pn}是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和
- 安全状态: 如果存在一个由系统中所有进程构成的安全序列P1,…,Pn,则系统处于安全状态,安全状态一定是没有死锁发生
不安全状态:不存在一个安全序列,不安全状态不一定导致死锁
3.数据结构
(1).可利用资源向量Available
是个含有m个元素的数组,其中的每一个元素代表一类可利用的资源数目。如果Available[j]=K,则表示系统中现有Rj类资源K个
(2).最大需求矩阵Max
这是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要Rj类资源的最大数目为K
(3).分配矩阵Allocation
这也是一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。如果Allocation[i,j]=K,则表示进程i当前已分得Rj类资源的 数目为K
(4).需求矩阵Need
这也是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程i还需要Rj类资源K个,方能完成其任务
Need[i,j]=Max[i,j]-Allocation[i,j]
二.算法
1.原理
银行家算法的基本思想是分配资源之前,判断系统是否是安全的;若是,才分配。可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款
为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配
2.系统模型
在银行家算法中,系统模型包括以下几个参数:
n:进程的数量
m:资源类型的数量
Available:长度为m的数组,表示系统当前可用的各类资源数目
Max:n*m的矩阵,表示每个进程对各类资源的最大需求
Allocation:n*m的矩阵,表示每个进程当前已分配到的各类资源数目
Need:n*m的矩阵,表示每个进程尚需的各类资源数目。Need[i][j] = Max[i][j] - Allocation[i][j]
具体步骤如下:
设进程cusneed提出请求REQUEST [i],则银行家算法按如下规则进行判断:
(1).如果REQUEST [cusneed] [i]<= NEED[cusneed][i],则转(2);否则,出错
(2).如果REQUEST [cusneed] [i]<= AVAILABLE[i],则转(3);否则,等待
(3).系统试探分配资源,修改相关数据:
AVAILABLE[i]-=REQUEST[cusneed][i];
ALLOCATION[cusneed][i]+=REQUEST[cusneed][i];
NEED[cusneed][i]-=REQUEST[cusneed][i];
(4).系统执行安全性检查,如安全,则分配成立;否则试探险性分配作废,系统恢复原状,进程等待
3.安全性检查
银行家算法在进行资源分配前,会进行安全性检查,即在分配资源后,系统是否能够进入一个安全状态。安全状态是指系统中存在一个进程序列,使得每个进程能够在其最大需求得到满足后顺利执行完毕,具体安全性检查算法如下:
(1).设置两个工作向量Work=AVAILABLE;FINISH
(2).从进程集合中找到一个满足下述条件的进程,
FINISH==false;
NEED<=Work;
如找到,执行(3);否则,执行(4)
(3).设进程获得资源,可顺利执行,直至完成,从而释放资源。
Work=Work+ALLOCATION;
Finish=true;
GOTO 2
(4).如所有的进程Finish= true,则表示安全;否则系统不安全。
4.银行家算法步骤
初始化系统状态:设置初始的Available、Max、Allocation和Need矩阵。
请求资源:当进程请求资源时,检查请求是否合法(即请求量不超过进程的最大需求和系统的可用资源)。
试探性分配:临时分配资源给进程,并更新系统状态。
安全性检查:调用安全性算法检查系统是否处于安全状态。如果安全,正式分配资源;否则,恢复原状态,拒绝请求
流程图如下:
5.算法实例
(1).实例1
假定系统中有五个进程{P0, P1, P2, P3, P4}和三类资源{A, B, C},各种资源的数量分别为10、5、7,在T0时刻的资源分配情况下图所示。输入M资源总数量、Max矩阵和Allocation矩阵显示初始状态表(1)判断T0时刻是否安全?存在一个安全序列<P1,P3,P0,P2,P4>
输入M资源总数量、Max矩阵和Allocation矩阵
显示初始状态表
1).判断T0时刻是否安全?
存在一个安全序列<P1,P3,P0,P2,P4>
2).P1请求资源:P1发出请求向量Request1(1,0,2),调用银行家算法检查是否能够分配?
输入
存在一个安全序列<P1,P3,P4,P2,P0>,显示新的状态表
3).P4请求资源:P4发出请求向量Request4(3,3,0),系统按银行家算法进行检查:
输入
① Request4(3, 3, 0)≤Need4(4, 3, 1);
② Request4(3, 3, 0) >Available(2, 3, 0),让P4堵塞等待。状态表没有变化
4).P0请求资源:P0发出请求向量Requst0(0,2,0),系统按银行家算法进行检查:
输入
① Request0(0, 2, 0)≤Need0(7, 4, 3);
② Request0(0, 2, 0)≤Available(2, 3, 0);系统暂时先假定可为P0分配资源,并修改有关数据,如下图所示
可用资源Available(2,1,0)不能满足任何进程的需求,进入不安全状态。此时系统不分配资源给P0
输出:找不到安全序列,状态表没有变化
5).若P0发出请求向量Requst0(0,1,0),系统是否将资源分配给它?
输入
存在一个安全序列<P0,P1,P2,P3,P4>,显示新的状态表
程序代码:
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
#define M 3
#define N 5
int Resource[M];
int Max[N][M];
int Allocation[N][M];
int Need[N][M];
int Available[M];
int Work[M];
int Finish[N];
int List[N]; //存放安全序列的下标序列
void initial()
//创建初始状态:先输入 Resource、Max和 Allocation,再计算出 Need、Available。
{
int i,j;
printf("Resource--输入M种资源的总数量:\n");
for(i=0;i<M;i++)
{
scanf("%d",&Resource[i]);
Available[i]=Resource[i];
}
printf("Max--输入N个进程分别对M种资源的最大需求量:\n");
for(j=0;j<N;j++)
for(i=0;i<M;i++)
scanf("%d",&Max[j][i]);
printf("Allocation--输入N个进程获得M种资源的数量:\n");
for(j=0;j<N;j++)
for(i=0;i<M;i++)
scanf("%d",&Allocation[j][i]);
/****************************************/
for(j=0;j<N;j++)
for(i=0;i<M;i++)
Need[j][i]=Max[j][i]-Allocation[j][i];
for(j=0;j<M;j++)
for(i=0;i<N;i++)
Available[j]=Available[j]-Allocation[i][j];
}
void printState()
//输出当前的状态表|Process |Max |Allocation |Need |Available |
{
int i;
printf("状态表:\n|Process |Max |Allocation |Need |Available | \n");
for(i=0;i<N;i++)
{
if(i==0)
printf("|P%-11d|%4d%4d%4d|%4d%4d%4d|%4d%4d%4d|%4d%4d%4d|\n",i,Max[i][0],Max[i][1],Max[i][2],Allocation[i][0],Allocation[i][1],Allocation[i][2],Need[i][0],Need[i][1],Need[i][2],Available[0],Available[1],Available[2]);
else
printf("|P%-11d|%4d%4d%4d|%4d%4d%4d|%4d%4d%4d| |\n",i,Max[i][0],Max[i][1],Max[i][2],Allocation[i][0],Allocation[i][1],Allocation[i][2],Need[i][0],Need[i][1],Need[i][2]);
}
}
int isfinish()
//返回同时满足两个条件{①Finish[i]=false; ②Need[i][j]≤Work[j]}的进程下标 i(修改Finish[i]=true),否则返回-1。
{
int i,j,count;
for(i=0;i<N;i++)
{
for(j=0,count=0;j<M;j++)
if(Finish[i]==0&&Need[i][j]<=Work[j])
{
count++;
}
if(count==3)
{
for(j=0;j<M;j++)
Work[j]+=Allocation[i][j];
Finish[i]=1;
return i;
}
}
return -1;
}
int issafe()
//判定当前状态是否为安全状态 (返回 true 或 false),把安全序列的下标放入 List[N]数组。
{
int i,a,count=0;
for(i=0;i<M;i++)
Work[i]=Available[i];
for(i=0;i<N;i++)
Finish[i]=0;
for(i=0;i<N;i++)
{
a=isfinish();
if(a!=-1)
{
List[i]=a;
count++;
}
}
if(count==5)
return 1;
else
return 0;
}
void printList( )
//输出安全序列表|Process |Work |Need |Allocation |Work+Alloc |Finish |
{
int i,j;
printf("\n安全序列表如下:\n|Process |Work |Need |Allocation |Work+Alloc |Finish |\n");
for(j=0;j<M;j++)
{
Work[j]=Available[j];
}
for(i=0;i<N;i++)
{
printf("|P%-11d|%4d%4d%4d|%4d%4d%4d|%4d%4d%4d|%4d%4d%4d|true\n",List[i],Work[0],Work[1],Work[2],Need[List[i]][0],Need[List[i]][1],Need[List[i]][2],Allocation[List[i]][0],Allocation[List[i]][1],Allocation[List[i]][2],Work[0]+Allocation[List[i]][0],Work[1]+Allocation[List[i]][1],Work[2]+Allocation[List[i]][2]);
for(j=0;j<M;j++)
Work[j]+=Allocation[List[i]][j];
}
}
void reqresource(int i, int Request[M])
//表示第 i个进程请求 M类资源 request[M]
{
int flag,count1,count2;
int j;
//Step1: 判断条件 Request[j]≤Need[i][j]
for(j=0,count1=0;j<M;j++)
if(Request[j]<=Need[i][j])
count1++;
//Step2: 判断条件 Request[j]≤Available[j]
for(j=0,count2=0;j<M;j++)
if(Request[j]<=Available[j])
count2++;
if(count2!=3)
printf("\n尚无足够的资源,第%d个进程堵塞。\n",i);
//Step3: 预先分配
if(count2==3&&count1==3)
{
for(j=0;j<M;j++)
{
Available[j]=Available[j]-Request[j];
Allocation[i][j]=Allocation[i][j]+Request[j];
Need[i][j]=Need[i][j]-Request[j];
}
if(issafe()==0)
{
printf("\n不存在安全序列,不是安全状态。\n");
for(j=0;j<M;j++)
{
Available[j]=Available[j]+Request[j];
Allocation[i][j]=Allocation[i][j]-Request[j];
Need[i][j]=Need[i][j]+Request[j];
}
}
else
{
printf("\n是安全序列分配成功!\n");
printList();
}
}
//Step4:检测是否为安全状态
//填补程序
}
void main()
{
int reqid=-1,j,req[M];
initial();
printState();
if(issafe()==0)
{
printf("Initial state is unsafe!\n");
}
else
{
printf("\nInitial state is safe!\n");
printList();
printf("Input the id of request process:");
scanf("%d",&reqid);
while(reqid>=0 && reqid<N) //输入进程 id是否合法
{
printf("Input request resources:");
for(j=0;j<M;j++)
{
scanf("%d",&req[j]);
}
reqresource(reqid, req);
printState();
printf("Input the id of request process:");
scanf("%d",&reqid);
}
}
}
参考:操作系统:银行家算法(C语言代码)详解 - 全栈程序员必看
(2).实例2
银行贷款案例
假设客户A、B、C 三人向银行贷款用于建房,分别需要贷款70万、60万、40万,而银行只有100万,已知A、B、C三人只有贷款额足够时,他们才能保证建房完成并归还贷款,否则将无法归还贷款。银行需要使用什么方式才能保证贷款是安全的?
分析:A、B、C三人合计的资金需求是170万,显然银行无法直接满足三者的需求。
如果刚开始ABC三人分布借走了20万,20万,20万
可以算出,客户A、B、C最多还要借50、40、20,而银行已经借出60万(20+20+20),银行剩余40万。
A再次申请50万,能批准吗?
那么此时,假设客户A申请借50万,而银行最多只能借出40万,那么如果银行将40万借给A。
显然此时A无法完成建房的操作,B和C也不能完成建房的操作。这就导致A、B、C三个客户一直占有者已分配的钱,但是又无法归还银行,这种情况我们称为不安全序列。也就意味着A第二次申请50万的操作,银行不能批准。
B再次申请40万,能批准吗?或者C申请20万,能批准吗?
通过前面的图我们知道B还需要40万。那我们试着分配40万给B
1).可以看到B再得到40万后,可以完成建房的操作,于是归还了全部的贷款60万,于是银行此时有60万
2).此时客户A申请50万,银行批准这50万的贷款,而此时银行剩余10万,当客户A完成了贷款的使用后,归还了贷款,此时银行剩余80万
3).此时C申请20万,银行当前余额80万,可以批准这个操作,于是C也完成了建房的操作。
把这个分配顺序(B > A > C)叫做安全序列。
安全序列可以有多个,按照上面的操作,也可以是B > C > A
或者 C > A >B ,C > B > A。
上面的操作是借出 > 归还 > 借出 > 归还,如果银行的资源比较多的时候,可以一次性满足多个客户,并且可以找到安全序列,那么也可以 借出 D > 借出 E > 归还 E >…
安全序列和不安全序列
当资源分配过程的策略可以满足所有参与者能够完成执行的时候,我们称指为安全序列。如果分配过程出现无解的时候,我们称为不安全序列。
比如下面这个场景,银行已经分配90万,剩余10万,此时剩余的10万无论分配给谁,都无法保证A、B、C完成建房。
于是这个无论如何都没有解,A、B 、C只能这样僵持着,称这个过程为死锁。
如果分配了资源之后,系统中找不到任何一种安全序列,系统将会进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然了,如果有进程提前归还了一些资源,那么系统也可能重新回到安全状态,不过分配资源之前总是要考虑到最坏的情况。
如果系统处于安全状态,就一定不会发生死锁。如果系统进入了不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是不安全状态)。
因此可以在资源分配前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求,这就是银行家算法的核心思想。
多维度资源分配
前面的案例,银行家算法,里面的资源只有钱,对于操作系统而言,它的资源是多种多样的。利用银行家算法这种思想,作为操作系统资源的分配策略,用于避免死锁。
核心思想:在进程提出资源请求时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。
对于计算机而言,计算机中有各种各样的资源,也有各种各样的进程,它们所需要的资源也不相同。可以将资源扩展成多维度的向量。比如系统中有4个进程P0~03,有三种资源R0 ~ R2,系统持有的初始资源Avaiable(7,10,6)
经过计算,已经分配的资源(6,4,4),剩余的资源(1,6,2)
此时系统是否处于安装状态?
尝试找到一个安全序列
一次检查剩余的资源(2,6,2)是否满足各进程的需求。
可以发现剩余资源数不能满足P0,P1,P2 进程的运行,当检查P3时发现,剩余资源数(1,6,2)可以满足P3进程的需求,于是把P3加入到安全序列,于是P3可以顺利执行结束,并将P3持有的全部资源归还给系统,此时系统状态如下
P3结束后,剩余可用资源数为(3,7,2)
继续一次检查剩余资源可满足满足的进程,可以发现不能满足P0进程的运行,可以满足P1或者P2的运行,可以将P1加入到安全序列(也可以将P2加入到安全序列),我们此时取P1
于是当资源满足P1时,P1执行完成,并归还资源
当P1结束后,归还资源,那么剩余资源是(3,8,2)
继续检查,可以发现剩余资源不能满足P0,但是可以满足P2的运行,于是我们分配资源给P2
当P2运行完成,归还资源后,系统剩余资源(4,9,3),于是满足了P0,那么此时P0申请资源,P0也能顺利完成。
那么按照刚才的执行过程,P3 > P1 > P2 > P0 这个分配过程就可以满足所有进程的执行,称这个分配过程为安全序列。
实际操作用,可以让还需要资源数少的进程先运行,因为它们更容易满足剩余资源的分配。
操作系统资源分配
假设系统有n个进程,m种资源,每个进程在运行前先声明对各种资源的最大需求数,则可以按照nm的矩阵(可以使用二维数组实现)表示所有进程对各种资源的最大需求数。不妨称为最大需求矩阵Max,则Max[i,j]=k表示进程Pi最多需要K个资源Rj。同理,系统看可以用一个nm的分配矩阵Allocation表示对所有进程的资源分配情况。Max-Allocation=Need矩阵,表示各进程最多还需要多少各类资源。另外,还需要一个长度为m的一维数组Avaiable表示当前系统中还有多少可用资源。
某进程Pi向系统申请资源,可用一个长度为m的以为数组Request,表示本次申请的各种资源量。
可用银行家算法预判本次分配是否会导致系统进入不安全状态,步骤如下:
参考:银行家算法
三.总结
通过上面的讲解以及案例,知道了银行家算法的核心思想,就可以在每次的资源请求分配之前预判该次资源分配是否安全,利用银行家算法,就可以知道哪些资源请求是不安全的,哪些资源请求是可以被允许的,这样就可以让程序始终处于安全状态,就可以避免死锁的产生