死锁
前言
大部分的死锁都和资源相关,所以首先要关注资源是什么。在进程对设备、文件等取得了排他性访问权时,就可能会出现死锁。把这类排他性使用的对象称为资源,资源可以是硬件设备或是一组信息。简单来说,资源就是随着时间的推移,必须能获得、使用以及释放的任何东西!!
可抢占资源和不可抢占资源
可抢占资源可以从拥有它的进程中抢占而不会产生任何副作用,存储器就是一类可抢占资源。
不可抢占资源是指在不引起相关的计算失败的情况下 ,无法把它从占用它的进程中抢占过来。
什么是死锁
指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵局状态时,如果没有外力的推动,那么这多个进程将无法前进。如下图所示:
具体一点的例子,如下代码。假设现存在进程A与进程B,这两个进程都会访问资源a、资源b。进程A访问资源的顺序是先访问资源a,然后在访问资源b;进程B访问资源的顺序是先访问资源b,然后再访问资源a;假如现在进程A给资源a加锁了,并且请求访问b资源,但是进程B给资源b加锁,同时也请求访问a资源。那么这两个进程就陷入了僵持状态,相互等待资源。
seamphore a,b;
void funcA(){
while(1){
// 占有锁a 进入临界区
P(a);
/*进行操作*/
// 试图占有锁b
P(b);
V(b);
// 释放锁a
V(a);
}
}
void funcB(){
while(1){
// 占有锁b 进入临界区
P(b);
/*进行操作*/
// 试图占有锁a
P(a);
V(a);
// 释放锁a
V(b);
}
}
产生死锁的原因
-
系统资源的竞争
在系统所配置的非剥夺性资源,由于它们的数量不能满足进程运行的需要,会使进程在运行过程中,因为争夺这些资源而进入僵局。
例如,系统中只有一台打印机R1和一台磁带机R2,可供进程P1和P2共享。假定P1占用了R1,P2占用了R2 。此时,若P2继续要求打印机,P2将阻塞;P1要求磁带机,P1也会阻塞。于是,P1和P2之间形成了僵局。两个进程都在等待对方释放出自己所需的资源!!!如下图所示:
-
进程推进顺序非法
如曲线4所示,该进程不能进入临界区D中,因为P1锁了R1,P2锁了R2,同时P1请求R2,P2请求R1导致两个进程相互竞争资源,发生死锁的情况 !!!
-
信号量使用不得当
进程间彼此相互等待对方发来的消息,结果也会使得这 些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A 发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。
-
死锁产生的必要条件——死锁发生时,以下四个条件一定是同时满足的!!
- 互斥条件。每个资源要么已经分配给了一个进程,要么就是可用的。若已分配其他请求的进程必须等待。
- 占有和等待条件。已经得到了某个资源的进程可以再请求新的资源
- 不可抢占条件。已经分配给一个进程的资源不能强制性地被抢占,只能显示地被释放
- 环路等待条件。死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,该环路的每个进程都在等待着下一个进程所占有的资源。
死锁预防
由上面可知,死锁的出现必须要满足四个条件。那么如过破环其中一个条件,那么死锁将不会产生。
-
破坏互斥条件
就是在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。但一般来说在所列的四个条件中,“互斥”条件是无法破坏的,因为不同的进程同时访问一个资源可能会产生异常。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件。
-
破坏占有并等待条件
只要禁止已持有资源的进程再等待其他资源便可以消除死锁。存在两种解决方案:
- 规定所有进程再开始执行前请求所有资源,如果所有资源可用,那么就可以分配到这个进程中,进程肯定能运行结束。如果有一个或多个资源正被使用,那么就不进行分配,进程等待。但是!很多进程都是直到运行时才知道它需要多少资源,如果能知道需要多少资源也就能使用银行家算法。其次,这种方法的资源利用率非常低!
- 要求当一个进程请求另一个资源时,必须释放当前占有的所有资源,然后再尝试以此获得所需的全部资源。
-
破坏不可抢占条件
破坏“不可抢占”条件就是允许对资源实行抢夺。
- 如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。
- 一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程(如果请求进程的优先级高),要求它释放资源。只有在任意两个进程的优先级都不相同的条件下,方法二才能预防死锁。
-
破坏环路等待条件
- 保证每一个进程在任何时刻只能占用一个资源,如果要请求另外一个资源,它必须先释放第一个资源。
- 将所有资源统一编号。如下图所示,其规则是:进程在任何时刻都可以提出资源请求,但是所有请求必须按照资源编号提出。也就是说进程可以线请求打印机后请求磁带机,但不可以先请求绘图仪后请求打印机。尽管能够消除死锁问题,但在大型的操作系统中找不出一种可行的编号次序,潜在的资源及各种不同用途的数目会变得很大,以至于使编号方法根本无法使用。
上述四种方法各有缺陷,一般不使用!!
死锁检测
每种类型一个资源的死锁检测
假设一个系统包括A到G共7个进程,R到W共6种资源。资源的占有情况和进程对资源的请求情况如下:
-
A进程持有R资源,且需要S资源
-
B进程不持有任何资源,但需要T资源
-
C进程不持有任何资源,但需要S资源
-
…
那么,显然进程D、E、G形成了一个环,陷入了死锁状态。虽然肉眼可轻易判断是否存在环,但是计算机不能这样子判断,需要一个正规的算法。具体算法如下,假设对已经检查过的弧(有向边)进行标记,以避免重复检查: -
对图中的每一个节点N,将N作为起始点执行以下5个步骤
-
将L初始化为空表,并清除所有的有向边标记
-
将当前节点添加到L的尾部,并检测该结点是否在L中出现过两次。如果是,那么该图包含了一个环(已在列中),算法结束。
-
从给定的结点开始,检测是否存在没有标记的从该结点出发的弧(有向边)。如果存在的话,将执行第五步;如果不存在,则执行第六步。
-
随机选取一条没有标记的从该结点出发的弧(有向边),并标记。然后顺着这条弧线找到新的当前节点,并返回到第3步。
-
如果这一节点是起始节点(回溯得到的),那么表明该图不存在任何环,算法结束。否则意味着我们走进了死胡同(第4个条件限制的,说明已经没有了新弧),所以需要一走该节点,返回到前一个节点,即当前节点前面的一个节点,并将它作为新的当前节点,同时转向第三步。
以上图为例,首先将R放入空表中,并移动到唯一可能的节点A,将A添加到L中。然后到达S,但是S不能往下走了,此时L={R,A,S},只能进行回溯,一直回溯到A,表示该部分不存在环,算法结束。那么现在已B节点开始,一直到D,这是L={B,T,E,V,G,U,D}。现在必须随机选择,如果选S点,那么走进了死胡同并回溯到D,选T接着将L更新为L={B,T,E,V,G,U,D,T},发现了环的出现,算法结束
每种类型多个资源的死锁检测——类似银行家算法
类似于银行家算法,给出两个矩阵,分别表示的是已分配到的资源以及需要的资源。
- 寻找一个没有标记的进程 Pi,要求它所请求的资源小于等于剩余资源量。
- 如果找到了这样一个进程,就将其占有的资源量加到剩余资源量中,并标记该进程,转回第 1 步。
- 如果没有找到这样一个进程,那么算法结束。
算法结束时,所有没有标记过的进程(如果存在)都是死锁进程。
死锁避免
安全状态和不安全状态
在图6-9a中有一个A拥有3个资源实例,但最终需要9个实例的状态;而B当前拥有两个资源实例,将来共需要4个资源实例。同样,C拥有2个资源实例,还需要另外5个资源实例。总共有10个资源实例,其中有7个资源已经分配,还有另外三个资源是空闲的。
图6-9a的状态是安全的,这是由于存在一个分配序列是的所有的进程都能完成!!
首先把空闲的3个中的两个分配给B,B结束后返回4个资源,此时空闲的共有5个资源;那么把5个空闲资源分给C,C结束后返回7个资源,此时空闲资源有7个;那么最后把7个分配给A,A恰好能完成。那么在这种调度方式中,这三个进程不会有存在任何资源争夺问题,可以说明这个状态是安全的!
假设现在的状态如图6-10a所示,按照以下形式对资源进行分配,6-10d陷入了困境之中,从安全状态进入到了不安全状态中,需要回过头来进行调整!!! 需要注意!不安全状态并不是死锁!!A可以释放一个资源等C完成后,从而避免死锁!! 安全状态和不安全状态的区别是:从安全状态出来,系统能够保证所有进程都能完成;而从不安全状态出发,就没有这样的保证!!
单个资源的银行家算法
在6-11a中看到4个客户A、B、C、D,每个客户都被授予一定数量的贷款,银行家知道不可能所有客户同时都需要最大贷款额,所以只保留10个单位而不是22个单位的资金来为客户服务。这里将客户比作进程,贷款比作资源,银行家比作成操作系统!
在6-11b中,银行家能够拖延除了C以外的其他请求,让C先完成,然后释放C所占的4个单位资源。有了这4个单位资源,银行家就可以给D或B分配所需的贷款单位,以此类推。
多个资源的银行家算法
可将单个资源的银行家算法进行推广已处理多个资源
下图中三个向量分别表示现有资产E、已分配资源P和可用资源A
E
i
=
P
i
+
A
i
E_i = P_i + A_i
Ei=Pi+Ai检查一个状态是否安全的算法如下:
- **查找右边矩阵中是否存在一行,其没有被满足的资源数均小于或等于A。如果不存在这样的行,那么系统将会死锁,因为任何进程都无法运行结束(**确认进程会一直占有资源直到它们终止为止)。
- 假若找到这样一行,那么可以假设它获得所需的资源并运行结束,将该进程标记为终止,并将资源加到向量A上。
- 重复以上两步,或者直到所有进程都标记为终止,其初始状态是安全的;或者所有进程的资源需求都得不到满足,此时就是发生了死锁
如果在第一步中同时有若干进程均符合条件,那么不管挑选哪一个运行都没有关系,因为可用资源或者会增多,或者至少保持不变,但一定不会减少!!!
死锁恢复
一旦检测出死锁,就应该立即采取措施来接触死锁。死锁解除的主要方法有:
- 资源剥夺法。 挂起某些死锁进程,抢占它的资源,分配给其他死锁进程。
- 撤销进程法。 强制撤销部分进程并剥夺这些进程的资源,让其他进程顺利执行。
- 进程回退法。