第一部分:死锁(Deadlock)
1. 死锁的定义
死锁是指两个或多个进程(或线程) 在执行过程中,因争夺资源而造成的一种相互等待的现象。若无外力干涉,这些进程都将无法向前推进。
2. 死锁产生的四个必要条件
死锁的发生必须同时满足以下四个条件:
- 互斥条件: 资源一次只能被一个进程独占使用(如打印机、临界区代码)。
- 持有并等待(请求保持条件): 一个进程在持有至少一个资源的同时,又在等待获取其他进程持有的资源。
- 非抢占(不可剥夺条件): 资源不能被强制从持有它的进程那里剥夺,只能由持有者主动释放。
- 循环等待(环路条件): 存在一个进程等待链:
P0
等待P1
持有的资源,P1
等待P2
持有的资源,……,Pn
等待P0
持有的资源。
3. 处理死锁的策略
- 预防(Prevention): 在设计系统时,确保至少破坏死锁四个必要条件中的一个。
- 破坏互斥: 让某些资源可共享(如只读文件)。但很多资源本质必须互斥(如打印机)。
- 破坏持有并等待:
- 方案1:一次性申请所有资源。 进程在运行前申请所需的所有资源,否则阻塞直到所有资源都可用。缺点:资源利用率低,可能饥饿。
- 方案2:申请新资源前先释放所有已持有资源。 缺点:代价大,可能导致状态丢失。
- 破坏非抢占:
- 如果进程请求资源失败,强制释放其持有的某些资源(可能导致该进程回退)。
- 如果进程请求的资源被其他等待进程持有,可以强制剥夺该资源(实现复杂,代价高)。
- 破坏循环等待:
- 强制顺序申请资源。 给所有资源类型编号,进程只能按递增顺序申请资源(如必须先申请编号小的资源,才能申请编号大的资源)。这是最实用的预防方法。
- 避免(Avoidance): 在资源分配时动态检查,确保系统始终处于安全状态,从而避免进入可能死锁的状态。核心算法就是银行家算法(第二部分详解)。
- 检测(Detection)与恢复(Recovery): 允许死锁发生,但系统能检测到死锁并采取措施恢复。
- 检测: 周期性地运行死锁检测算法(如基于资源分配图RAG的简化方法)。
- 恢复:
- 进程终止:
- 终止所有死锁进程: 简单粗暴,代价高。
- 终止部分进程(按优先级、代价最小原则): 逐步终止进程直到死锁解除。
- 资源抢占:
- 选择一个牺牲者进程,强制剥夺其资源给其他进程。
- 需解决回滚(Rollback) 问题(进程状态恢复)和饥饿(Starvation) 问题(同一进程反复被选为牺牲者)。
- 进程终止:
4.资源分配图(RAG)化简步骤详解(带图例)
以下是资源分配图化简的核心步骤,结合具体例子和图解说明:
案例演示(系统资源:R1(2个实例), R2(1个实例)
进程状态:
- P1:持有R1×1,请求R2
- P2:持有R1×1,请求R2
- P3:持有R2×1,请求R1
初始资源分配图:
化简步骤演示:
步骤1:找非孤立且只有分配边的进程
✅ 符合条件:无(P1/P2有请求边,P3有请求边)
步骤2:找非孤立且只有请求边的资源
🔍 发现资源R2:
- 有2个请求边(P1→R2, P2→R2)
- 但无空闲实例(已被P3占用)
- 无法化简
死锁验证:
- 环路1:P1→R2→P3→R1→P1
- 环路2:P2→R2→P3→R1→P2
- 结论:存在死锁!
无死锁案例对比:
修改后的状态:
- P1:持有R1×1,请求R2
- P3:持有R2×1(无请求)
初始图:
化简过程:
- 找到P3:只有分配边(无请求边)
- 释放R2资源:
- 转换P1的请求边:
- 找到P1:只有分配边(R1→P1和R2→P1)
- 所有结点变孤立 → 无死锁!
关键总结表:
步骤 | 操作目标 | 成功标志 | 死锁判断 |
---|---|---|---|
1. 找只有分配边的进程 | 释放其所有资源 | 进程变孤立 | 若找不到则进入步骤2 |
2. 转换等待进程请求边 | 请求边→分配边 | 资源重新分配 | 循环直到无法操作 |
最终状态 | 检查所有结点 | 全孤立=无死锁 | 有非孤立结点=存在死锁 |
第二部分:银行家算法(Banker’s Algorithm)
银行家算法是 Dijkstra 提出的一种死锁避免算法。它模拟银行家放贷的策略:在分配资源前,先检查此次分配是否会导致系统进入不安全状态。只有能确保系统始终处于安全状态的分配请求才会被批准。
1. 核心概念与数据结构
假设系统有 n
个进程 (P0, P1, ..., Pn-1
) 和 m
种资源类型 (R0, R1, ..., Rm-1
)。
- 可用资源向量(Available Vector) -
Available[1..m]
:- 长度为
m
的数组。 Available[j] = k
表示资源类型Rj
当前有k
个实例可用。
- 长度为
- 最大需求矩阵(Max Matrix) -
Max[n][m]
:n x m
矩阵。Max[i][j] = k
表示进程Pi
最多需要k
个资源类型Rj
的实例。
- 已分配矩阵(Allocation Matrix) -
Allocation[n][m]
:n x m
矩阵。Allocation[i][j] = k
表示进程Pi
当前已持有k
个资源类型Rj
的实例。
- 需求矩阵(Need Matrix) -
Need[n][m]
:n x m
矩阵。Need[i][j] = k
表示进程Pi
未来还可能需要k
个资源类型Rj
的实例。- 计算:
Need[i][j] = Max[i][j] - Allocation[i][j]
。
2. 安全性算法(Safety Algorithm)
用于判断当前系统状态是否安全。安全状态意味着存在一个安全序列(Safe Sequence):一个进程执行顺序 <P1, P2, ..., Pn>
,使得按此顺序执行,每个进程 Pi
都能顺利完成(即 Pi
需要的资源 <=
当前可用资源 + 所有排在它前面的进程 Pj
(j < i
) 释放的资源)。
算法步骤:
- 初始化:
Work[1..m] = Available[1..m]
// 工作向量,初始化为当前可用资源Finish[1..n] = false
// 标记每个进程是否完成
- 寻找一个进程
Pi
满足:Finish[i] == false
// 进程未完成Need[i][1..m] <= Work[1..m]
// 该进程所需的所有资源 <= 当前可用资源 (Work
)- 如果找到,执行步骤 3;否则(一个都找不到),执行步骤 4。
- 假设进程
Pi
运行完成并释放其持有的资源:Work[1..m] = Work[1..m] + Allocation[i][1..m]
// 回收Pi
的资源Finish[i] = true
// 标记Pi
完成- 返回步骤 2。
- 检查所有进程是否完成:
- 如果
Finish[i] == true
对所有i
都成立,则系统处于安全状态。 - 否则,系统处于不安全状态。
- 如果
3. 资源请求算法(Resource-Request Algorithm)
当进程 Pi
发出资源请求向量 Request_i[1..m]
时,银行家算法执行以下步骤决定是否批准请求:
- 基本检查:
- 如果
Request_i[j] <= Need[i][j]
对所有j
(1 ≤ j ≤ m) 成立,则转步骤 2。否则,请求非法(进程申请超过其声明的最大需求),报错拒绝。 - 如果
Request_i[j] <= Available[j]
对所有j
(1 ≤ j ≤ m) 成立,则转步骤 3。否则,Pi
必须等待(资源不足)。
- 如果
- 试探性分配(Pretend to Allocate): 假设系统批准了请求:
Available[j] = Available[j] - Request_i[j]
// 更新可用资源Allocation[i][j] = Allocation[i][j] + Request_i[j]
// 更新已分配资源Need[i][j] = Need[i][j] - Request_i[j]
// 更新需求资源
- 检查安全性: 运行安全性算法检查试探性分配后的新状态是否安全。
- 如果新状态是安全的:则实际执行分配操作(即确认步骤 2 的试探性分配)。
- 如果新状态是不安全的:则拒绝请求!进程
Pi
必须等待。撤销步骤 2 的试探性分配,恢复原状态(Available
,Allocation
,Need
回滚)。
4. 银行家算法示例 (简化)
一、核心数据结构(矩阵表示)
初始状态矩阵(5个进程,3类资源)
进程 | Allocation (已分配) | Max (最大需求) | Need (还需资源) | Available (可用资源) |
---|---|---|---|---|
A B C | A B C | A B C | A B C | |
P0 | 0 1 0 | 7 5 3 | 7 4 3 | 3 3 2 |
P1 | 2 0 0 | 3 2 2 | 1 2 2 | |
P2 | 3 0 2 | 9 0 2 | 6 0 0 | |
P3 | 2 1 1 | 2 2 2 | 0 1 1 | |
P4 | 0 0 2 | 4 3 3 | 4 3 1 |
关键公式:Need = Max - Allocation
二、安全性检查算法流程
步骤1:初始化工作向量
Work = Available = [3, 3, 2]
Finish = [false, false, false, false, false]
步骤2:寻找可执行进程
步骤3:执行P1并更新矩阵
Work = Work + Allocation[P1] = [3,3,2] + [2,0,0] = [5,3,2]
Finish[P1] = true
进程 | Allocation | Max | Need | Work更新 |
---|---|---|---|---|
P1 | 2 0 0 | 3 2 2 | 1 2 2 | [5,3,2] ✅ |
步骤4:继续寻找可执行进程
步骤5:执行P3并更新矩阵
Work = [5,3,2] + [2,1,1] = [7,4,3]
Finish[P3] = true
进程 | Allocation | Max | Need | Work更新 |
---|---|---|---|---|
P3 | 2 1 1 | 2 2 2 | 0 1 1 | [7,4,3] ✅ |
最终安全序列
完整安全序列:P1 → P3 → P0 → P2 → P4
三、资源请求算法示例
场景:P1请求资源 Request = [1, 0, 2]
步骤1:检查请求合法性
步骤2:模拟分配(更新矩阵)
矩阵 | 更新前 | 更新后 |
---|---|---|
Available | [3,3,2] | [3-1,3-0,2-2]=[2,3,0] |
Allocation[P1] | [2,0,0] | [2+1,0+0,0+2]=[3,0,2] |
Need[P1] | [1,2,2] | [1-1,2-0,2-2]=[0,2,0] |
步骤3:安全性检查(新状态)
进程 | Allocation | Need | Work | Finish |
---|---|---|---|---|
A B C | A B C | A B C | ||
P0 | 0 1 0 | 7 4 3 | 2 3 0 | false |
P1 | 3 0 2 | 0 2 0 | false | |
P2 | 3 0 2 | 6 0 0 | false | |
P3 | 2 1 1 | 0 1 1 | false | |
P4 | 0 0 2 | 4 3 1 | false |
安全序列查找过程:
-
P1: Need[0,2,0] ≤ Work[2,3,0] → 执行
Work = [2,3,0] + [3,0,2] = [5,3,2]
-
P3: Need[0,1,1] ≤ [5,3,2] → 执行
Work = [5,3,2] + [2,1,1] = [7,4,3]
-
P0: Need[7,4,3] ≤ [7,4,3] → 执行
Work = [7,4,3] + [0,1,0] = [7,5,3]
-
P2: Need[6,0,0] ≤ [7,5,3] → 执行
Work = [7,5,3] + [3,0,2] = [10,5,5]
-
P4: Need[4,3,1] ≤ [10,5,5] → 执行
✅ 存在安全序列:P1 → P3 → P0 → P2 → P4
✅ 请求被批准
五、死锁场景(无安全序列)
场景:P2请求资源 Request = [1, 0, 1]
模拟分配后状态:
矩阵 | 值 |
---|---|
Available | [2,3,1] |
Allocation[P2] | [4,0,3] |
Need[P2] | [5,0,-1] ❌(无效) |
⚠️ 请求被拒绝:请求超过最大需求(Need矩阵出现负值)
六、算法总结流程图
银行家算法矩阵操作要点:
- 资源请求时需双验证:
Req ≤ Need
且Req ≤ Available
- 安全性检查核心:寻找安全序列
- 矩阵更新需保持一致性:
- Available = Available - Request
- Allocation = Allocation + Request
- Need = Need - Request
- 安全状态判断标准:存在完整安全序列
- 多资源类型需逐元素比较
💡 实际应用:该算法需要预知进程最大资源需求,适用于批处理系统或资源分配固定的场景,在交互式系统中应用有限但原理重要。
5. 银行家算法的优缺点
- 优点:
- 理论上能完全避免死锁。
- 比死锁预防策略(如强制顺序申请)更灵活,资源利用率可能更高。
- 缺点(限制):
- 需要预知最大需求 (
Max
矩阵): 进程必须事先声明其整个生命周期所需的最大资源量。这在动态变化的系统中很难做到。 - 进程数量固定: 算法假设进程数量
n
是固定的。在动态创建/销毁进程的环境中,维护Max
,Allocation
,Need
矩阵很困难。 - 开销大: 每次资源请求都需要运行安全性算法(时间复杂度约为
O(m * n^2)
),在进程和资源数量多时开销显著。 - 资源类型固定: 资源类型
m
需要固定。 - 可能导致不必要的拒绝: 即使系统有足够资源且分配后实际不会死锁,但安全性算法认为不安全也会拒绝请求,降低了资源利用率。
- 需要预知最大需求 (
- 实际应用: 由于上述限制,银行家算法很少在通用操作系统的核心资源分配中直接使用。但它作为死锁避免理论的典范被广泛研究和教学。其思想(安全状态检查)可能被用于特定场景(如某些数据库管理系统、嵌入式系统)。