操作系统-死锁
死锁
死锁的定义
死锁的定义是如果一组进程中的每一个进程都在等待被该组进程中另一进程所占有的资源,因而永远无法得到的资源,这种现象称为进程死锁,这一组进程被称为死锁进程。
死锁出现的场合
死锁通常是源于多个进程对资源的争夺,对不可抢占资源或对可消耗资源进行争夺时都可能引起死锁。
临界资源
可重用性资源
可重用性资源是一种可供用户重复使用多次的资源,每一个可重用性资源中的单元只能分配给一个进程使用,不允许多个进程共享。进程在使用可重用性资源的顺序是先请求资源,接着使用资源,最后当进程使用完后自己释放资源。系统中每一类可重用性资源中的单元数目是相对固定的,进程在运行期间既不能创建也不能删除它。对资源的请求和释放通常都是利用系统调用来实现的,计算机系统中大多数资源都属于可重用性资源。
可消耗性资源
可消耗性资源是在进程运行期间由进程动态地创建和消耗的,进程在运行过程中可以不断地创造可消耗性资源的单元放入缓冲区中,也可以消耗若干个可消耗性资源单元供进程使用。可消耗性资源通常是由生产者进程创建,由消费者进程消耗,最典型的就是用于进程间通信的消息等。
可抢占性资源
可抢占性资源是指某进程在获得这类资源后,该资源可以再被其它进程或系统抢占。例如优先级高的进程可以抢占优先级低的进程的处理机,或者在内存紧张时可以将一些进程从内存调出到外存上来抢占内存空间。
不可抢占性资源
抢占性资源是一旦系统把某资源分配给该进程后就不能强行收回,只能在进程用完后自行释放。例如进程使用打印机打印文件,如果此时打印机资源被抢占就会导致完整的文件不能被打印,这样就无法进行工作了,因此打印机资源不能强行收回。
引起死锁的原因
竞争不可抢占性资源引起死锁
例如系统中有 2 个临界资源 R1 和 R2,两个进程 P1 和 P2 的运行都需要 R1 和 R2 资源。如果进程 P1 或者 P2 中有个进程执行得比较快,在另一个进程开始之前就完成了工作并且归还资源,就不会产生死锁。
但是若进程 P1 先获得资源 R1,进程 P2 先获得资源 R2。后来 P1 又请求资源 R2,但是 R2 已被分配给了 P2 而导致 P1 阻塞。P2 又请求资源 R1,但是 R1 已被分配给了 P1 而导致 P2 阻塞。此时两个进程都被阻塞,同时它们都希望对方能释放出自己所需要的资源,最终谁都因不能获得自己所需的资源去继续运行,也无法释放出自己占有的资源陷入僵持状态。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211014222912612-1230407519.png)
竞争可消耗资源引起死锁
例如系统中有 P1、P2、P3 三个进程使用消息通信机制进行通信,m1、m2 和 m3 是发送的消息,属于可消耗资源。3 个进程的通信方式是 P1 产生 m1 发送给 P2,P2 产生 m2 发送给 P3,P3 产生 m3 发送给 P1 形成一个环。然后它们继续将信息发送给下一个进程,也就是 P1 产生 m3 发送给 P2,P2 产生 m1 发送给 P3,P3 产生 m2 发送给 P1。
此时由于 P1、P2、P3 三个进程都是先发送消息,再接受其他进程发给自己的消息。但是如果 3 个进程先执行接收操作,由于此时没有进程发送消息,3 个进程会不断等待其他进程发消息,同时自己因为没有收到消息也无法向其他进程发消息。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211014223000713-1197095156.png)
进程推进顺序不当引起死锁
进程在运行过程中,如果对资源进行申请和释放的顺序不合法也可能导致死锁。例如系统中有 2 个临界资源 R1 和 R2,两个进程 P1 和 P2 的运行都需要 R1 和 R2 资源。若进程 P1 先获得资源 R1,进程 P2 先获得资源 R2,2 个进程使用完资源后马上释放。后来 P1 又请求资源 R2,P2 又请求资源 R1,此时由于资源有正确释放所以不会引起死锁。
如果 2 个进没有及时释放资源,而是执行完之后统一释放就会发生进程死锁,这样的进程推进顺序就是非法的。
发生死锁的条件和处理方法
死锁的条件
进程在运行过程中产生死锁必须同时具备下面四个必要条件,只要其中任一个条件不成立死锁就不会发生:
- 互斥条件(资源独占):进程对所分配到的资源进行排它性使用;
- 请求和保持条件(占有且等待):进程已经保持了至少一个资源,但又提出了对其他已被占用的资源的请求;
- 不可抢占条件(不可剥夺):进程已获得的资源在未使用完之前不能被抢占,只能自己主动释放;
- 循环等待条件:在发生死锁时必然存在一个进程一资源的循环链。
处理方法
处理死锁的方法可归结为四种,四种方法对死锁的防范程度逐渐减弱,但资源利用率和并发程度逐渐加强。
- 预防死锁:属于静态策略,通过设计合适的资源分配算法,破坏产生死锁四个必要条件中的一个或几个;
- 避免死锁:属于动态策略,在资源的动态分配过程中,跟踪并评估资源分配过程,根据评估结果决策是否分配;
- 检测死锁:发生死锁后及时地检测出死锁的发生;
- 解除死锁:当检测到系统中已发生死锁时,就采取相应措施将进程从死锁状态解除。
1.预防死锁
预防死锁的方法是通过破坏产生死锁的四个必要条件中的一个或几个,由于互斥条件是非共享设备所必须的,所以预防死锁主要是破坏产生死锁的后三个条件。
1.1破坏“互斥条件(资源独占)”条件
资源转换技术:把独占资源变为共享资源。
SPOOLing技术的引入解决不允许任何进程直接占有打印机的问题。设计一个守护进程/线程负责管理打印机,进程需要打印时,将请求发给该daemon,由它完成打印任务。
1.2破坏“请求和保持条件(占有且等待)”条件
破坏“请求和保持”条件需要保证:当一个进程在请求资源时,不能持有不可抢占资源。该保证可通过如下两个不同的实现方案实现:
实现方案1
所有进程在开始运行之前,必须一次性地申请其在整个运行过程中所需的全部资源。如果进程获得了所有需要的资源,在整个运行期间就不会再提出资源要求,从而破坏了“请求”条件。系统在分配资源时只要有一种资源不能满足进程的要求,就不分配给该进程。由于该进程在等待期间未占有任何资源,于是破坏了“保持”条件。
第一种协议的优点是简单、易行且安全,但由于进程的某些资源可能仅在短时间内调用,或者根本没有用过,这就导致了资源的浪费。同时进程可能会因为某个资源被占用而迟迟无法进入就绪状态,造成了饥饿的发生。
实现方案2
允许一个进程只获得运行初期所需的资源后开始运行,进程运行过程中再逐步释放已分配给自己的、且已用毕的全部资源,然后再请求新的所需资源。第二种协议是对第一种的改进,提高了进程的效率、设备的利用率,减少进程发生饥饿的机率。
1.3破坏“不可抢占”条件
当一个进程提出不能被满足的新的资源请求时,如果它已经占用了某些不可被抢占资源,则它必须释放已经保持的所有资源。这种协议可以暂时地释放已占有的资源,从而破坏了“不可抢占”条件。不过这种协议实现起来比较复杂,且需付出很大的代价,因为释放已有的资源可能导致该进程已经做的工作前功尽弃了。
1.4破坏“循环等待”条件
对系统所有资源类型进行线性排序,并按照合理的方式赋予不同的序号。规定每个进程必须按序号递增的顺序请求资源,如果需要多个同类资源单元则必须一起请求。如果进程又想请求一个序号低的资源时,必须先释放所有具有相同和更高序号的资源后才能申请。例如可以对扫描仪和打印机进行编号,如果扫描仪的编号小于打印机,则进程只能先申请扫描仪,释放后再申请打印机。
这种策略保证了持有较大编号资源的进程不能逆过来申请编号较小的资源,因此占据了较高序号的资源的进程续申请的资源必然是空闲的,进程可以一直向前推进,破坏了“循环等待”条件。
这种预防死锁的策略比前两种策略有着更高的资源利用率和系统吞吐量,但是为系统中各类资源所规定的序号必须相对稳定,限制了新类型设备的增加。同时这种方式需要先对资源的使用的先后顺序有预先的估计,但是实际情况下作业使用各类资源的顺序与系统规定的顺序可能不同,造成对资源的浪费。而且这种方式限制了资源申请的顺序,就限制了用户编写应用程序的功能。
2避免死锁
避免死锁是在资源动态分配过程中,防止系统进入不安全状态以避免发生死锁。避免死锁的限制条件弱于预防死锁,因此比预防死锁的系统性能更好。
系统安全状态
在死锁避免方法中把系统的状态分为安全状态和不安全状态,当系统处于安全状态时可避免发生死锁,反之可能进入到死锁状态。安全状态是指系统能按某种进程推进顺序 (P1, P2,……, Pn) 为进程分配资源,使每个进程都可顺利地完成,此时称 (P1, P2,……, Pn) 为安全序列。如果系统无法找到这样一个安全序列,则称系统处不安全状态。避免死锁的基本思想是确保系统始终处于安全状态,当有进程请求一个可用资源时,系统需对该进程的请求进行计算,若将资源分配给进程后系统仍处于安全状态分配资源。
例如假定系统中有三个进程 P1、P2 和 P3,共有 10 个单位的资源,3 个进程分别需要 10、7、4 个单位的资源。假设在 T0 时刻进程 P1、P2 和 P3 已分别获得 3、3、2 个单位的资源,有 2 个单位的资源未分配。
进程
|
需求
|
已分配
|
P1
|
10
|
3
|
P2
|
7
|
3
|
P3
|
4
|
2
|
当按照 (P3, P2, P1) 的顺序执行时,P1 获取剩余的 2 个单位资源后执行完毕,释放资源后有 4 个单位的资源空闲;P2 获取空闲的 4 个单位资源后执行完毕,释放资源后有 7 个单位的资源空闲;P3 获取空闲的 7 个单位资源后执行完毕,此时没有发生死锁。因此存在一个安全序列 (P3, P2, P1),在 T0 时刻系统是安全的。如果不按照安全序列分配资源,则系统可能会由安全状态进入不安全状态,例如在 T0 时刻系统把剩余 2 台中的 1 台分配给 P2 系统就进入不安全状态。
银行家算法
银行家算法的具体定义和所用的数据结构,在课本已经给了详细的描述,这里只是简单地说一说。银行家算法的思路就是先尝试把资源分配给一个进程,然后尝试能否找到一个安全序列,如果能找到就执行该方案,若找不到就放弃试探方案并恢复试探前的状态。
算法描述
算法使用的数据结构有:
数据结构
|
大小
|
说明
|
Available
|
长度为 m 的一维数组
|
表示还有多少可用资源
|
Max
|
n * m 矩阵
|
表示各进程对资源的最大需求数
|
Allocation
|
n * m 矩阵
|
表示已经给各进程分配了多少资源
|
Need
|
Max - Allocation 矩阵
|
表示各进程最多还需要多少资源
|
Request
|
长度为 m 的一位数组
|
表示进程此次申请的各种资源数
|
银行家算法步骤如下:
- 检查此次申请是否超过了之前声明的最大需求数
- 检查此时系统剩余的可用资源是否还能满足这次请求
- 试探着分配,更改各数据结构
- 用安全性算法检查此次分配是否会导致系统进入不安全状态
其中安全性算法是先检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以就把该进程加入安全序列,并把该进程持有的资源全部回收。不断重复上述过程,看最终是否能让所有进程都加入安全序列。
算法样例
通过一个例子来描述算法,假定系统中有五个进程 {P0, P1,P2, P3, P4} 和三类资源 {A, B, C},各种资源的数量分别为 10、5、7。如果把三类资源的分配数量用一个三维向量表示,在 T0 时刻的资源分配情况如表格所示,此时剩余可用资源为 (3,3,2)。
进程
|
最大需求
|
已分配
|
最多还需要
|
P0
|
(7,5,3)
|
(0,1,0)
|
(7,4,3)
|
P1
|
(3,2,2)
|
(2,0,0)
|
(1,2,2)
|
P2
|
(9,0,2)
|
(3,0,2)
|
(6,0,0)
|
P3
|
(2,2,2)
|
(2,1,1)
|
(0,1,1)
|
P4
|
(4,3,3)
|
(0,0,2)
|
(4,3,1)
|
对进程进行试探操作,用剩余可用资源这个向量和每个进程的“最多还需要”做对比。进程 P1 的最多还需要 (1,2,2) 的每个元素都小于等于 (3,3,2),说明剩余资源可以满足 P1 的需求。将资源分配给 P1 并加入安全序列,P1 执行完毕后释放所有资源得到更新后的剩余资源为 (5,3,2)。
进程
|
最大需求
|
已分配
|
最多还需要
|
P0
|
(7,5,3)
|
(0,1,0)
|
(7,4,3)
|
P2
|
(9,0,2)
|
(3,0,2)
|
(6,0,0)
|
P3
|
(2,2,2)
|
(2,1,1)
|
(0,1,1)
|
P4
|
(4,3,3)
|
(0,0,2)
|
(4,3,1)
|
进程 P3 的最多还需要 (0,1,1) 的每个元素都小于等于 (5,3,2),说明剩余资源可以满足 P3 的需求。将资源分配给 P3 并加入安全序列,P3 执行完毕后释放所有资源得到更新后的剩余资源为 (7,4,3)。
进程
|
最大需求
|
已分配
|
最多还需要
|
P0
|
(7,5,3)
|
(0,1,0)
|
(7,4,3)
|
P2
|
(9,0,2)
|
(3,0,2)
|
(6,0,0)
|
P4
|
(4,3,3)
|
(0,0,2)
|
(4,3,1)
|
进程 P0 的最多还需要 (7,4,3) 的每个元素都小于等于 (7,4,3),说明剩余资源可以满足 P0 的需求。将资源分配给 P0 并加入安全序列,P0 执行完毕后释放所有资源得到更新后的剩余资源为 (7,5,3)。
进程 P2 的最多还需要 (6,0,0) 的每个元素都小于等于 (7,5,3),说明剩余资源可以满足 P2 的需求。将资源分配给 P2 并加入安全序列,P2 执行完毕后释放所有资源得到更新后的剩余资源为 (13,5,3)。
进程
|
最大需求
|
已分配
|
最多还需要
|
P4
|
(4,3,3)
|
(0,0,2)
|
(4,3,1)
|
进程 P4 的最多还需要 (4,3,1) 的每个元素都小于等于 (7,5,3),说明剩余资源可以满足 P2 的需求。将资源分配给 P2 并加入安全序列,P2 执行完毕后所有进程都执行完毕了,得到安全序列 {P1, P3,P0, P2, P4} 说明这种分配资源的方式不会产生死锁。
3死锁的检测
系统发生死锁时,就只能使用死锁的检测算法和解除算法进行处理。死锁检测算法用于检测系统状态,确定系统中是否发生了死锁。死锁解除算法是在认定系统中已发生了死锁,利用该算法可将系统从死锁状态中解脱出来。
检测时机:
- 当进程由于资源请求不满足而等待时检测死锁。缺点:系统开销大
- 定时检测
- 系统资源利用率下降时检测死锁
资源分配图
系统死锁可利用资源分配图来描述,资源分配图的定义和数据结构中的 G = (N, E) 一样。其中结点集 N 分为两个互斥的子集,分别是一组进程结点 P = {P1, P2, …, Pn} 和一组资源结点 R = {R1, R2, …, Ro}。E 中的一个资源请求边 e = {Pi,Rj} 都连接着 P 中的一个结点和 R 中的一个结点,由进程 Pi 指向资源 Rj 表示进程 Pi 请求一个单位的 Rj,由资源 Rj 指向进程 Pi 表示把一个单位的资源 Rj 分配给进程 Pi。
![](https://img2020.cnblogs.com/blog/1708060/202110/1708060-20211014223129010-2062562414.png)
死锁定理
利用把资源分配图加以简化的方法,来检测当系统是否处于死锁状态。如果系统中剩余的可用资源数足够满足进程的需求,则这个进程暂时不会阻塞可以继续执行。如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去。这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程。不断执行后归还进程,如果最终能消除所有边,就称这个图是可完全简化的,此时一定没有发生死锁。如果最终不能消除所有边,那么此时就是发生了死锁。例如上面的资源分配图的化简过程如下:
并不是系统中所有的进程都是死锁状态,用死锁检测算法化简资源分配图后连着边的那些进程就是死锁进程。
4死锁的解除
如果利用死锁检测算法检测出在系统中已发生了死锁,可以立即通知操作员以人工方法处理死锁,也可以使用死锁解除算法。常采用解除死锁的 3 种方法是:
- 抢占资源:挂起某些死锁进程并抢占它的资源,将这些资源分配给其他的死锁进程,这种方式需要注意避免饥饿;
- 终止(或撤消)进程:终止(或撤消)系统中的一个或多个死锁进程,并剥夺资源直至打破循环环路;
- 进程回退:让一个或多个死锁进程回退到足以避免死锁的地步,要求系统要设置还原点。
终止所有死锁进程是一种最简单的方法,但是其中有些进程可能已经接近结束,一旦被终止会导致前功尽弃。稍微温和的方法是按照某种顺序逐个地终止进程,直至有足够的资源以打破循环等待。但该方法所付出的代价也可能很大,因为每终止一个进程都需要用死锁检测算法确定系统死锁是否已经被解除,若未解除还需再终止另一个进程。同时在采取逐个终止进程策略时,还涉及到应采用什么策略来度量“代价最小”,一般需要考虑的因素有:
- 进程的优先级的大小;
- 进程已执行的时间;
- 进程的截止时间;
- 进程已经使用资源的多少,还需要多少资源;
- 进程的性质是交互式的还是批处理式的。、
“哲学家进餐”问题
有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。若五位哲学家同时饥饿而各自拿起了左边的筷子,当他们试图去拿起右边的筷子时,都将因无筷子而无限期地等待下去,即可能会引起死锁。
哲学家进餐问题的改进解法
- 方法一:至多只允许四位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用。
- 方法二:仅当哲学家的左右手筷子都拿起时才允许进餐。
- 方法三:规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。
方法一
至多只允许四位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用。
设置一个初值为 4 的信号量 r,只允许 4 个哲学家同时去拿左筷子,这样就能保证至少有一个哲学家可以就餐,不会出现饿死和死锁的现象。
原理:至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。
方法二
仅当哲学家的左右手筷子都拿起时才允许进餐。
解法 1:利用 AND 型信号量机制实现。
原理:多个临界资源,要么全部分配,要么一个都不分配,因此不会出现死锁的情形。
解法 2:利用信号量的保护机制实现。
原理:通过互斥信号量 mutex 对 eat() 之前取左侧和右侧筷子的操作进行保护,可以防止死锁的出现。
方法三
规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。
原理:按照下图,将是 2,3 号哲学家竞争 3 号筷子,4,5 号哲学家竞争 5 号筷子。1 号哲学家不需要竞争。最后总会有一个哲学家能获得两支筷子而进餐。