王道计算机操作系统学习心得
1. 管程
因为通过信号量保证互斥和同步比较复杂,所以引入了管程。
管程实际上是一种高级同步机制。管程的引入也是用来实现进程的同步于互斥的。
管程的定义有点像类,在管程中,有对临界资源的说明,有一组API来操作临界资源,同时也有对临界资源初始化的语句。
管程的特点:(这里的过程指的是一组函数)
- 局部于管程的数据只能被局部于管程的过程所访问
- 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
- 每次仅允许一个进程在管程内执行某个内部过程。
- 可以在管程中设置条件变量来实现同步问题。
生产者线程和消费者线程不需要考虑同步与互斥,只需要调用管程对应方法,同步互斥交给管程内部实现。类比于类的概念
2. 死锁
死锁:各个进程或线程互相等待对方的资源释放,导致各进程阻塞,无法推进的现象。
饥饿:各个进程因为长期等不到想要的资源,使得进程无法推进。
eg:短进程优先算法中,若有源源不断的短进程到来的时,长进程因为长期得不到调度引发长进程饥饿。
死锁产生的条件
- 互斥条件:只有存在对目标资源进行争抢的行为,才有可能出现死锁(哲学家进餐问题,打印机设备)
需要注意的是:像是内存和扬声器这类设备可以同时让多个进程使用不会导致死锁的,进程不需要阻塞等待这种资源。 - 不剥夺条件:进程所获得的资源在未使用完之前不能被其他进程剥夺,只能自己主动释放。
- 请求和保留条件:进程已经保持了一个资源,又要申请另一个资源,但是这个资源已经被其他进程申请,这就导致了原进程第一次申请的资源得不到释放。
- 循环等待条件:存在某一种资源循环等待链,链中的每一个进程以获得资源的同时被下一个进程请求。
预防死锁策略
静态方式:
- 破坏互斥条件:SPOOLing技术把独占设备改造成共享设备。(通过输出进程接受各个进程的使用需求,在由输出进程给到独占设备上)(不安全,一般不允许这样做)
- 破坏不剥夺条件:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用,可能导致进程饥饿)
- 破坏请求和保持条件:可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。(可能导致进程饥饿)
- 破坏循环等待条件:可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。(进程只能从小到大的申请资源,申请到大编号资源的进程不可能返回来申请小编号的资源,从而破坏了死锁,系统新增设备时,需要对所有资源重新编号)
避免死锁:银行家算法
安全序列: 就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。
不安全状态: 如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。
如果系统处于安全状态,则系统一定不会发生死锁,处于不安全状态不一定会发生死锁(可能中途有一些进程释放了资源),但是发生死锁,系统一定是不安全状态。
因此:可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是银行家算法的核心思想。
安全序列计算法
eg:系统有5个进程p0-p5 ,3种资源r0-r2,这三种资源的初始数量可以抽象为(10,5,7)
假设某次分配如下表
进程 | 最大需求 | 已经分配 | 剩余最大需求量 |
---|---|---|---|
p0 | (7,5,3) | (0,1,0) | (7,4,3) |
p1 | (3,2,2) | (2,0,0) | (1,2,2) |
p2 | (9,0,2) | (3,0,2) | (6,0,0) |
p3 | (2,2,2) | (2,1,1) | (0,1,1) |
p4 | (4,3,3) | (0,0,2) | (4,3,1) |
计算各个资源剩下的数量,可以求出,现在剩余的资源数目为(3,3,2)
之后循环判断剩余最大需求量,判断是否存在安全序列。
- (3,3,2)可以完全分配给p1进程,当p1进程归还资源后,剩余资源为(5,3,2),将p1加入安全序列
- 进入第二轮判断,此时(5,3,2)可以满足p3的最大需求,当p3进程归还资源后,资源序列变为(7,4,3),将p3添加到安全序列中。
- …
- 经过五轮循环后,可以得到安全序列:{p1 ,p3 ,p0 ,p2 ,p4},包括了所有的进程,所以系统处于安全状态。系统暂时不会出现死锁。
银行家算法步骤:
- 检查此次申请是否超过了之前声明的最大需求数
- 检查此时系统剩余的可用资源是否还能满足这次请求
- 试探着分配,更改各数据结构
- 用安全性算法检查此次分配是否会导致系统进入不安全状态
安全性算法步骤:
5. 检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收。
死锁的检测和解除
如果系统允许死锁的产生,那么系统就必须提供死锁的检测和解除的方法。
死锁检测
这里采用图的方式来进行死锁检测:
首先,进行数据结构抽象:
- 将每一个进程抽象成一个进程节点
- 将每一种资源抽象为一个资源节点(每种资源节点内的资源个数不止一个)
- 进程节点到资源节点这条边,代表了这个进程要申请那个资源,如果这个进程需要申请多个资源,那么就会有多条边。(请求边)
- 资源节点到进程节点这条边,代表了已经为进程分配了几个资源,每条边代表一个。(分配边)
如果系统中剩余的可用资源数足够满足进程的需求,那么这个进程暂时是不会阻塞的,可以顺利地执行下去。
如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去。
相应的,这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程…
死锁的检测就看请求边和对应节点的剩余资源的个数之间的关系:
- 首先 先看p1节点,p1一条请求边指向R2,R2分配边有一条,资源数目有两个,所以此时p1进程节点可以执行。
- 看p2节点,请求边指向R1,但是R1的分配边有三条,资源数目有三个,无剩余资源,所以p2节点需要阻塞。
调度完p1节点后,删除对应的请求边和分配边。使其变为孤立节点。
此时就可以调度p2节点了。当p2节点调度完成后,删除对应的请求边个分配边,让p2变为孤立节点。
此时这种状态称为这个资源分配图是可以完全简化的,此时不会发生死锁,类似于找到一个安全序列。
如果不能消除所有的边,那么就是发生了死锁。此时还保留边的进程节点就是死锁进程。这就是死锁定理
死锁的解除
- 资源剥夺法:挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿。
- 撤销进程法(或称终止进程法):强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这种方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止还得从头再来运行。
- 进程回退法:让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点。
撤销,剥夺或回退思路如下:
- 进程优先级低的优先被撤销剥夺。
- 进程运行时间短的优先被撤销。
- 进程运行的剩余时间长的优先被撤销
- 进程持有资源数目多的优先被撤销或剥夺。
- 批处理式进程优先于交互式进程被撤销。