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

6.1 死锁的概念

所谓死锁,是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。

下面举个例子,进程P1已经占用了资源R1,进程P2已经占用了资源R2,而P1和P2都要同时使用两个资源才能继续运行,所以P1要求R2,P2要求R1,这时候P1、P2便处于死锁状态。

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

 产生死锁的两个原因:

  • 竞争系统资源
  • 进程推进顺序不当

6.2 产生死锁的条件和处理

6.2.1 必要条件

1. 互斥条件:一个资源每次只给一个进程使用;

2. 请求保持条件:在申请新资源时,保持原资源;

3. 非剥夺条件:资源由占有者自愿释放,不得强制占用;

4. 循环等待条件:进程间形成了等待资源的环路。

6.2.2 处理死锁的基本方法

不让死锁发生:

  • 预防死锁(静态策略)
  • 避免死锁(动态策略)

死锁可能发生:

  • 检测死锁
  • 解除死锁

6.3 死锁的预防

预防死锁就要破坏死锁四个必要条件的一个或多个,但是其中第一个互斥条件是不能破坏的,因为资源可共享的情况下不可能发生死锁,也就没有预防死锁的前提了。

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

两种方案:

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

两个缺点:

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

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

方案1:申请资源得不到满足,释放已占有的全部资源;

方案2:请求资源得不到满足,讨论两种情况:

  • 有进程占用该资源+等待更多资源,则剥夺这些资源以满足请求进程;
  • 有进程仅占用该资源,则请求进程等待(适用资源:容易保存恢复,如寄存器、存储器)

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

把系统中所有资源进行编号,进程在申请资源时必按资源编号的递增次序进行,否则系统不予分配,例如:

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

6.4 死锁的避免

系统运行过程中,允许进程动态地申请资源,在资源分配之前,先计算此次资源分配的安全性,若分配导致系统进入不安全状态,则将资源分配给进程,否则令进程等待。

最有代表性的避免死锁算法是银行家算法

6.4.1 系统安全状态

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

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

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

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

#include
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
#include
#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;iav.available[i])return 0;//若检测不通过则返回0
    return 1;//检测通过
}

void cur_state()
{//检测系统当前状态
    int i;
    if(banker())
    {
        printf("分析结果:当前系统处于安全状态,存在安全序列 ");
        for(i=0;iproc[n].need[i]||request[i]>av.available[i])
        {
            printf("分析结果:请求不合法,系统不予响应!");
            for(j=0;j

6.5 死锁的检测与解除

区别死锁的避免和非避免:

  • 避免:银行家算法时避免系统内各进程竞争系统资源而发生死锁;
  • 非避免:死锁检测与解除,操作系统不断监测系统进展情况,判断死锁是否发生,一旦死锁就采取专门的措施,解除死锁并以最小代价恢复操作系统运行。

6.5.1 死锁的检测

1. 资源分配图

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

2. 死锁判定法则

  • 如果图中没有环路,则系统中没有死锁
  • 如果图中有环路,且每类资源只有一个,则有环时系统存在死锁的充要条件
  • 如果图中有环路,但每类资源的个数不全为1,此时可以简化资源分配图,再检测系统是否存在死锁

3. 资源分配图化简

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

示例如下:

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

6.5.2 死锁的解除

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

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

6.6 死锁的综合处理策略

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

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

习题解析

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

答案:B

解析:如果只有两个进程的话,那么每个进程刚好能分配到4台打印机,不会死锁;但如果有三个进程的话,就有可能出现打印机分配为3、3、2的情况,此时每个进程都不满足,都再申请,但是都没有资源了,所以就会发生死锁,三个进程都这样,更不用说四个、五个进程了。所以K最小为3,选B。

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

答案:C

解析: 使得每个进程都只再申请一个资源时就能满足,在这里的已分配资源就是2、3、3,如果再加一个资源,系统就不会发生死锁,因为再加一个的话就可以满足其中的一个进程,等到这个进程结束后就会释放它的资源,从而满足剩下的进程。因此,可确保系统不发生死锁的设备数最小为9,选C。

 

你可能感兴趣的:(#,OS,笔记)