- 参考:《现代操作系统》
- 在计算机系统中有很多独占性的资源, 在任一时刻它们都只能被一个进程使用 (如打印机)。 正因为如此, 操作系统都具有授权一个进程(临时)排他地访问某一种资源的能力
- 在很多应用中, 需要一个进程排他性地访问若干种资源而不是一种。这时就有可能产生死锁
- 例如, 有两个进程准备分别将扫描的文档记录到蓝光光盘上。 进程 A A A 请求使用扫描仪, 并被授权使用。 但进程 B B B 首先请求蓝光光盘刻录机, 也被授权使用。 现在, A A A 请求使用蓝光光盘刻录机, 但该请求在 B B B 释放蓝光光盘刻录机前会被拒绝。 但是, 进程 B B B 非但不放弃蓝光光盘刻录机, 而且去请求扫描仪。 这时, 两个进程都被阻塞, 并且一直处于这样的状态
- 除此之外,在数据库系统中也可能产生死锁
资源 (resource)
- 资源:需要排他性使用的对象;使用一个资源所需要的事件顺序可以用抽象的形式表示如下:(1) 请求资源; (2) 使用资源; (3) 释放资源
- 若请求时资源不可用, 则请求进程被迫等待。 进程可能会被 OS 自动阻塞,也可能不断进行轮询 (下面假设采用进程被 OS 自动阻塞的方式)
可抢占资源和不可抢占资源
- 可抢占资源可以从拥有它的进程中抢占而不会产生任何副作用 (e.g. 存储器、CPU)
- 不可抢占资源: 在不引起相关的计算失败的情况下, 无法把它从占有它的进程处抢占过来 (e.g. 驱动器、打印机)
- 总的来说, 死锁与不可抢占资源有关, 有关可抢占资源的潜在死锁通常可以通过在进程之间重新分配资源而化解。 所以, 我们的重点放在不可抢占资源上
死锁的概念
死锁
- 指多个进程在运行过程中因争夺资源而造成的一种僵局,若无外力作用,这些进程都将无法向前推进
- (1) 参与死锁的进程数至少为 2; (2) 参与死锁的所有进程均等待资源; (3) 参与死锁的进程至少有两个占有资源; (4) 参与死锁的进程是系统中当前正在运行进程的一部分
- 例如,按下列顺序进行可能产生死锁:
- (1) 参与死锁的进程数至少为 2; (2) 参与死锁的所有进程均等待资源; (3) 参与死锁的进程至少有两个占有资源; (4) 参与死锁的进程是系统中当前正在运行进程的一部分
- 进程推进顺序不当引起死锁
- e.g. 进程推进顺序合法 (紫,绿,蓝); 进程推进顺序非法(红色折线)
- e.g. 进程推进顺序合法 (紫,绿,蓝); 进程推进顺序非法(红色折线)
产生死锁的条件和处理
必要条件
- 互斥条件 (资源独占): 一个资源每次只能给一个进程使用
- 请求和保持条件 (部分分配,占有申请): 一个进程在申请新的资源的同时保持对原有资源的占有
- 非剥夺条件 (不可抢占资源): 资源申请者不能强行从资源占有者手中夺取资源,资源只能由占有者自愿释放
- 循环等待条件: 存在一个进程等待队列 { P 1 , P 2 , … , P n } \{P_1, P_2 , … , P_n\} {P1,P2,…,Pn}, 其中 P 1 P_1 P1 等待 P 2 P_2 P2 占有的资源, P 2 P_2 P2 等待 P 3 P_3 P3 占有的资源,…, P n P_n Pn 等待 P 1 P_1 P1 占有的资源,形成一个进程等待环路
处理死锁的四种基本方法
- 预防死锁:指通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件 (常针对条件 2, 3, 4),来防止死锁的发生
- 避免死锁:指在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生
- 检测死锁:允许系统在运行过程中发生死锁,但可设置检测机构及时检测死锁的发生,并采取适当措施加以清除
- 解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来
死锁的预防
- (1) 破坏互斥条件 (不可行): 即允许多个进程同时访问资源。但由于资源本身固有特性限制,有的资源根本不能同时访问,只能互斥访问,所以不可能用破坏互斥条件来预防死锁
- (2) 破坏请求和保持条件: 可采用预先静态分配方法,即要求进程在运行之前一次申请它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦运行后,这些资源全归其占有,同时它也不再提出其它资源要求,这样可以保证系统不会发生死锁
- 此方法虽简单安全,但降低了资源利用率,同时必须预知进程所需要的全部资源
- (3) 破坏不可剥夺条件: 一个已经获得某些资源的进程,若又请求新的资源时不能得到满足,则它必须释放出已获得的所有资源,以后需要资源时再请求。即一个进程已获得的资源在运行过程中可被剥夺。从而破坏了“不剥夺”条件
- 这种方法实现较复杂,会增加系统开销,降低系统吞吐量
- (4) 破坏环路条件: 可采用有序资源分配方法,即将系统中的所有资源都按类型赋予一个编号,要求每一个进程均严格按照编号递增的次序来请求资源,同类资源一次申请完
- 也就是,只要进程提出请求资源 R i R_i Ri,则在以后的请求中,只能请求 R i R_i Ri 后面的资源,这样不会出现几个进程请求资源而形成环路
- 该方法虽提高了资源的利用率,但编号难,加重进程负担及因使用资源顺序与申请顺序不同而造成资源浪费
死锁的避免
- 死锁的避免: 系统运行过程中, 允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程; 否则,令进程等待
- 在该方法中把系统的状态分为安全状态和不安全状态,只要能使系统始终处于安全状态,便可以避免死锁的发生
系统的安全状态
- 指在某一时刻,系统能按某种进程顺序 ( P 1 , P 2 , … , P n ) (P_1,P_2,…,P_n) (P1,P2,…,Pn) 来为每个进程 P i P_i Pi 分配其资源, 直到满足每个进程对资源的最大需求, 使每个进程都可顺利地完成,称此时的系统状态为安全状态,称序列 ( P 1 , P 2 , … , P n ) (P_1,P_2,…,P_n) (P1,P2,…,Pn) 为安全序列
- 若某一时刻系统中不存在这样一个安全序列,则称此时的系统状态为不安全状态
安全、不安全、死锁状态空间
- 如果一个系统在安全状态,就没有死锁
- 如果一个系统处于不安全状态,就有可能死锁
- 避免死锁的实质:确保系统不进入不安全状态
安全状态实例
- 假定系统中有三个进程
P
1
P_1
P1,
P
2
P_2
P2 和
P
3
P_3
P3,共有 12 台磁带机,
T
0
T_0
T0 时刻,三个进程对磁带机的需求和占有情况如下表所示:
- T 0 T_0 T0 时刻,存在一个安全序列 ( P 2 , P 1 , P 3 ) (P_2,P_1,P_3) (P2,P1,P3),所以系统是安全的
- 在 T 0 T_0 T0 时刻后, P 3 P_3 P3 又请求一台磁带机,若满足 P 3 P_3 P3 的请求,系统进入不安全状态,会死锁 (因为分配完后还剩 2 台可用,这 2 台分给 P 2 P_2 P2, P 2 P_2 P2 完成后,只能释放 4 台,这既不能满足 P 1 P_1 P1 (5 台),也不能满足 P 3 P_3 P3 (6 台),将导致死锁)
银行家算法
- 银行家算法的模型基于一个小城镇的银行家。假定一个银行家拥有资金,数量为
Σ
Σ
Σ,被
N
N
N 个客户共享 (在银行家算法中,客户可看做进程,资金可看做资源,银行家可看做操作系统)。银行家对客户提出下列约束条件:
- 每个客户必须预先说明自已所要求的最大资金量
- 每个客户每次提出部分资金量申请并获得分配
- 如果银行满足了客户对资金的最大需求量,那么,客户在资金动作后,应在有限时间内全部归还银行
- 只要每个客户遵守上述约束,银行家将保证做到:若一个客户所要求的最大资金量不超过 Σ Σ Σ,则银行一定接纳该客户,并可处理他的资金需求;银行在收到一个客户的资金申请时,可能因资金不足而让客户等待,但保证在有限时间内让客户获得资金
- 银行家算法就是对每一个请求进行检查,检查如果满足这一请求是否会达到安全状态。若是,就满足该请求。若否,就推迟对这一请求的满足
例子: 金庸给大侠贷款
- 在某一时刻,具体贷款情况如图
B
B
B; 该状态是安全的,由于保留了 2 个单位资金,金庸能够拖延除了乔峰之外的其他客户的请求. 先让乔峰把生意完成,然后还款所贷的 4 个单位的资金。有了这 4 个单位的资金,金庸就可以给杨过或令狐冲贷款,以此类推
- 这一天,令狐冲来再要求贷款 1 万美金。可否贷给他?如果贷款给令狐大侠,状态如图
D
D
D。该状态是不安全的。如果忽然所有的大侠都请求最大限额贷款。而金庸无法满足任何一个,就会产生死锁,可能导致各位大侠资金链断裂,全部破产。不安全状态并不一定引起死锁,由于各个大侠不一定需要最大贷款额度,但金庸老先生不敢报侥幸心理
银行家算法
- 假定系统中有
n
n
n 个进程
(
P
1
,
P
2
,
…
,
P
n
)
(P_1,P_2,…,P_n)
(P1,P2,…,Pn),
m
m
m 类资源
(
R
1
,
R
2
,
…
,
R
m
)
(R_1,R_2,…,R_m)
(R1,R2,…,Rm),银行家算法中使用的数据结构如下:
- 可利用资源向量:
available[j] = k
, 资源 R j R_j Rj 类有 k k k 个可用 - 最大需求矩阵:
Max[i, j] = k
, 进程 P i P_i Pi 最大请求 k k k 个 R j R_j Rj 类资源 - 分配矩阵:
Allocation[i, j] = k
, 进程 P i P_i Pi 分配到 k k k 个 R j R_j Rj 类资源 - 需求矩阵:
Need[i ,j] = k
, 进程 P i P_i Pi 还需要 k k k 个 R j R_j Rj 类资源Need[i, j] = Max[i, j] – Allocation[i, j]
- 可利用资源向量:
- 资源分配算法: (1) 系统试分配资源给进程 P i P_i Pi; (2) 系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,则正式进行分配,否则恢复原状态,让进程 P i P_i Pi 等待
// 资源分配
Available[j] := Available[j]- Requesti[j];
Allocation[i, j] := Allocation[i,j] + Requesti[j]
Need[i, j]:= Need[i, j]- Requesti[j]
- 安全性检查算法: 需要定义如下数据结构:
int Work[m]
: 记录可用资源。开始时,Work := Available
boolean Finish[n]
: 记录进程是否执行完。开始时,Finish[i] = false
; 当有足够资源分配给进程 P i P_i Pi 时,令Finish[i] = true
- (1)
Work := Available
;Finish[i] := false
- (2) 寻找满足如下条件的进程
P
i
P_i
Pi:
Finish[i] = false
并且Need[i, j] ≤ Work
; 如果找到,转 (3),否则转 (4) - (3) 当进程
P
i
P_i
Pi 获得资源后,可顺利执行完,并释放分配给它的资源 ,故执行:
Work := Work + Allocation; Finish[i] := true
. 转 (2). - (4) 若所有进程的
Finish[i] = true
,则表示系统处于安全状态,否则处于不安全状态
银行家算法的例子
- 假定系统中有 5 个进程, 3 类资源及数量分别为
A
A
A(10个),
B
B
B (5个),
C
C
C (7个),
T
0
T_0
T0 时刻的资源分配情况如下表所示:
- (1)
T
0
T_0
T0 时刻的安全性: 利用安全性算法对
T
0
T_0
T0 时刻的资源分配情况进行分析,可得下表 (Work + Allocation 表示该进程分配了足够多的资源,执行完成后,可用的资源为原有资源量 + 之前分配给该进程的资源量 (进程执行完,该部分资源就被释放了)); 分析得知:
T
0
T_0
T0 时刻存在着一个安全序列
{
P
1
,
P
3
,
P
4
,
P
2
,
P
0
}
\{P_1, P_3, P_4, P_2, P_0\}
{P1,P3,P4,P2,P0},故系统是安全的
- (2)
P
1
P_1
P1 请求资源
Request1(1, 0, 2)
- 系统按银行家算法进行检查:
Request1(1,0,2) ≤ Need1(1,2,2)
且Request1(1,0,2) ≤ Available(3,3,2)
;因此系统试为 P 1 P_1 P1 分配资源, 并修改相应的向量 (Available
、Need
、Allocation
)
- 利用安全性算法检查资源分配后系统是否安全; 由安全性检查分析得知: 此时刻存在着一个安全序列
{
P
1
,
P
3
,
P
4
,
P
2
,
P
0
}
\{P_1,P_3,P_4,P_2,P_0\}
{P1,P3,P4,P2,P0},故系统是安全的,可以立即将
P
1
P_1
P1 所申请的资源分配给它
- 系统按银行家算法进行检查:
- (3)
P
4
P_4
P4 请求资源
Request4(3, 3, 0)
- 系统按银行家算法进行检查:
Request4(3,3,0) ≤ Need4 (4,3,1)
但Request4 (3,3,0) > Available (2,3,0)
,表示资源不够,则让 P 4 P_4 P4 等待
- 系统按银行家算法进行检查:
- (4)
P
0
P_0
P0 请求资源
Request0(0,2,0)
- 系统按银行家算法进行检查:
Request0(0,2,0) ≤ Need0(7,4,3)
且Request0(0,2,0) ≤ Available(2,3,0)
; 系统试为 P 0 P_0 P0 分配资源, 并修改相应的向量
- 进行安全性检查:资源分配后此时系统是否安全,因
Avaliable(2,1,0)
已不能满足任何进程需要,故系统进入不安全状态,此时系统不分配资源
- 系统按银行家算法进行检查:
死锁的检测与解除
- 如果在一个系统中,既未采用死锁预防方法,也未采用死锁避免方法,而是直接为进程分配资源,则系统中便有可能发生死锁。此时系统需提供死锁检测和解除的手段
死锁的检测
资源分配图
- 资源分配图将进程和资源间的申请和分配关系描述成一个有向图,之后便可以利用某种算法对这些信息加以检查,以判断是否存在死锁
- 结点集合
N
N
N 被分为两个互斥子集:
- 进程结点子集: 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}
- 边集合
E
E
E 可分为
- 请求边: e = ( P i → R j ) e=(P_i → R_j ) e=(Pi→Rj)
- 分配边:
e
=
(
R
j
→
P
i
)
e=(R_j → P_i )
e=(Rj→Pi)
- 结点集合
N
N
N 被分为两个互斥子集:
圆圈表示一进程,方框表示一类资源,其数目由方框中的小圆圈数表示
死锁的必要条件
- 如果一个图中没有环路,则系统中不存在死锁;若有环路,系统可能存在死锁 (环路是死锁的必要条件,但不是充分条件)
资源分配图中的化简方法
- 可以通过对资源分配图进行化简,来判断系统是否处于死锁状态:
- (1) 寻找一个既不阻塞又非孤立 (能申请到需要的资源;有边) 的进程结点 P i P_i Pi,若无,则算法结束
- (2) 去除 P i P_i Pi 的所有分配边和请求边,使 P i P_i Pi 成为一个孤立节点 (相当于该进程节点 P i P_i Pi 正常结束,释放相应的资源)
- (3) 转步骤 (1)
- 在进行一系列化简后,若能消去图中所有的边,使所有进程都成为孤立结点,则称该图是可完全简化的;反之,称该图是不可完全简化的
死锁定理
- S S S 为死锁状态 ⇔ \Leftrightarrow ⇔ S S S 状态的资源分配图是不可完全简化的
死锁检测 vs. 死锁避免
- 采用银行家算法来预防死锁是可靠的,但也是比较保守的,因为它限制了进程对资源的存取。从而降低了进程的并发运行程度
- 死锁检测并不限制进程对资源的申请,只要有就分配,但这也可能造成死锁。但由于死锁并不是经常发生的,故大大提高了系统运行的效率
死锁检测的时机
- 进程请求资源得不到满足而等待时(开销大)
- 定时检测
- 系统资源利用率下降
死锁检测算法
基本思想
- 获得时刻 t t t 系统中各类可利用资源的数目向量 w ( t ) w(t) w(t), 对于系统中的一组进程 { P 1 , P 2 , … , P n } \{ P_1,P_2, …,P_n\} {P1,P2,…,Pn},找出那些对各类资源请求数目均小于系统现有的各类可利用资源数目的进程。这样的进程可以获得它们所需要的全部资源并运行结束,当它们运行结束后释放所占有的全部资源,从而可用资源数目增加,将这样的进程加入到可运行结束的进程序列 L L L 中,然后对剩下的进程再作上述考查。如果一组进程 { P 1 , P 2 , … , P n } \{ P_1,P_2, …,P_n\} {P1,P2,…,Pn} 中有几个进程不属于序列 L L L 中,那么它们会发生死锁
数据结构
- 可利用资源向量:
Available[j] = k
, 资源 R j R_j Rj 有 k k k 个可用 - 最大需求矩阵:
Max[i, j] = k
, 进程 P i P_i Pi 最大请求 k k k 个 R j R_j Rj 资源 - 分配矩阵:
Allocation[i, j] = k
, 进程 P i P_i Pi 分配到 k k k 个 R j R_j Rj 资源 - 需求矩阵:
Need[i, j] = k
, 进程 P i P_i Pi 还需要 k k k 个 R j R_j Rj 资源 (Need[i, j] = Max[i, j] – Allocation[i, j]
)
检测算法
- (1) 定义变量
Work
和Finish
,分别存放可用资源和进程占用资源状态:- (a)
Work := Available
- (b) For
i
=
1
,
2
,
…
,
n
i = 1,2, …, n
i=1,2,…,n, if
Allocation_i ≠ 0
, thenFinish[i] := false
; otherwise,Finish[i] := true
(不占用任何资源 → \rightarrow → 随时都可以结束的进程)
- (a)
- (2) 找到满足以下条件的下标
i
i
i,如果没有这样的
i
i
i 存在, go to step 4
- (a)
Finish[i] = false
- (b)
Request_i ≤ Work
- (a)
- (3)
Work := Work + Allocation_i
;Finish[i] := true
; go to step 2. - (4) If
Finish[i] = false
, for some i i i, 1 ≤ i ≤ n 1 ≤ i ≤ n 1≤i≤n, then the system is in deadlock state. Moreover, ifFinish[i] = false
then P i P_i Pi is deadlocked
死锁的解除
- 一旦检测出系统中出现了死锁,就应将陷入死锁的进程从死锁状态中解脱出来,常用的解除死锁方法有两种:
- (1) 资源剥夺法: 当发现死锁后,从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态
- (2) 撤消进程法: 最简单撤消进程的方法是使全部死锁的进程夭折掉;但以前工作全部作废, 损失可能很大; 另一方法是按照某种顺序逐个地撤消进程,直至有足够的资源可用,死锁状态消除为止
按复杂度递增的顺序列出的方法:
- 撤消所有的死锁进程
- 死锁进程回滚到某些检查点
- 连续撤消死锁进程直到不再存在死锁
- 连续剥夺资源直到不再存在死锁
对于 3 和 4,选择代价最小的进程进行
代价最小的进程考虑因素:
- 目前为止消耗的处理器时间最少;
- 目前为止产生的输出最少;
- 预计剩下的时间最长;
- 目前为止分配的资源总量最少;
- 优先级最低
死锁的综合处理策略
- 将全部资源按层次分成若干类,对于每一类,使用最适合于它的办法解决死锁问题。由于使用了资源分层技术,在一个死锁环中,通常只包含某一个层次的资源,因此,整个系统就不会受控于死锁了
- 例:4 个不同层次的资源组成的系统:
- (1) 内部资源。这是由系统本身使用的资源,例如进程控制块 PCB → \rightarrow → 资源有序分配
- (2) 主存储器。主要指用户态程序空间 → \rightarrow → 剥夺资源 (因为主存和外存可以对换)
- (3) 作业资源。可分配的设备 (如磁带机) 和文件 → \rightarrow → 死锁避免算法,作业要求的资源信息从作业控制表中获得
- (4) 交换空间。存放用户作业的后援存储器空间 → \rightarrow → 预先分配策略,存储空间的最大需求量通常是知道的