最近在做操作系统的课程设计,其中实验四是“银行家算法的模拟和实现”。好在前面看过一点,有点印象。所以想尝试自己写一下,下面是我的编码过程以及个人的一点分享,如果有问题欢迎指出,也希望能和我一起交流。我的邮箱是:jiangzhengtao49@outlook.com
一、 什么是银行家算法?
银行家算法顾名思义就是模拟银行的借贷业务的一种算法,操作系统就相当于一个银行家,而进程就是客户,进程向操作系统请求资源、归还资源就相当于客户向银行借贷、还贷。这个应该是很好理解的。理解这个名词之后,下面我们看看具体深入一点,银行家能否给一名客户贷款的要求是什么呢?很简单:在它还款期间,整个银行要能进行正常的资金周转。如果可以,那好,就借款给它,如果不能就不借给他。恩,就这么干脆。
换做操作系统的资源分配,也是如此:操作系统能否响应一个进程的资源请求,要求是,在它归还分配的资源之前,系统能维持一个正常的资源周转。到底什么是正常的周转?你可能在想。正常的周转就是,系统现有的资源能够满足多个进程的需求。也就是系统处于一个安全的状态。
二 银行家算法的数据结构
上面的论述,对银行家算法应该有了一点模糊的印象了,下面就深入一步的探究,为了方便,下面先就介绍银行家算法的数据结构。操作系统的资源可以是很多种,不同的进程对资源的需求也不同。
n n :进程的数量 m m :资源的种类
定义:
名词 | 数据结构 |
---|---|
分配矩阵 | Allocation[n][m] A l l o c a t i o n [ n ] [ m ] |
最大需求矩阵 | Max[n][m] M a x [ n ] [ m ] |
需求矩阵 | Need[n][m] N e e d [ n ] [ m ] |
可利用资源 | Available[m] A v a i l a b l e [ m ] |
请求资源 | Request[n][m] R e q u e s t [ n ] [ m ] |
进程i对资源j:
Allocation[i][j] A l l o c a t i o n [ i ] [ j ] 表示进程 i i 已分配资源 j j 的数量
Max[i][j]: M a x [ i ] [ j ] : 表示进程 i i 对资源 j j 的最大需求的数量
Need[i][j] N e e d [ i ] [ j ] :表示进程 i i 对资源 j j 剩下的需求 (Max[i][j]−Allocation[i][j]) ( M a x [ i ] [ j ] − A l l o c a t i o n [ i ] [ j ] )
Available[j] A v a i l a b l e [ j ] :对各个进程经过一番资源的分配后,资源 j j 的数量。
Request[i][j] R e q u e s t [ i ] [ j ] :进程 i i 对资源 j j 的请求数量。
(注:这里要区分请求和需求。可以这样理解,请求是动态、不确定的,进程对资源的请求有时多有时少)
三、银行家算法的流程
回到前面,先假设当前系统是处于一个正常周转的状态,这时一个进程 i i 发出资源请求,系统能否分配给它要经历如下步骤:
- 进程 i 的资源请求过度了吗?要知道一个进程在最初始状态是声明了最大的资源请求的。用数据结构表示就是: Request[i][j]<Need[i][j] R e q u e s t [ i ] [ j ] < N e e d [ i ] [ j ] 。如果满足就继续往下,不满足就拒绝分配。不满足,通俗讲就是此时进程i 的资源请求是过分的,撕毁条约,得寸进尺。
- 系统当前的资源情况能满足进程i的资源请求吗?经过步骤一,这时进程 i i 的请求是合理的。但是这时也不能给它分配,要判断一下此时资源够不够?现在也不考虑资源周转的问题,当务之急是当前资源能否满足进程i的请求。如果可以,就继续往下,不可以则进程需要等待,直到系统有足够的资源再说。
- 系统处于安全状态吗?经过步骤一、二,进程 i 能否得到系统的资源分配还需要经过最后一道难关:系统将资源分配给该进程 i i 后,还能处于一个安全状态吗?或者说,系统还能进行正常的资源周转吗? 这个地方可以说是整个算法的重中之重了。粗略来讲:系统先尝试对该进程 i i 进行资源分配,然后进行安全性检测, 如果系统安全则分配成功,否则则撤销分配。详细的过程我下面再讲。
这里用一个简单的流程图表示上面的内容:
Created with Raphaël 2.1.2 开始 资源初始化 该进程请求是否过度? 系统是否有足够资源? 尝试对该进程进行资源分配 系统是否安全 结束 yes no yes no yes
第一次用markdown画流程图,这个流程图没画好,在第三个条件少了恢复分配这个操作,见谅。
四、银行家算法中的安全性检测
上面将的数据结构、算法流程其实都是很容易理解的,但就判断当前系统是否安全可能还懵懵懂懂:到底怎么进行安全性检测?
我觉得这部分应该是整个算法最关键的地方,只要干掉它,就OK了!
前面讲了系统先对该进程尝试进行资源分配(注意是尝试!),就会得到新的资源情况,更新的过程我们可以看看,对进程 i i 进行资源分配 Request[i][j] R e q u e s t [ i ] [ j ] ,资源更新情况如下:
Need[i][j]=Need[i][j]−Request[i][j] N e e d [ i ] [ j ] = N e e d [ i ] [ j ] − R e q u e s t [ i ] [ j ]
Allocation[i][j]=Allocation[i][j]+Request[i][j] A l l o c a t i o n [ i ] [ j ] = A l l o c a t i o n [ i ] [ j ] + R e q u e s t [ i ] [ j ]
Available[j]=Available[j]−Request[i][j] A v a i l a b l e [ j ] = A v a i l a b l e [ j ] − R e q u e s t [ i ] [ j ]
(0<=i<n,0<=j<m) ( 0 <= i < n , 0 <= j < m ) 下标从0开始
然后资源更新后的系统进行安全性检测,具体我们先看一个例子:
Process | Allocation | Need | Available |
---|---|---|---|
P0 | 0 0 3 2 | 0 0 1 2 | 1 6 2 2 |
P1 | 1 0 0 0 | 1 7 5 0 | |
P2 | 1 3 5 4 | 2 3 5 6 | |
P3 | 0 3 3 2 | 0 6 5 2 | |
P4 | 0 0 1 4 | 0 6 5 6 |
问:该状态是不是安全状态?
我们是先找出一个进程,使得系统可利用的资源能够它的所有资源需求
可以看到只有 P0 P 0 满足要求,就把p0加入安全队列中,然后将它以前占用的所有资源收回。这个时候, Available=Available+Allocation A v a i l a b l e = A v a i l a b l e + A l l o c a t i o n
即: Available:1622+0032=1654 A v a i l a b l e : 1 6 2 2 + 0 0 3 2 = 1 6 5 4
Need中减少P0进程
Need=⎡⎣⎢⎢⎢1200736655550626⎤⎦⎥⎥⎥ N e e d = [ 1 7 5 0 2 3 5 6 0 6 5 2 0 6 5 6 ]
又接着找一个进程,看是否满足:系统可利用的资源满足它的所有资源需求。
可以看到,P3 Need= 0 6 5 2 满足要求,P4 Need = 0 6 5 6 也满足要求,
将P3加入安全队列中。这里我们可以任意选择一个,我们选择P3,
Available=Available+Allocation A v a i l a b l e = A v a i l a b l e + A l l o c a t i o n ,将P3的分配资源Allocation归还。P3 Allocation = 0 3 3 2
所以
Available:1654+0332=1986 A v a i l a b l e : 1 6 5 4 + 0 3 3 2 = 1 9 8 6
Need=⎡⎣⎢120736555066⎤⎦⎥ N e e d = [ 1 7 5 0 2 3 5 6 0 6 5 6 ]
继续,可以看到P1、P4满足要求,选择P1,
将P1加入安全队列中:
P1 Allocation =1 0 0 0
所以
Available:1986+1000=2986 A v a i l a b l e : 1 9 8 6 + 1 0 0 0 = 2 9 8 6
Need=[20365566] N e e d = [ 2 3 5 6 0 6 5 6 ]
可以看到P2 、P4都满足要求,选择P2,
将P2加入安全队列中:
P2 Allocation =1 3 5 4
所以
Available:2986+1354=3|12|13|10 A v a i l a b l e : 2 9 8 6 + 1 3 5 4 = 3 | 12 | 13 | 10
选择P4,
将P4加入安全队列中:
P2 Allocation =0 0 1 4
Available:3|12|13|10+0014=3|12|14|14 A v a i l a b l e : 3 | 12 | 13 | 10 + 0 0 1 4 = 3 | 12 | 14 | 14
到此为止,所有的进程都加入安全队列中,说明整个系统是处于安全状态的,如果没有进程加入安全队列中,说明系统是不安全的,可能会发生死锁,银行家算法是避免死锁而出名的一种算法,由此可见一斑。
五、尝试编程
当在(四)中描述算法的逻辑后,聪明的你应该有思路了,想尝试实现这个算法了。写完后想检测对比一下,我前面找到了一个小例子:银行家算法例子讲解
我自己按照上面的思路实现了这个算法,如果有问题,欢迎评论留言。
源代码如下,有注释。
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
#define max 50
int Allocation[max][max];
int Available[max];
int resources[max];
int Max[max][max];
int Need[max][max];
int resourcesSize;//资源总数
int processSize;//进程总数
bool isSecurity;//系统是否安全
bool checkSecurity();
void initialize()
{
printf("请输入资源总数\n");
scanf("%d",&resourcesSize);
printf("输入各个资源的数量:\n");
for(int i = 0;i<resourcesSize;i++)
{
printf("资源%d=",i);
scanf("%d",&resources[i]);
}
printf("输入进程数量\n");
scanf("%d",&processSize);
printf("输入每个进程对资源的最大需求\n");
for(int i = 0;i<processSize;i++)
{
for(int j = 0;j<resourcesSize;j++)
{
printf("进程%d对资源%d的最大需求=",i,j);
scanf("%d",&Max[i][j]);
}
}
printf("对每个进程的资源已分配情况:\n");
for(int i = 0;i<processSize;i++)
{
for(int j = 0;j<resourcesSize;j++)
{
printf("已对进程%d分配资源%d的数量=",i,j);
scanf("%d",&Allocation[i][j]);
}
}
for(int i = 0;i<processSize;i++)
{
for(int j = 0;j<resourcesSize;j++)
{
Need[i][j] = Max[i][j] - Allocation[i][j];
}
}
for(int j = 0;j<resourcesSize;j++)
{
for(int i = 0;i<processSize;i++)
{
resources[j] -= Allocation[i][j]; //分配完之后剩下的资源总数
}
Available[j] = resources[j];//可用资源数
}
checkSecurity();
}
bool checkSecurity()
{
bool security[processSize];//security[i]表示进程i是否安全
int securityQueue[processSize];//表示安全队列,将进程的id号加进去
memset(security,false,processSize); //初始化为不安全的。
int lastNoSecurity = processSize; //初始化上次不安全进程数为processSize
int temp[max]; //避免检测时对Available数组进行修改
for(int i = 0;i<resourcesSize;i++)
{
temp[i] = Available[i];
}
int k = 0;//添加安全进程到securityQueue中。计数的作用
while(true)
{
int currentNoSecurity = 0;//每次检测都currentNoSecurity都从0开始计数,表示当前不安全的进程数
int count;
for(int i = 0;i<processSize;i++)
{
count = 0;
if(security[i]==true) continue; //跳过安全的进程
for(int j = 0;j<resourcesSize;j++)
{
if(Need[i][j]<=temp[j]) //资源数满足进程i对它的需求
count++;
else
break;
}
if(count==resourcesSize)//所有可利用的资源全部满足进程i+1的需求,将此进程加入安全队列中
{
security[i] = true; //进程i是安全的
//将进程i+1已分配的资源偿还
printf("进程%d是安全的哦,资源可利用更新为:\n",i);
for(int j = 0 ;j<resourcesSize;j++)
{
temp[j]+=Allocation[i][j];
printf("%d ",temp[j]);
}
printf("\n");
securityQueue[k++] = i;
}
//当不满足时,暂时说明此进程是不安全的,当前不安全进程数+1
else
{
currentNoSecurity++;
printf("进程%d现在还不安全\n",i);
}
}
//检测一遍后,下面判断检测的结果
if(currentNoSecurity==0) //检测完毕,所有进程都已加入安全队列中。系统可以进行资源分配了。
{
printf("当前系统是安全的,可以对进程进行资源分配,安全队列如下:\n");
for(int i = 0;i< k;i++)
{
printf("%d ",securityQueue[i]);
}
printf("\n");
return true;
}
//当下检测不是安全队列的进程数如果跟上次一样,就说明扫一遍后找不出符合安全性的进程了。
if(currentNoSecurity==lastNoSecurity)
{
//不安全
printf("当前系统是不安全的,不能进行资源的分配!");
return false;
}
//至少有一个加入了安全队列中了,所以再扫一遍进程队列,再检测一遍。
else{
lastNoSecurity = currentNoSecurity;
}
}
return false;
}
bool answerQuest()
{
int Request[resourcesSize];
printf("输入哪个进程的资源请求?\n");
int process;
scanf("%d",&process);
for(int j = 0;j<resourcesSize;j++)
{
printf("输入进程%d对资源%d的请求=",process,j);
scanf("%d",&Request[j]);
}
//有多少我就请求多少,这貌似合理,系统也是安全的。但是这是不对的!不能多于最大请求
for(int j = 0;j<resourcesSize;j++)
{
if(Request[j] + Allocation[process][j] > Max[process][j]){
printf("进程%d对资源%d的请求不合理,超出所宣布的最大值!",process,j);
return false;
}
}
//没有大于最大请求,但是当前系统可能还没有这么资源分配他们
for(int j = 0;j<resourcesSize;j++)
{
if(Request[j] > Available[j])
{
printf("系统还没有足够的资源%d响应进程%d的请求",j,process);
}
}
//尝试分配
printf("尝试分配资源\n");
for(int j = 0;j<resourcesSize;j++)
{
Allocation[process][j]+=Request[j];
Need[process][j]-=Request[j];
}
for(int j = 0;j<resourcesSize;j++)
{
Available[j]-=Request[j];
}
if(!checkSecurity()){//如果系统不处于安全状态。则本次分配作废,恢复原来的资源分配状态
printf("分配作废,恢复原来的状态\n");
for(int j = 0;j<resourcesSize;j++)
{
Allocation[process][j] -= Request[j];
Need[process][j] += Request[j];
}
for(int j = 0;j<resourcesSize;j++)
{
Available[j] += Request[j];
}
return false;
}
return true;
}
void look()
{
printf("最大资源需求:\n");
for(int i = 0;i<processSize;i++)
{
for(int j = 0;j<resourcesSize;j++)
{
printf("%d\t",Max[i][j]);
}
printf("\n");
}
printf("已分配资源:\n");
for(int i = 0;i<processSize;i++)
{
for(int j = 0;j<resourcesSize;j++)
{
printf("%d\t",Allocation[i][j]);
}
printf("\n");
}
printf("Need需求:\n");
for(int i = 0;i<processSize;i++)
{
for(int j = 0;j<resourcesSize;j++)
{
printf("%d\t",Need[i][j]);
}
printf("\n");
}
printf("现有资源:\n");
for(int j = 0;j<resourcesSize;j++)
{
printf("%d\t",Available[j]);
}
printf("\n");
}
void menu()
{
printf("\n\n\t1.继续对进程进行资源的分配?\n");
printf("\t2.查看资源情况\n");
printf("\t3.exit\n");
}
int main()
{
printf("\n\n\t\t银行家算法,欢迎使用!!!\t\t\n\n");
printf("created by jiangzhengtao\n");
printf("time:2018.6.28 17:43\n");
printf("place:yifu building 431\n");
printf("\n\t1.请输入系统的初始状态\n");
initialize();
int choose;
while(1)
{
menu();
scanf("%d",&choose);
switch(choose)
{
case 1:
if(answerQuest())
printf("请求成功!");
else
printf("请求失败!");
break;
case 2:look();break;
case 3:
printf("再见!");
exit(0);
}
}
return 0;
}
六、总结
安全性检测这部分应该是银行家算法的重中之重了,将逻辑搞清楚应该是不难的。具体的思路是:如果找到一个进程,使得当前可利用资源(Available)能够满足它的需求(Need),就先对其分配资源,然后收回分配给它之前的资源(Allocation),接着更新Available(Available+=Allocation),然后将其加入安全性序列。继续找一个进程,重复上面操作。需要注意的是,必须要循环多次,因为前面没有符合条件的进程可能因为后面的资源更新又符合条件了。最后,临界条件是如果循环一遍后,还是找不到符合条件的进程,那么说明系统不安全,退出循环,反之如果所有的进程都加入安全性序列,就说明此系统是安全的,退出循环。
我处理的一个细节是:循环判断的时候,要跳过之前加入安全性序列的进程。
我没有注意的一点是:上面过程,要防止修改Available,需要使用一个临时数组,否则一旦判断出此系统是不安全的,Available是无法恢复的。这一点很重要!当时我就忽略了这一点。感谢博客:https://www.cnblogs.com/Lynn-Zhang/p/5672080.html。