概述
什么是死锁
如果一个进程申请的资源被另一个等待进程占用,那么这两个进程就可能永远进入等待状态,这种情况称为死锁(deadlock)。
死锁一旦发生,进程将永远不能完成,并且系统资源将被阻碍调用,阻碍其他作业的执行。
本章描述一些死锁现象,并提供一些方法预防和避免死锁的发生
系统模型
假设:用Ri来表示不同的资源类型,每种Ri可以有Wi个实例(可以用于进程申请的资源)。
进程使用资源的过程:
- 申请:如果申请不能被允许(如被其他进程使用),那么该进程就要进入等待知道可以获取该资源。
- 使用
- 释放
死锁特征
死锁发生的必要条件
如果一下四个条件同时满足,那么死锁就会发生:
- 互斥:至少有一个资源处于非共享状态(即一次只有一个进程能使用这个资源)
- 占优并等待:一个进程必须占有一个资源,并且等待一个被其他进程占有的另一个资源。
- 非强占:资源不能被强占,即资源只能在进程完成后自动释放
- 循环等待:第二条的情况循环发生,即P0等待的资源被P1占有,P1等待的资源被P2占有…Pn等待的资源被P0占有
资源分配图
系统资源分配图是一种用于精确描述死锁问题的有向图,这个图的点有两类(系统活动进程的集合P和系统所有资源类型的集合R)
表示方法:
- 进程Pi已经申请了Rj的一个实例,并正在等待该资源:Pi->Rj,称为申请边
- 资源类型Rj的一个实例已经分配给了进程Pi:Rj->Pi,称为分配边
申请边和分配边不同时存在与两点之间,当该申请可以得到满足时,申请边立即转换为分配边
根据资源分配图的定义,可以用该图简单的判断是否有死锁:
如果资源分配图没有环,那么一定没有死锁
- 如果资源分配图有环,那么如果所有的资源只有一个实例,那么一定会发生死锁。
- 如果资源分配图有环,但一些资源的实例不止一个,那么不一定会发生死锁。
一个资源分配图的实例如下:
R1,R2,R3,R4资源类型分别有1,2,1,3个资源实例
死锁处理方法
从原理上,有一下几种方法:
- 使用协议预防和避免死锁,确保系统不会进入死锁
- 允许系统进入死锁状态,但能检测它,并加以恢复
- 忽视这种问题,认为死锁不可能在系统内发生(多为操作系统采用的方法)
在具体实施上,有多种死锁处理方法,但有些科研人员认为这些基本方法不能单独用来处理操作系统内的资源分配问题,因此需要将这些方法组合起来,以便为系统选择一种最佳方法
确保死锁不会发生(第一种原理)
为了确保死锁不会发生,有死锁预防(deadlock prevention)和死锁避免(deadlock avoidance)两种方案:
- 死锁预防:是一组通过限制申请资源来避免死锁的方法,确保死锁的必要条件不全都成立
具体将在死锁预防一节介绍
- 死锁避免:这个方案要求操作系统实现得到进程有关申请资源和使用资源的额外信息,以便系统确定对于一个申请,进程是否应该等待。系统需要进行多种判断来确定当前申请是允许还是延迟。
具体将在 死锁避免一节介绍
发生,检测,恢复(第二种原理)
如果系统不采用上述的方案,那么就可能发生死锁,在这种情况下,系统会提供**一个算法**来检查系统状态来检测死锁是否发生,并提供**另一个算法**从死锁中恢复忽略(第三种原理)
如果以上两种方式都不采用(不能确保不会发生死锁,也不检测死锁的存在),就会导致产生了死锁但系统察觉不到,资源申请得不到释放,从而导致更多的死锁,最终导致系统停止工作。 但这的确是一个方法,因为 **成本低**,并且在有的系统中,能够保证基本不发生死锁(一年一次之类的…)死锁预防
死锁预防是确保**死锁发生的必要条件不全被满足**,每个条件都可以采取一些手段来避免。互斥->共享资源
对于非共享资源,必须要有互斥,如一台打印机不能同时打印两张纸(一般意义下)。但共享资源则不需要互斥(如只读文件)。有的资源本身就是非共享的,因此不能通过全部设定为共享资源来避免这一条件的发生,只能尽可能的减少这一条件
占有并等待->单恋
为了确保这一条件不发生,需要保证:当一个进程申请一个资源的时候,它不能占有其他资源,对于这一要求,有以下协议可以采用:- 每个进程在开始的时候就申请所有(需要)的资源,从而确保如果一个进程如果无法申请到应得的资源就不会调用。(实现要求申请资源的系统调用在所有其他系统调用之前进行)
- 要求进程在没有资源时才能申请资源。一个进程在任意时刻都能申请资源,但必须确保他释放了其他的资源。
这两种协议的有两个主要缺点:
- 资源利用率低,许多资源可能已分配,但很长时间没有被使用
- 可能导发生饥饿,如一个进程需要多个资源,但因为有一个资源没有办法申请而永久等待
非抢占
为了确保这一条件,有一个协议可以解决:
- 如果一个进程申请了另一个不能立即分配的资源,处于等待状态,那么这个进程现已分配的其他任何资源都可被抢占
//检查资源是否可用
if("可用")
//分配
else:
//检查这些资源是否已分配给其他等待额外资源的进程
if("占有这些资源的进程在等待其他资源")
//抢占
else:
//等待,并允许自身的资源被抢占
这个协议通常应用于状态可以保存和恢复的资源,如寄存器和内存,而不适用于打印机或磁带
循环等待
这是最后一个条件,要确保此条件不成立的方法是对所有资源类型进行完全排序。并要求每个进程按递增顺序申请资源。
给所有的资源分配一个不同的整数,以进行排序,一个进程开始的时候可以申请任意资源,假设资源对应的整数是Ri
,那么其后申请的任意资源Rj
必须满足Rj > Ri
,除非Ri
得到释放
死锁预防普遍的副作用是低设备利用率和系统吞吐率。
死锁避免
这是避免死锁的另一种方法,即让系统能获得进程以后如何申请资源的附加信息。每次申请均要求系统考虑现有可用资源,现已分配资源和每个进程将要申请和释放的资源,来决定当前申请是否满足或必须等待
注意避免死锁和死锁避免不一样,避免死锁的手段有死锁预防和死锁避免,它们是上下级关系
根据这种方法,有不同的算法,这些算法要求的信息量和信息的类型各有不同,其中最简单有效的模型要求每个进程说明其可能需要的每种资源类型的最大需求。
通过这些信息可以构造一个算法来确保系统不会进入死锁状态,其中的两个算法是资源分配图算法和银行家算法
死锁状态只是系统多种状态中的一种,在介绍算法之前,先对系统所处的各种状态做简单说明
安全状态
安全序列:
进程顺序< P1, P2,...Pn >
,如果对于每个Pi
,Pi能够申请的资源数<(当前可用的资源数+所有进程Pj(j<i)所占有的资源)
那么这一顺序称为安全序列
如果系统能按某个顺序为每个进程分配资源(不超过最大值)并能避免死锁,那么系统状态就是安全的,即如果存在一个安全序列,那么系统就处于安全状态。否则系统处于不安全状态。
不安全状态不是死锁状态,两者是包含于被包含的关系。
可以通过一个实例来看一下安全状态和不安全状态,考虑如下的表格:
进程 | 最大需求 | 当前占有1(安全) | 当前占有2(不安全) |
---|---|---|---|
Thread | Need | Allocation 1 | Allocation 2 |
P0 | 10 | 5 | 5 |
P1 | 4 | 2 | 2 |
P2 | 9 | 2 | 3 |
假设现在的最大资源总数为12,三个进程P0
、P1
、P2
的资源最大需求和当前占有如上表所示。可以判断需求1时系统处于安全状态:因为存在一个序列<P1、P0、P2>
为安全序列。判断过程如下:
当前已分配的资源数为5+2+2=9
,未分配资源数为12-9=3
。
- 一次穷举后发现,未分配资源分配给
P1
时,可以满足其最大需求(3+2 > 4),P1
满足后,释放所有资源,于是当前未分配资源为5。 - 第二次穷举发现,可以将资源分配给
P0
(5+5=10),P0
满足后,释放所有资源,于是当前未分配资源数为10 - 第三次可以很容易得知未分配资源足够分配给
P2
以满足需求
再考虑需求2,当满足P1
的需求时,当前未分配资源为4,这样无论是P0
还是P2
,都得不到满足,这样就导致了死锁
翻译版将上述表格翻译为最大需求和当前需求,我认为不是很准确,当初甚至误导了我很久,在查了一些资料后,将后者改为当前占有,不知道是否有更正式的翻译。
通过安全状态这一概念,就可以让系统通过避免算法,通过让系统始终处于安全状态来避免死锁。
资源分配图算法
P221
银行家算法
死锁检测
如果系统不采用死锁预防算法和死锁避免算法,就没办法避免死锁,在这种环境下,系统应该提供:
- 一个用来检查系统状态从而确定是否产生了死锁的算法。
- 一个用来从死锁中恢复状态的算法。
恢复算法有其额外开销,不光包括维护信息和执行检测的算法开销,还有死锁恢复引起的损失。
下面通过资源实例数量的两种情况来说明不同的算法
每种资源类型单个实例
这种情况可以通过资源分配图的一个变种:等待(wait-for)图来执行算法。
当且仅当图中存在环的时候,系统存在死锁。因此系统需要定期维护等待图,并周期性的执行检测算法。
通过删除资源类型的节点,合并适当边,即为等待图。方式比较直观,如下图所示:
检测算法的复杂度为 n2 n 2 ,其中 n n <script type="math/tex" id="MathJax-Element-2">n</script> 为图中节点数。
每种资源类型多个实例
等待图不适用于这种情况,需要一种新的算法,这种算法和银行家算法类似:
P226
应用检测算法的时机
应用检测算法的频率取决于以下两个因素:
- 死锁可能发生的频率是多少
- 当死锁发生时,有多少进程会受影响
对每一个请求都检测是否发生了死锁会产生很大的开销,所以一个不太”昂贵”的方法是以一个不太高的频率调用检测算法,或者当CPU利用率低于一定值的时候调用检测算法(因为死锁的一个明显特征就是CPU利用率下降)
但如果不在每个请求后检测,那么就无法得知是哪一个进程导致了死锁。
死锁恢复
死锁恢复承接死锁检测,当死锁已经发生并且被检测到时,就需要采取措施:
- 通知操作员,让操作员人工处理死锁
- 打破死锁,打破死锁有两种方式:
- 简单的终止一个到多个进程以打破循环
- 从一个或多个进程中强占资源
下面主要介绍打破死锁的两种方式
进程终止
首先要知道进程终止后,这个进程占有的所有资源都会被回收。
以及进程终止有两种方式:
- 终止所有产生死锁的进程。这样可以保证终止了死锁,但是很明显代价很大,因为恢复后可能很多计算过程都需要重新完成。
- 一次终止一个进程直到死锁状态被打破。但这种方法开销很大,因为没结束一个进程,都需要调用检测算法检查进程是否仍然处于死锁中。
因此如何终止进程是一个经济问题,也就是如何让进程终止后的代价最小化,这需要考虑多个方面,包括但不限于:进程优先级、进程已执行时间、进程当前使用资源数,进程最大资源需求、进程是否交互、需要终止的进程数量。
资源抢占
通过抢占一个进程的资源给其他进程,从而一步步打破死锁。这一个办法需要处理的问题有三个:
- 选择一个牺牲品。要抢占资源,需要判断抢占哪一个进程的资源,这同样是一个“代价最小”的问题。
- 回滚。当一个进程被另一个进程抢占资源的时候,需要对该进程进行一定的处理,很显然因为缺少资源,该进程不能正常执行,那么就需要让该进程回滚到一个状态以便最后能重新执行。
- 饥饿。当基于一个策略来判断谁来作为被抢占资源的进程时,同一个进程很容易被反复选中作为被抢占资源的进程(神tm马太效应),这是抢占资源的算法需要考虑的问题。一个方法是在代价因素中加入一个进程的回滚次数。