6.1 死锁的概念
所谓死锁,是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。
下面举个例子,进程P1已经占用了资源R1,进程P2已经占用了资源R2,而P1和P2都要同时使用两个资源才能继续运行,所以P1要求R2,P2要求R1,这时候P1、P2便处于死锁状态。
产生死锁的两个原因:
- 竞争系统资源
- 进程推进顺序不当
6.2 产生死锁的条件和处理
6.2.1 必要条件
1. 互斥条件:一个资源每次只给一个进程使用;
2. 请求保持条件:在申请新资源时,保持原资源;
3. 非剥夺条件:资源由占有者自愿释放,不得强制占用;
4. 循环等待条件:进程间形成了等待资源的环路。
6.2.2 处理死锁的基本方法
不让死锁发生:
- 预防死锁(静态策略)
- 避免死锁(动态策略)
死锁可能发生:
- 检测死锁
- 解除死锁
6.3 死锁的预防
预防死锁就要破坏死锁四个必要条件的一个或多个,但是其中第一个互斥条件是不能破坏的,因为资源可共享的情况下不可能发生死锁,也就没有预防死锁的前提了。
1. 破坏请求保持条件:采用预先分配策略
两种方案:
- 要求每个进程在运行之前申请它所需全部资源。
- 规定每个进程在请求新资源前必须释放全部已占用的资源。
两个缺点:
- 资源利用率较低;
- 可能产生饥饿现象:资源竞争激烈导致某个进程陷入无限期等待。
2. 破坏非剥夺条件:收回未使用完毕的资源
方案1:申请资源得不到满足,释放已占有的全部资源;
方案2:请求资源得不到满足,讨论两种情况:
- 有进程占用该资源+等待更多资源,则剥夺这些资源以满足请求进程;
- 有进程仅占用该资源,则请求进程等待(适用资源:容易保存恢复,如寄存器、存储器)
3. 破坏循环等待条件:有序分配策略
把系统中所有资源进行编号,进程在申请资源时必按资源编号的递增次序进行,否则系统不予分配,例如:
6.4 死锁的避免
系统运行过程中,允许进程动态地申请资源,在资源分配之前,先计算此次资源分配的安全性,若分配导致系统进入不安全状态,则将资源分配给进程,否则令进程等待。
最有代表性的避免死锁算法是银行家算法。
6.4.1 系统安全状态
安排好进程的顺序,使得按顺序可以顺利完成所有进程,就称系统处于安全状态,如果系统中的进程不存在这么一个安全序列,则称系统处于不安全状态。
要注意,安全状态一定是没有死锁发生的。
三个进程一类资源安全性检测代码:
#include<iostream>
using namespace std;
int seq[100]; // 保存安全序列的下标
struct { // 代表三个进程的结构体
int max_need; // 最大需求
int allocated; // 已分配的资源数
int need; // 还需要的资源数
int state = 0; // 进程是否已运行,0代表未运行,1代表已运行
}P[3];
int main() {
for (int i = 1; i <= 3; i++) {
cin >> P[i].max_need >> P[i].allocated >> P[i].need;
} // 输入三个进程的数据
int available, work; // available表示目前可用的资源数,work用来代替available参与判断
int s = 3, j = 1; // s表示进程数,j是用来保存安全序列的下标
cin >> available;
work = available;
while (s--) {
for (int i = 1; i <= 3; i++) {
if (P[i].need <= work && P[i].state == 0) {
work += P[i].allocated;
seq[j++] = i;
P[i].state = 1;
}
}
}
if (seq[3] != 0) {
cout << " The system is safe because there is a safe sequence: ";
cout << "P" << seq[1] << "→P" << seq[2] << "→P" << seq[3];
}
else
cout << "The system is not safe!";
return 0;
}
6.4.2 银行家算法
1. 银行家算法数据结构
- 可利用资源数量:Available
- 最大需求矩阵:Max
- 分配矩阵:Allocation
- 需求矩阵:Need
2. 银行家算法过程
当进程Pi提出资源申请,系统执行以下四个步骤:
1)若Request[i]≤Need[i],转2);否则错误返回(需要资源数超过最大值)
2)若Request[i]≤Available, 转(3);否则进程等待(无足够资源)
3)系统试分配资源,则有 3 个计算:
Available:=Available - Request[i];
Allocation[i]:=Allocation[i] + Request[i];
Need[i]:=Need[i] – Request[i]
4)执行安全性算法;若新状态安全,则分配完成,若新状态不安全,则恢复原状态,进程等待
3. 安全性算法
为进行安全性检查,再定义2个数据结构:
Work: ARRAY[1..m] of integer;
Finish: ARRAY[1..n] of Boolean;
算法4个步骤如下:
1) Work := Available; Finish := false;
2) 寻找满足条件的 i : Finish[i] = false and Need[i]≤Work; 如果不存在,则转4)
3) Work:=Work+Allocation[i];Finish[i]:=true;转2)
4) 若对所有i,Finish[i]=true,则系统处于安全状态,否则处于不安全状态
4. 银行家算法的代码实现
#include<stdio.h>
#include<string.h>
#define PN 5//进程数
#define RN 4//资源种类数
typedef struct//定义结构体类型pcb
{
char name[3];//进程名,如p0
int max[RN];//最大资源值
int allocation[RN];//已分配资源数
int need[RN];//任需求资源数
}pcb;
struct//定义为结构体类型,方便赋值
{
int flag[PN];//进程检测标志,1时表示已通过
int safes[PN];//存放进程编号,作为安全序列输出
}fs0,fs;//fs0保存初始值,fs用于工作
struct//定义为结构体类型,方便赋值
{
int available[RN];//系统可用资源向量
}av0,av,avx;//av0保存初始值,av和avx用于工作
pcb proc0[PN],proc[PN];//proc0保存初始值,proc用于工作
int request[RN];//进程请求资源向量
void init();//初始化,输入数据
void output(pcb*);//输出数据
void cur_state();//判断系统当前状态
void req_state();//分析进程提出的请求是否可以响应
void back0();//退回至初始值
void back1(int);//退回至分配前
int check(int*);//进程need向量与available向量比较
int banker();//银行家算法
int main()
{
int num;
printf("【银行家算法】\n");
printf("进程数:%d\n",PN);
printf("资源数:%d\n",RN);
printf("=================\n");
init();
output(proc0);//输出初始资源分配情况
printf("【系统当前状态分析】\n");
cur_state();
printf("======================================================\n\n");
printf("选择操作序号(0:结束程序;1:资源请求分析)\n");
printf("输入序号:");
scanf("%d",&num);getchar();
while(1)
{
switch(num)
{
case 1:printf("\n【进程资源请求分析】\n");req_state();break;
default:printf("\n======分析结束!======\n");return 0;
}
printf("\n");
printf("======================================================\n");
printf("\n");
printf("选择操作序号(0:结束程序;1:资源请求分析)\n");
printf("输入序号:");
scanf("%d",&num);getchar();
}
}
void init()
{//初始化
int i,j;
for(i=0;i<PN;i++)
{
printf("\n输入进程名:");
gets(proc0[i].name);
printf("输入max:");
for(j=0;j<RN;j++)
scanf("%d",&proc0[i].max[j]);
printf("输入allocation:");
for(j=0;j<RN;j++)
scanf("%d",&proc0[i].allocation[j]);
for(j=0;j<RN;j++)//计算need
proc0[i].need[j]=proc0[i].max[j]-proc0[i].allocation[j];
fs0.flag[i]=0;//进程标志位置0
fs0.safes[i]=-1;//安全序列置-1
proc[i]=proc0[i];//给工作矩阵proc赋值
getchar();//吸收scanf()产生的回车符
}
printf("\n输入available:");
for(j=0;j<RN;j++)
scanf("%d",&av0.available[j]);
fs=fs0;//给工作单元fs和av赋值
av=av0;
getchar();//吸收scanf()产生的回车符
}
void output(pcb* p)
{//输出资源分配情况,觉得麻烦可以不考虑
int i,j;
printf("\n");
printf("===============================================================================\n");
printf(" 标志位 | 进程名 | 最大值 \t| 已分配 \t| 需求值 \t| 可用数 \n");
printf("-------------------------------------------------------------------------------\n");
for(i=0;i<PN;i++)
{
printf(" %-5d",fs.flag[i]);
printf("|");
printf(" %-5s",p[i].name);
printf("| ");
for(j=0;j<RN;j++)
printf("%-3d",p[i].max[j]);
printf("\t| ");
for(j=0;j<RN;j++)
printf("%-3d",p[i].allocation[j]);
printf("\t| ");
for(j=0;j<RN;j++)
printf("%-3d",p[i].need[j]);
printf("\t| ");
if(i==0)
for(j=0;j<RN;j++)
printf("%-3d",av.available[j]);
printf("\n");
}
printf("===============================================================================\n\n");
}
int banker()
{//银行家算法
int i,j,t=0,k,f;
k=0;//计数器,找到符合条件的进程数
f=1;//标志位,每轮查找标志,f=0时该轮未找到合适进程,查找结束
while(f==1&&k<PN)
{
f=0;//本轮查找标志置0
for(i=0;i<PN;i++)
if(!fs.flag[i]&&check(proc[i].need))
{//找到符合条件的进程
f=1;//本轮查找标志置1
fs.flag[i]=1;//进程标志置1
for(j=0;j<RN;j++)//修改available中的值
av.available[j]+=proc[i].allocation[j];
fs.safes[t]=i;//进程序号i记入safes数组中,作为安全序列输出
t++;k++;
}
}
if(k<PN)return 0;//当k<PN时说明不存在安全序列,返回0
else return 1;
}
int check(int* p)//p为当前进程的need向量
{//当前进程need向量与available向量比较
int i;
for(i=0;i<RN;i++)
if(p[i]>av.available[i])return 0;//若检测不通过则返回0
return 1;//检测通过
}
void cur_state()
{//检测系统当前状态
int i;
if(banker())
{
printf("分析结果:当前系统处于安全状态,存在安全序列 ");
for(i=0;i<PN;i++)//输出安全序列
printf("p%d",fs.safes[i]);
printf("\n");
}
else
printf("分析结果:当前系统处于不安全状态!请结束程序");
//output(proc);//输出分析后的状态(查看flag和available状况)
fs=fs0;av=av0;//退回至初始状态
printf("\n\n");
}
void req_state()
{//对进程提出的资源请求,判断分配的可行性
int i,j,n;
printf("输入提出请求的进程序号n:p");
scanf("%d",&n);
printf("输入请求资源向量request:");
for(i=0;i<RN;i++)
scanf("%d",&request[i]);
for(i=0;i<RN;i++)//判断请求的合法性
if(request[i]>proc[n].need[i]||request[i]>av.available[i])
{
printf("分析结果:请求不合法,系统不予响应!");
for(j=0;j<RN;j++)
request[j]=0;//清空request
return;
}
for(i=0;i<RN;i++)
{//尝试分配,修改allocation,need和available的值
av.available[i]-=request[i];
proc[n].need[i]-=request[i];
proc[n].allocation[i]+=request[i];
}
avx=av;//avx保存分配后available的值
//output(proc);//输出分配后的资源分布状况
if(banker())//调用银行家算法,寻找安全序列
{
printf("分析结果:当前系统处于安全状态,存在安全序列 ");
for(i=0;i<PN;i++)//输出安全序列
printf("p%d ",fs.safes[i]);
printf("\n");
av=avx;//分析结束,确定资源分配
fs=fs0;//清空标识位flag和安全序列safes
}
else
{
printf("分析结果:不存在安全序列,系统不予响应,进程阻塞!\n");
//output(proc);//输出分析结束后的情况(查看flag和available状况)
back1(n);//取消分配,退回至分配前
printf("已取消分配,退回至分配前\n");
}
printf("\n\n");
printf("输入“y”可退回到初始状态,请选择(y/n)\n");
printf("请输入:");
getchar();//吸收前面的输入所产生的回车符
if(getchar()=='y')
{
back0();
printf("已退回至初始状态\n");
}
}
void back0()
{//恢复至初始值
int i;
fs=fs0;
av=av0;
for(i=0;i<PN;i++)
proc[i]=proc0[i];
}
void back1(int m)//m为提出资源请求的进程序号
{//撤销分配,退回至分配前
int i;
fs=fs0;
for(i=0;i<RN;i++)
{
av.available[i]=avx.available[i]-request[i];
proc[m].need[i]+=request[i];
proc[m].allocation[i]-=request[i];
}
}
6.5 死锁的检测与解除
区别死锁的避免和非避免:
- 避免:银行家算法时避免系统内各进程竞争系统资源而发生死锁;
- 非避免:死锁检测与解除,操作系统不断监测系统进展情况,判断死锁是否发生,一旦死锁就采取专门的措施,解除死锁并以最小代价恢复操作系统运行。
6.5.1 死锁的检测
1. 资源分配图
2. 死锁判定法则
- 如果图中没有环路,则系统中没有死锁
- 如果图中有环路,且每类资源只有一个,则有环时系统存在死锁的充要条件
- 如果图中有环路,但每类资源的个数不全为1,此时可以简化资源分配图,再检测系统是否存在死锁
3. 资源分配图化简
示例如下:
6.5.2 死锁的解除
6.6 死锁的综合处理策略
习题解析
答案:B
解析:如果只有两个进程的话,那么每个进程刚好能分配到4台打印机,不会死锁;但如果有三个进程的话,就有可能出现打印机分配为3、3、2的情况,此时每个进程都不满足,都再申请,但是都没有资源了,所以就会发生死锁,三个进程都这样,更不用说四个、五个进程了。所以K最小为3,选B。
答案:C
解析: 使得每个进程都只再申请一个资源时就能满足,在这里的已分配资源就是2、3、3,如果再加一个资源,系统就不会发生死锁,因为再加一个的话就可以满足其中的一个进程,等到这个进程结束后就会释放它的资源,从而满足剩下的进程。因此,可确保系统不发生死锁的设备数最小为9,选C。