15.操作系统死锁处理
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了15.操作系统死锁处理相关的知识,希望对你有一定的参考价值。
【README】
1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;
【19.1】死锁场景
1)死锁: 多个进程由于互相等待对方持有的资源而造成的谁也无法执行的情况;
1.1)死锁造成的结果:
- cpu执行的进程都在阻塞或自旋,且阻塞的进程越来越多,则cpu执行的指令没有运行业务程序,全都运行自旋程序(如while循环);这时用户感觉机器响应非常慢,最终不响应,或死机;
1.2)正常代码示例
// 生产者
Producer(item)
P(empty); // empty减1后判断empty是否小于0,是则表明没有可用缓冲区,当前进程阻塞
P(mutext);// mutex减1后判断mutex是否小于0,是则表明已有进程进入临界区(mutex初始值为1),当前进程阻塞
// 读入in;将item写入到in的位置上; (临界区)
V(mutex); // mutex加1后判断mutex是否小于等于0,是则表明存在阻塞进程,唤醒在mutex信号量上阻塞的进程
V(full); // full加1后判断full是否小于等于0,是则表明缓冲区中没有内容,唤醒在full信号量上阻塞的消费者进程(消费当前进程产生的数据内容);
// 消费者
Consumer()
P(full); // full减1后判断full是否小于0,是则表明缓冲区没有内容(可消费),当前进程阻塞
P(mutex); // mutex减1后判断mutex是否小于0,是则表明已有进程进入临界区(mutex初始值为1),当前进程阻塞
// 读入out; 从文件中的out 位置读出到item;(临界区)
// 打印item
V(mutex); // mutex加1后判断mutex是否小于等于0,是则表明存在阻塞进程,唤醒在mutex信号量上阻塞的进程
V(empty); // empty加1后判断 empty 是否小于等于0,是则表明没有可用缓冲区,唤醒在empty信号量上阻塞的生产者进程(使用当前进程释放的一个缓冲区);
补充:PV操作:
// 生产者:消费资源(这里消费资源指的是生产者消费一个空闲缓冲区,或一个数组项)
P (semaphore s)
s.value--; // 消费资源
if (s.value < 0)
sleep(s.queue); // 当前生产者进程睡眠
// 消费者:产生资源 (这里产生资源指的是消费者释放一个空闲缓存区,或一个数组项)
V (semaphore s)
s.value++; // 释放资源
if (s.value <=0 )
wakeup(s.queue); // 消费者唤醒睡眠的生产者进程
1.3)死锁代码示例(可以看做是用户程序写错了顺序)
同正常代码相比,把生产者的P(mutex) 放在P(empty)的前面,把消费者的P(mutex)放在 P(full)的前面,如下:
生产者 | 消费者 |
Producer(item) P(mutex); P(empty); // 读入in;将item写入到in的位置上; (临界区) V(mutex); V(full);
| Consumer() P(mutex); P(full); // 读入out; 从文件中的out 位置读出到item;(临界区) // 打印item V(mutex); V(empty); |
【代码解说】
- 生产者P(mutex):mutex减1后判断mutex是否小于0,小于0则阻塞;等待V(mutex)唤醒;
- 生产者P(empty):empty减1后判断empty是否小于0,小于0则阻塞;等待消费者的V(empty)唤醒;
- 生产者P(mutex):mutex减1后判断mutex是否小于0,小于0则阻塞;等待V(mutex)唤醒;
- 消费者P(full):full减1后判断full 是否小于0,小于0则阻塞;等待生产者的V(full)唤醒;
【产生死锁场景】
生产者代码1处的empty信号量上阻塞;
- 因为生产者获取了mutex互斥信号量没有释放,所以消费者在代码3处阻塞;
- 生产者代码1处的P(empty)只能被消费者代码2处的V(empty)唤醒;
- 又消费者代码2处的V(empty)依赖代码3处的P(mutex)被唤醒;
- 消费者代码3处的P(mutex)只能被生产者的代码4处的V(mutex)唤醒;
- 又生产者的代码4处的V(mutex)依赖代码1处的P(empty)被唤醒;
- 而代码1处的P(empty)只能被消费者代码2处的V(empty)唤醒(这里就形成一个环路了,死循环了......);
为什么会出现死锁?
- 原因是因为对 mutex的加锁解锁顺序不一致(加锁解锁的正确顺序应该是先进后出),造成各自占有的资源和互相申请的资源形成记录环路等待。
- 正常代码是 先对empty加锁,再对mutex加锁,对mutex解锁,再对full解锁;
- 死锁代码是 先对mutex加锁,再对empty加锁,对mutex解锁,再对full解锁;
【19.2】 死锁成因
1)死锁成因: 各自占有的资源和互相申请的资源形成环路等待。
【19.3】死锁的4个必要条件
1)4个必要条件
- 互斥使用:资源的固有属性;
- 不可抢占:资源只能自愿放弃;
- 请求和保持:进程必须占有资源,再去申请;
- 循环等待:在资源分配图中存在一个环路;
【19.4】死锁处理方法概述
1)死锁处理的4种方法:
- 死锁预防: 破坏死锁的条件;
- 死锁避免: 检测每个资源请求,如果造成死锁就拒绝;
- 死锁检测与恢复: 检测到死锁出现,让一些进程回滚,让出资源;
- 死锁忽略(不处理死锁):就好像没有出现死锁一样;(对于普通pc机器,可以使用,因为可以通过重启来解决死锁)
【19.4.1】死锁预防的方法例子
方法1:在进程执行前,一次性申请所有需要的资源,不会占有资源再去申请其他资源;
- 缺点1:需要预知未来,编程困难;
- 缺点2:许多资源分配后很长时间后才使用,资源利用率低;
方法2: 对资源类型进行排序,资源申请必须按序进行,不会出现环路等待;
- 缺点: 仍然造成了资源浪费;
【19.4.2】死锁避免
1)判断此次请求是否引起死锁;
2)如果系统中的所有进程存在一个可完成的执行序列 P1, ..., Pn,则称系统处于安全状态;
3)银行家算法:
- 如何判断系统中是否存在一个可完成的执行序列,通过银行家算法来实现;
4)银行家算法:找出一个可完成的执行序列的算法
补充:
- m表示资源的个数;n表示进程的个数;
- 每做一次资源分配时,都要执行银行家算法,每次时间复杂度为 10^6,若m=100,n=100的情况下;
- 缺点:银行家算法的时间复杂度为 O(mn^2),时间复杂度比较高,效率低;
补充:银行家算法实例
图片解说:
- Allocation:已分配资源; 如 P0对应已分配的A B C类资源分别为 0个 1个 0个 ;
- Need: 还需要分配资源;如P0对应还需要分配的A B C资源分别为 7个,4个,3个;
- Available:操作系统可用资源个数;如 ABC对应332 表示可用的A B C资源分别为 3个,3个,2个;
银行家算法步骤:
- 步骤1:把可用资源332分配给P0,而P0还是无法执行,且P1,P2,P3,P4 这4个进程也无法执行,所以死锁;所以可用资源拒绝分配给P0;(这是一种预分配算法)
- 步骤2:把可用资源122分配给P1,P1可以运行起来,则实际情况是操作系统把可用资源122分配给P1;P1运行完成后,释放出分配的资源,则可用资源为 532(因为P1会释放2个A类资源);
- 步骤3:把可用资源532分配给P2,P2无法执行,且除P1外的其他进程也无法执行,则死锁;所以532拒绝分配给P2;
- 步骤4:把011分配给P3,P3可以执行,则实际情况是操作系统把011分配给P3,以此类推....;
因为把可用资源210分配给进程P0后,P0无法执行,且其他进程因为没有可用资源也无法执行,这就造成了死锁,所以银行家算法的分配结果是拒绝进程P0的资源申请请求;
【19.4.3】死锁检测与恢复:发现问题再处理
1)定时检测或者是发现资源利用率低时检测;
2)进程回滚
- 问题1:选择哪一个进程回滚;
- 问题2:进程如何回滚,回滚到什么状态下可以解除死锁;
【19.4.4】死锁忽略
许多通用操作系统,如windows,linux,都 采用了死锁忽略方法,因为死锁忽略的代价最小;
以上是关于15.操作系统死锁处理的主要内容,如果未能解决你的问题,请参考以下文章