死锁
基本概念
- 定义
- 一组进程中, 每个进程都无限等待被该组进程中另一进程所占有的资源, 因而永远无法得到的资源,这种现象称为进程死锁,这一组进程称为死锁进程.
- 死锁的危害
- 如果发生死锁, 资源就会大量浪费,最后导致系统崩溃
- 为什么出现死锁?
- 可重用资源: 可以被多个进程多次使用
- 可抢占资源与不可抢占资源
*处理器, I/O部件, 内存, 文件, 数据库, 信号量
- 可消耗资源: 只可使用一次,可创建和销毁的资源
- 带来死锁的可能:
- 进程竞争可重用资源(两个进程同时申请内存, 但是内存不够, 双方都不释放资源)
- 进程竞争可消耗资源
- 产生死锁的必要条件
- 互斥使用(资源独占): 一次只能给一个进程使用
- 占有且等待: 进程在申请新的资源时, 保持对原有资源的占有
- 不可抢占: 资源申请者不能从资源占有者那方抢占资源
- 循环等待: 假设有p1,p2,p3三个进程, p2等待p1释放资源, p3等待p2, p1等到p3那么三个进程形成一个环路。
- 以上几个条件只要有一个不存在, 那么就不会有死锁现象发生
资源分配
- 资源分配图
- 死锁定理
- 如果资源分配图中没有环路, 则系统中没有死锁, 如果图中存在环路则可能产生死锁
- 如果环路中每个资源只有一个实例 那么环路就是死锁的充分必要条件
预防
- 解决死锁的方法
- 死锁预防
- 静态策略: 设计合适的资源分配算法, 不让死锁发生
- 在设计系统时, 就设计好资源分配算法, 核心点在于破坏产生死锁中的四个必要条件之一
- 资源转换技术(破坏互斥): 将独占资源->共享资源
- 破坏"占有等待"
- 要求进程运行前一次性申请所有需要的资源(资源利用率很低), 容易饥饿现象发生
- 进程在申请资源得不到的时候, 主动释放再等待, 上CPU的时候再重新申请
- 破坏"不可抢占": 优先级高的可以抢占资源
- 破坏"循环等待": 定义资源类型的线性顺序实现, 资源的有序分配法. 进程申请资源按照严格的顺序进行。
- 死锁避免
- 动态策略: 动态的跟踪资源, 已到达不让死锁发生的效果
- 进程向进程申请的资源可以得到满足时, 动态的检测是否可能发生死锁
- 安全序列: 通过动态检测, 发现任一进程中所需资源不超过当前剩余资源与所有进程占有资源总和,那么我们称进程是安全状态的
- 不安全状态: 系统找不到一个安全序列, 一定会发生死锁
- 让死锁发生
避免
银行家算法
- 先决条件
- 固定进程共享固定数量资源
- 每个进程预先指定完成工作所需的最大资源量
- 进程不能申请比当前资源总数还多的资源
- 进程等待资源的时间是有限的
- 如果系统满足了进程所需要的最大资源, 那么进程需要在有限的时间内还给进程
- 核心思想
解除
- 死锁检测
- 系统允许死锁发生, 但是会不断监视系统进展情况
- 一旦死锁发生, 则采用专门的措施解除死锁, 并以最小的代价恢复操作系统运行
- 进程申请资源不能满足当前状态而进入等待状态
- 定时检测
- 系统资源率下降再检测
死锁检测算法
- 给进程和资源唯一编号
- 两张表(数据结构)
- 流程
- 查看进程等待表, 找到资源分配表查找有没有环, 如果最后出现了环路说明有死锁
死锁的接触
- 撤销所有死锁的线程
- 进程的回退(RollBack) 再启动, 不完全撤销
- 逐一撤销死锁进程, 直到解除死锁
- 不断剥夺资源, 直到解除
哲学家就餐问题
- 问题描述
- 一个圆桌, 五个哲学家, 五根筷子, 一碗粉. 需要让每个人都吃上粉, 怎么设计?
- 解决方案:
- 最多允许四个哲学家就餐, 另外一个做别的事
- 只有当一个哲学家两边都有筷子时才能就餐
- 给所有哲学家编号, 奇数的拿左边, 偶数反之
信号量方案
semaphore forks[5] = {1};
semaphore room = 4;
int i;
void zhexuejia(int i){
P(room);
P(fork[i]);
P(fork[(i+1) % 5]);
V(fork[(i+1) % 5]);
V(fork[i]);
V(room);
}
管程方案
while(true){
<think>
get_fork(k);
<eat>
release_fork(k);
}
monitor controller
cond ForkReady[5];
bool fork[5] = {true};
void get_forks(int pid){
int left = pid;
int right = (++pid) % 5;
if (!fork[left]){
cwait(ForkReady(left));
fork[left] = false;
}
if(!fork[right]){
cwait(ForkReady(right));
fork[right] = false;
}
}
void release_forks(int pid){
int left = pid;
int right = (++pid) % 5;
if (empty(ForkReady[left])){
fork[left] = true;
}else{
csignal(ForkReady[left]);
}
if (empty(ForkReady[right])){
fork[right] = true;
}else{
csignal(ForkReady[right]);
}
}
参考