【OS】操作系统课程笔记 第六章 并发性——死锁

目录

6.1 死锁的概念

6.2 产生死锁的条件和处理

6.2.1 必要条件

6.2.2 处理死锁的基本方法

6.3 死锁的预防

1. 破坏请求保持条件:采用预先分配策略

2. 破坏非剥夺条件:收回未使用完毕的资源

3. 破坏循环等待条件:有序分配策略

6.4 死锁的避免

6.4.1 系统安全状态

6.4.2 银行家算法

1. 银行家算法数据结构

2. 银行家算法过程

3. 安全性算法

4. 银行家算法的代码实现

6.5 死锁的检测与解除

6.5.1 死锁的检测

1. 资源分配图

2. 死锁判定法则

3. 资源分配图化简

6.5.2 死锁的解除

6.6 死锁的综合处理策略

习题解析


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. 破坏请求保持条件:采用预先分配策略

两种方案:

  1. 要求每个进程在运行之前申请它所需全部资源。
  2. 规定每个进程在请求新资源前必须释放全部已占用的资源。

两个缺点:

  1. 资源利用率较低;
  2. 可能产生饥饿现象:资源竞争激烈导致某个进程陷入无限期等待。

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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值