Operating System · DeadLock(操作系统 · 死锁) (五)

DeadLock



前言

假设系统中有一台打印机和一台扫描仪,现在有两个进程P1和P2。首先,进程P1先请求使用打印机并获得了成功,同时,进程P2请求使用扫描仪,也获得了成功。后俩P1又需要使用扫描仪,但是由于扫描仪正在被进程P2所使用,导致P1只好等待被阻塞;而进程P2也想使用打印机,但由于打印机正在在P1所占有,导致P2也只好等待被阻塞,我们把此时这两个进程的状态称为死锁。本篇文章会为大家阐述死锁的概念,导致死锁产生的原因以及相应的解决方法。


一、死锁产生的原因

1、哲学家进餐问题 (Dining Philosophers)

在系统介绍死锁成因之前,我们再来看一个产生死锁的经典例子——哲学家进餐问题。哲学家进餐问题由Dijkstra于1971年提出,它描述了这样一个场景,有五个哲学家共用一张圆桌,分别坐在桌子周围的五张椅子上,在圆桌上有五个装有意大利面的盘子和五个叉子(下图所示),他们会交替进行思考和进餐。例如,假设其中一个哲学家正在进行思考,他突然饿了想吃饭,便试图取其左右两侧最靠近他的叉子来吃饭,只有在他拿到两支筷子时才能吃饭,吃完饭后,他会放下筷子继续思考。在这个问题中,每一个哲学家相当于一个进程,而每一个叉子相当于共享的资源。
请添加图片描述
由于哲学家之间从来不交谈,这就回导致死锁的情况发生,即每个哲学家都拿着自己左手边的叉子,同时,他们呢都在等待右手边的叉子。

通过前面的两个例子,我们不难得出产生死锁的直接原因。即当两个进程都被阻塞的时候,双方都希望得到被对方所占有的资源而继续执行,但是由于双方都得不到,导致它们也无法释放被自己所占有的资源,这样两个进程就都处于了一种僵持的阻塞状态,这就是死锁。换句话讲,可以引起死锁的原因有很多,其中最主要的原因是因为资源的互斥使用:一旦资源被占有后便不能再被其他进程所使用除非当前进程释放该资源,即我们在上一章介绍的互斥资源。而占有这些资源的进程在不释放已有资源的情况下又去申请其他资源,最终导致进程之间各自占有的资源和互相申请的资源形成了死环路,谁都无法继续执行下去。

2、产生死锁的四个条件

这样,我们就可以总结出产生死锁的四个必要条件:
[ 1 ] [1] [1] 资源的互斥使用 (Mutual Exclusion)
[ 2 ] [2] [2] 不可抢占资源 (No Preemption)
[ 3 ] [3] [3] 进程请求和保持资源 (Wait and Hold)
[ 4 ] [4] [4] 循环等待 (Circular Wait)

二、死锁的处理方法

1、预防死锁

顾名思义,预防死锁就是通过破坏死锁产生的四个条件中的一个或者几个,从而防止死锁的发生。由于互斥条件是非共享设备所必须的,不仅不能改变,还必须加以保证,因此我们主要通过破坏死锁的后三个条件来预防死锁。

例如,我们可以让进程在执行前,一次性申请其在运行过程中所有需要的资源。这样该进程在运行期间,便不会再既占有资源又去申请其他资源了。但是该方法有两个缺点,第一需要系统预知未来,提前知道进程执行需要的所有资源,这就导致变成很困难;其次,许多资源分配后要经过很长时间才会被使用,使得资源利用率降低,相应地,也会导致进程经常发生饥饿现象。

第二种方法是对资源类型进行排序,要求进程对资源按顺序进行申请,这样就不会出现环路等待的情况,但是这也会造成资源的浪费。例如这样的情况,对于该进程来说10号资源要比7号资源先被使用,但是由于要按顺序进行申请,就导致进程必须先占有7号资源并且暂时不使用,后申请10号资源,直到使用完10号资源才能再次使用7号资源。

2、避免死锁

避免死锁的核心思想是通过提前判断此次请求是否会引起死锁来决定是否接受进程对该资源的请求。如果系统中的所有进程都存在一个可完成不死锁的执行序列P1, P2, P3, …Pn,我们就称该系统处于安全状态。我们以下面这个表格为例:

进程AllocationNeedAvailable
P00 1 07 4 32 3 0
P13 0 20 2 0
P23 0 26 0 0
P32 1 10 1 0
P40 0 24 3 1

表格中Allocation表示已经分配给该进程的资源, 三个数字分别对应着三种资源(A, B, C)的数量;Need表示该进程还需要的各资源的数量;Available表示系统中各资源所用的数量。值得注意的是,当某个进程所需要的资源得到满足后,就会继续执行该进程直到结束,最后被该进程所占有的资源都会被释放,此时系统可用的资源数目就会增加供给下一个进程使用。根据上述表格,我们可以得到的一个安全序列为:P1, P3, P2, P4, P0 。

银行家算法

荷兰科学家Dijkstra提出的银行家算法就实现了上述判断系统最终安全状态的过程。为了实现银行家算法,系统中需要设置四个数据结构,分别用来描述可利用资源(Available),系统中的资源分配(Allocation),以及所有进程还需要多少资源(Need)。现在我们来看一下银行家算法的整体代码:

int Available[1..m];
int Allocation[1..n, 1..m];
int Need[1..n, 1..m];
int Work[1..m];
bool Finish[1..n];

Work = Available;
Finish[1..n] = false;
while(true){
	for(int i=1; i<=n; i++){
		if(Finish[i] == false && Need[i] <= Work){
			Work = Work + Allocation[i];
			Finish[i] = true;
			break;
		}else{
			goto end;
		}
	}
}
End: for(int i=1; i<=n; i++){
		if(Finish[i] == false){
			return "deadlock";
		}
	  }

代码中的工作向量Work表示系统可提供给进程继续运行所需的各类资源数目,它含有m个元素,在执行安全算法开始时,我们让Work=Available。另外,代码中的Finish相当于进程执行的标记,表示系统中是否有足够的资源分配给进程,使之运行完成。如果该进程可以分配到资源以结束运行,则该进程对应的Finish索引值为true, 否则为false。最后,如果分配完之后所有的进程的Finish值都为true, 就表示可以生成一个安全序列,否则,会返回“deadlock”的提示,此次资源申请便会被拒绝。

分析上述代码我们不难发现,该算法的时间复杂度为 O ( m n 2 ) O(mn^2) O(mn2), 也就是说,如果m和n都为100的时候,时间复杂度将为 1 0 6 10^6 106,加上进程每次申请资源的时候都会执行这个算法,这将是一个无比巨大的数字。显然我们需要对该算法的使用进行改进。

3、死锁检测和恢复

由于银行家算法的执行效率比较低,因此我们可以不用每次申请资源的时候都调用该算法,而采用发现问题再处理的方法。具体的可以采用定期检测或者是发现资源利用率低的时候再检测。在定期进行死锁检测的时候我们依然可以运用银行家算法,然后将Finish为False的进程放入到死锁队列中,此时处于这个队列中的进程已经发生了死锁。

检测到死锁之后,应立即采取相应的措施解除死锁。最简单的处理措施就是通知操作员,以人工的方式处理死锁。另一种就是利用死锁解除算法,常常采用的死锁解除算法有以下两种:
[ 1 ] [1] [1] 抢占资源: 即从一个或多个进程中抢占数量足够多的资源,分配给贝死锁的进程,以解除死锁状态。
[ 2 ] [2] [2]终止进程:终止或撤销系统中的一个或多个死锁进程,直至打破循环环路,时系统从死锁状态解脱出来。

事实上无论是抢占资源还是终止进程,系统所花费的代价都是非常可观的。因此,许多通用的操作系统,如PC机上安装的Windows和Linux,都采用了死锁忽略的方法。


总结

以上就是本章讲解的内容,我们分析了死锁产生的原因和必须要满足的四个基本条件。另外我们还介绍了相应的死锁处理办法,有提前预防死锁,避免死锁等。其中我们详细讲解了避免死锁的银行家算法,通过提前模拟资源分配的方式来查看最糊是否会造成死锁。但是无论是避免死锁还是回复死锁,其复杂度都很大。再加上死锁发生的概率比较低,因此,现代操作系统一般都采用忽略死锁的方式。到本章为止,我们就讲解完了进程的相关知识,接下来我们还会用两章的内容为大家介绍线程的相关知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值