【操作系统】Operation System-第11章-死锁
操作系统—死锁
死锁问题
为什么出现死锁?由于进程的并发执行抢占资源。
系统模型
-
资源类型 R 1 R_1R1, R 2 R_2R2, ... ...... , R m R_mRm
➢ CPU cycles CPU\ cyclesCPU cycles, memory space memory\ spacememory space, I/O devices I/O\ devicesI/O devices
-
每个资源类型 R i R_iRi 有 W i W_iWi 个实例.
-
每个进程使用资源如下:
➢ require/get<=free resource require / get <= free\ resourcerequire/get<=free resource
➢ use/hold<=requested/used resource use / hold <= requested / used\ resourceuse/hold<=requested/used resource
➢ release<=free resource release <= free\ resourcerelease<=free resource
可重复使用的资源
- 在一个时间只能有一个进程使用且不能被删除
- 进程获得资源,后来释放由其他进程重用
- 处理器,I/O通道,主和副存储器,设备和数据结构,如文件,数据库和信号量
- 如果每个进程拥有一个资源并请求其他资源,死锁可能发生
使用资源
- 创建和销毁
- 在I/O缓存区的中断,信号,消息,信息
- 如果接收消息阻塞可能会发生死锁
- 可能少见的组合事件会引起死锁
资源分配图
一组顶点 V V V 和边 E E E 的集合
-
V VV 有两种类型:
➢ P={ P 1 , P 2 ,..., P n } P=\{P_1,P_2,...,P_n\}P={P1,P2,...,Pn},集合包括系统中的所有进程。
➢ R={ R 1 , R 2 ,..., R m } R=\{R_1,R_2,...,R_m\}R={R1,R2,...,Rm},集合包括系统中的所有资源类型。
-
requesting/claiming edge−directed edge P i => R j requesting/claiming\ edge - directed\ edge\ P_i => R_jrequesting/claiming edge−directed edge Pi=>Rj
-
assignment/holding edge−directed edge R j => P i assignment/holding\ edge - directed\ edge\ R_j => P_iassignment/holding edge−directed edge Rj=>Pi
资源分配图(续)
资源分配图例子(无死锁)
资源分配图例子(有死锁)
有循环的资源分配图但没有死锁
基本情况
-
如果图中不包含循环
➢ 没有死锁。
-
如果图中包含循环
➢ 如果每个资源类只有一个实例,那么死锁。
➢ 如果每个资源类有几个实例,可能死锁。
死锁特征
死锁出现一定会出现以下四个条件,但是出现以下四个条件不一定死锁:
- 互斥:在一个时间只能有一个进程使用资源
- 持有并等待:进程保持至少一个资源正在等待获取其他进程持有的额外资源
- 无抢占:一个资源只能被进程资源释放,进程已经完成了它的任务之后
- 循环等待:存在等待进程集合 {P0 ,P1 , . . . ,Pn } \{P_0,P_1,...,P_n\} {P0,P1,...,Pn}, P0 P_0 P0 正在等待 P1 P_1 P1 所占用的资源, P1 P_1 P1 正在等待 P2 P_2 P2 占用的资源,…, P n − 1P_{n-1} Pn−1 在等待 Pn P_n Pn 的资源, Pn P_n Pn 正在等待 P0 P_0 P0 所占用的资源
死锁处理方法
- 确保系统永远不会进入死锁状态。
- 运行系统进入死锁状态,然后恢复。
- 忽略这个问题,假装系统中从来没有发生死锁,用于大多数操作系统,包括UNIX。
Deadlock Prevention(死锁预防)
限制申请方式
-
互斥 - 共享资源不是必须的,必须占用非共享资源。
-
占用并等待 - 必须保证当一个进程请求的资源,它不持有任何其他资源。
➢ 需要进程请求并分配其所有资源,它开始执行之前或允许进程请求资源仅当进程没有资源
➢ 资源利用率低,可能发生饥饿
-
无抢占
➢ 如果进程占有某些资源,并请求其他不能被立即分配的资源,则释放当前正占有的资源
➢ 被抢占资源添加到资源列表中
➢ 只有当它能够获得旧的资源以及它请求新的资源,进程可以得到执行
-
循环等待 - 对所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请。
Deadlock Avoidance(死锁避免)
需要系统具有一些额外的先验信息提供
-
最简单和最有效的模式是要求每个进程声明它可能需要的每个类型资源的最大数目
-
资源的分配状态是通过限定提供与分配的资源数量,和进程的最大需求
-
死锁避免算法动态检查的资源分配状态,以确保永远不会有一个环形等待状态
-
当一个进程请求可用资源,系统必须判断立即分配是否能使系统处于安全状态
-
系统处于安全状态指:针对所有进程,存在安全序列。
-
序列
➢ 如果 P i P_iPi 资源的需求不是立即可用,那么 P i P_iPi 可以等到所有 P j P_jPj 完成
➢ 当 P i P_iPi 完成后, P i + 1 P_{i+1}Pi+1 可以得到所需要的资源,执行,返回所分配的资源,并终止。
➢ 用同样的方法, P i + 2 P_{i+2}Pi+2, P i + 3 P_{i+3}Pi+3 和 P n P_nPn 能获得其所需的资源。
-
如果系统处于安全状态 => 无死锁
-
如果系统处于不安全状态 => 可能死锁
-
避免死锁:确保系统永远不会进入不安全状态
如下图,死锁避免机制检验下一时刻的状态是否安全,然后根据情况将 e d g e edge edge 转换(图中实现虚线的转换,即尽管你有请求意图,但是可能并不允许占用)。
银行家算法
Banker’s Algorithm
银行家算法(Banker’s Algorithm)是一个死锁避免的著名算法,是由艾兹格 · 迪杰斯特拉在1965年为T. H. E系统设计的一种避免死锁产生的算法。
它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。
背景
在银行系统中,客户完成项目需要申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求并完成项目时,客户应及时归还。
银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。
在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。
前提条件
- 多个实例
- 每个进程都必须能最大限度地利用资源
- 当一个进程请求一个资源,就不得不等待
- 当一个进程获得所有的资源就必须在一段有限的时间释放它们
基于上述前提条件,银行家算法通过尝试寻找允许每个进程获得的最大资源并结束(把资源返还给系统)的进程请求一个理想执行时序,来决定一个状态是否是安全的。
不存在这满足要求的执行时序的状态都是不安全的。
数据结构
n = 进 程 数 量 , m = 资 源 类 型 数 量 n = 进程数量,m=资源类型数量 n=进程数量,m=资源类型数量
- Max(总需求量) Max(总需求量)Max(总需求量):n × m n×m n×m 矩阵。如果 M a x [ i , j ] = k Max[i,j]=k Max[i,j]=k,表示进程 Pi P_i Pi 最多请求资源类型 Rj R_j Rj 的 k k k 个实例。
- Available(剩余空限量) Available(剩余空限量)Available(剩余空限量):长度为 m m m 的向量,如果 A v a i l a b l e [ j ] = k Available[j]=k Available[j]=k,有 k k k 个类型 Rj R_j Rj 的资源实例可用。
- Allocation(已分配量) Allocation(已分配量)Allocation(已分配量):n × m n×m n×m 矩阵,如果 A l l o c a t i o n [ i , j ] = k Allocation[i,j]=k Allocation[i,j]=k,则 Pi P_i Pi 当前分配了 k k k 个 Rj R_j Rj 实例。
- Need(未来需要量) Need(未来需要量)Need(未来需要量):n × m n×m n×m 矩阵,如果 N e e d [ i , j ] = k Need[i,j]=k Need[i,j]=k,则 Pi P_i Pi 可能需要至少 k k k 个 Rj R_j Rj 实例完成任务。
N e e d [ i , j ] = M a x [ i , j ] − A l l o c a t i o n [ i , j ] Need[i,j]=Max[i,j]-Allocation[i,j] Need[i,j]=Max[i,j]−Allocation[i,j]
Safety State Estimating Algorithm(安全状态估计算法)
-
Work WorkWork 和 Finish FinishFinish 分别是长度为 m mm 和 n nn 的向量,初始化:
Work = Available // 当前资源剩余空闲量Finish[i] = false for i - 1,2,...,n // 线程i没结束 如果为true则说明结束
-
找这样的 i ii:(找出 Need NeedNeed 比 Work WorkWork 小的进程 i ii)
(a) Finish[i] = false(b) Need[i] <= Work
没有找到这样的 i ii,转到 4 44,找到,转 3 33
-
认为进程可以正常结束,并回收资源
Work = Work + Allocation[i]// 进程i的资源需求量小于当前的剩余空闲资源量Finish[i] = true// 所以配置给它再回收
转到 2 22
-
检验是否是处于安全状态
If Finish[i] == true for all i,then the system is in a safe state.// 所有进程的Finish为true,表明系统处于安全状态。
Banker’s Algorithm(银行家算法)
基于上述安全状态判断算法,设计银行家算法。
Initial: Request = request vector for process Pi. If Request[i, j] = k then process Pi wants k instances of resource type Rj.
W h i l e : While: While:
-
如果 Reques t i ⩽Nee d i Request_i \leqslant Need_iRequesti⩽Needi,转到步骤 2 22。否则,提出错误条件,因为进程i已经超过了其最大要求。
-
如果 Reques t i ⩽Available Request_i \leqslant AvailableRequesti⩽Available,转到步骤 3 33。否则, P i P_iPi 必须等待,因为资源不可用。
-
假装给 P i P_iPi 分配它需要的资源:(生成一个需要判断状态是否安全的资源分配环境)
Available=Available−Reques t i ; Available = Available - Request_i;Available=Available−Requesti;
Allocatio n i =Allocatio n i +Reques t i ; Allocation_i = Allocation_i + Request_i;Allocationi=Allocationi+Requesti;
Nee d i =Nee d i −Reques t i ; Need_i = Need_i - Request_i;Needi=Needi−Requesti;将上述 3 33 个变量作为安全状态判断算法的 input inputinput:
CALL Safety State Estimating Algorithm
➢ 如果返回 safe safesafe,将资源分配给 P i P_iPi
➢ 如果返回 unsafe unsafeunsafe, P i P_iPi 必须等待,旧的资源分配状态被恢复
银行家算法的安全状态判断示例
初始状态:
对于上面目前的 A v a l i a b l e Avaliable Avaliable,再参考每个进程的 N e e d Need Need,只有 P 2 P_2 P2 满足,所以 P 2 P_2 P2 执行,执行完将所属的 A l l o c a t i o n Allocation Allocation 释放,如下图:
接下来可使 P 1 P_1 P1 执行:
此时剩余的 P 3 P_3 P3、 P 4 P_4 P4 都可以满足。
得到了一个安全序列:
银行家算法的安全状态判断示例2
此时可以先执行 P 2 P_2 P2,但假如此时进程 P 1 P_1 P1 申请了 1 , 0 , 1 1,0,1 1,0,1 的资源,并分配给它后,如下图:
此时对于任意一个进程 P i P_i Pi 发出的 N e e d Need Need 请求, A v a i l a b l e Available Available 都无法满足,意味着这是一个 u n s a f e unsafe unsafe 状态,此时银行家算法不会满足进程 P 1 P_1 P1 发出的 1 , 0 , 1 1,0,1 1,0,1 请求。
银行家算法的思路就是:假定我分配之后,剩下的状态是否是安全的,如果安全则分配,反之则不分配。
Deadlock Detection(死锁检测)
- 允许系统进入死锁状态
- 死锁检测算法
- 恢复机制
把死锁的检测放到了更靠后的阶段,不是在每次发出请求时判断,而是在系统运行中的某一个特定时间做一下检测判断。
有死锁 没关系,我们把它检测出来并恢复。
每个资源类型单一实例
-
Maintain wait-for graph
➢ 节点是进程
➢ P i → P j P_i→P_jPi→Pj : P i P_iPi 等待 P j P_jPj
-
定期调用检测算法来搜索图中是否存在循环
-
算法需要 n 2 n^2n2 次操作, n nn 是图中顶点的数目
数据结构(资源类型的几个实例)
- A v a i l a b l e Available Available:长度为 M M M 的向量表示每种类型可用资源的数量。
- A l l o c a t i o n Allocation Allocation:一个 n × m n×m n×m 矩阵定义了当前分配给各个进程每种类型资源的数量,如果 A l o c a t i o n [ i , j ] = k Alocation[i, j] = k Alocation[i,j]=k, 进程 Pi P_i Pi 拥有资源 Rj R_j Rj 的 k k k 个实例。
- R e q u e s t Request Request:一个 n × m n×m n×m 矩阵表示各进程的当前请求.如果 R e q u e s t [ i , j ] = k Request[i, j] = k Request[i,j]=k,表示进程 Pi P_i Pi 请求 k k k 个资源 Pj P_j Pj 的实例。
死锁检验算法
-
Work WorkWork 和 Finish FinishFinish 分别是长度为 m mm 与 n nn 的向量,初始化:
(a) Work = Available// work为当前空闲资源量(b) For i = 1,2,...,n if Allocation[i] > 0then Finish[i] = falseotherwise Finish[i] = true// Finish为线程是否结束
-
找出这样的索引 i ii
(a) Finsih[i] == false// 线程没有结束(b) Request[i] <= Work// 且需要资源量小于当前空闲资源量
如果没有找到,转步骤 4 44,找到,转步骤 3 33
-
让该线程正常结束,将该线程持有资源释放回空闲资源中
Work = Work + Allocation[i]Finish[i] = true
转步骤 2 22
-
检验 Finish FinishFinish 是否有死锁
if Finish[i] == false, for some i, 1 <= i <= n// 系统处于死锁状态if Finish[i] == false// Pi死锁// 如果有Finish[i]等于false 这表示系统处于死锁状态
定期的执行该算法,检测系统中是否有环。
算法需要 O ( m × n 2 ) O(m×n^2) O(m×n2) 操作检测是否系统处于死锁状态。
银行家算法、死锁检测算法都需要先检测每个进程的最大需求量,时间复杂度比较高,因此,死锁检测过程更多是用来调试应用程序、操作系统,真正正常的跑某个系统基本不会用死锁检测算法。
检查算法使用
-
何时,使用什么样的频率来检测依赖于:
➢ 死锁多久可能会发生?
➢ 多少进程需要被回滚?
one for each disjoint cycle
-
如果检测算法多次被调用,有可能是资源图有多个循环,所以我们无法分辨出多个可能死锁进程中的哪些“造成”死锁。
检测算法在实际的运行环境中很难使用,更多是用在开发阶段来判断系统是否正确。
Recovery from Deadlock(死锁恢复)
-
终止所有的死锁进程
-
在一个时间内终止一个进程直到死锁消除
-
终止进程的顺序应该是
➢ 进程的优先级
➢ 进程运行了多久以及需要多少时间才能完成
➢ 进程占用的资源
➢ 进程完成需要的资源
➢ 多少进程需要被终止
➢ 进程是交互还是批处理
-
选择一个受害者 - 最小的成本。
-
回滚 - 返回到一些安全状态,重启进程到安全状态。
-
饥饿 - 同一进程可能一直被选作受害者,包括回滚的数量。
整理自 【清华大学】 操作系统