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

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 系统安全状态

安排好进程的顺序,使得按顺序可以顺利完成所有进程,就称系统处于安全状态,如果系统中的进程不存在这么一个安全序列,则称系统处于不安全状态。

要注意,安全状态一定是没有死锁发生的

三个进程一类资源安全性检测代码:

cpp 复制代码
#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. 银行家算法的代码实现

cpp 复制代码
#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。

相关推荐
Aileen_0v04 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
Rinai_R5 小时前
计算机组成原理的学习笔记(7)-- 存储器·其二 容量扩展/多模块存储系统/外存/Cache/虚拟存储器
笔记·物联网·学习
吃着火锅x唱着歌5 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
ragnwang5 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
胡西风_foxww6 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
胡西风_foxww9 小时前
【es6复习笔记】函数参数的默认值(6)
javascript·笔记·es6·参数·函数·默认值
胡西风_foxww9 小时前
【es6复习笔记】生成器(11)
javascript·笔记·es6·实例·生成器·函数·gen
waterme1onY9 小时前
Spring AOP 中记录日志
java·开发语言·笔记·后端
2401_879103689 小时前
24.12.25 AOP
java·开发语言·笔记
索然无味io11 小时前
跨站请求伪造之基本介绍
前端·笔记·学习·web安全·网络安全·php