资源
需要排他性使用的对象。正因为资源具有排他性,发生死锁的情况通常是,两个进程都占有完成任务所需要的所有资源的一部分,并且都不愿意释放。
可抢占资源和不可抢占资源
可抢占资源是指从拥有它的进程中抢占而不会产生任何副作用。存储器就是典型的可抢占资源。
不可抢占资源是指,抢占进程占有的资源会导致计算失败。
使用资源的步骤:
1、请求资源
2、使用资源
3、释放资源
比如一个文件资源,当它正在被使用时,发出open调用的进程被阻塞,一直到文件的使用者关闭该文件为止。该进程在阻塞的这段时间,会处在这样一个循环中:请求资源,休眠,再请求资源。
资源获取
用户管理资源的方法是,为每个资源分配一个信号量,信号量初始化为1,每次执行down操作来获取资源,释放资源后,执行up操作。
● 一个进程情况下,不存在资源竞争,无死锁。
#使用信号量保护一个资源
typedef int semaphore;
semaphore resource_1;
void process_A(void){
down(&resoure_1);//资源1可用数量减1
use_resource_1();
up(&resource_1);//资源1可用数量加1
}
#使用信号量保护两个资源
typedef int semaphore;
semaphore resource_1;
semaphore resource_2;
void process_B(void){
down(&resource_1);
down(&resource_2);
use_both_resources();
up(&resource_2);
up(&resource_1);
}
● 两个进程情况下,存在资源竞争,有可能死锁。
#无死锁代码
typedef int semaphore;
semaphore resource_1;
semaphore resource_2;
void process_A(void){
down(&resource_1);
down(&resource_2);
use_both_resources();
up(&resource_2);
up(&resource_1);
}
void process_B(void){
down(&resource_1);
down(&resource_2);
use_both_resources();
up(&resource_2);
up(&resource_1);
}
#有可能出现死锁代码,A进程拥有resource_1,B进程拥有resource_2,两个进程都阻塞
typedef int semaphore;
semaphore resource_1;
semaphore resource_2;
void process_A(void){
down(&resource_1);
down(&resource_2);
use_both_resources();
up(&resource_2);
up(&resource_1);
}
void process_B(void){
down(&resource_2);
down(&resource_1);
use_both_resources();
up(&resource_1);
up(&resource_2);
}
死锁
如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件,那么,该进程集合就是死锁的。
假设进程只含有一个线程,并且被阻塞的进程无法被中断唤醒(无中断条件)。死锁进程集合中的每个进程都在等待其他进程已经占有的资源,并永远等待下去。
考虑一个例子,当两列火车在相同的轨道上相向行驶时,只有一条轨道,一旦他们在彼此前面,没有一辆火车可以移动。当有两个或多个进程保存一些资源并等待其他资源占有的资源时,操作系统中会发生类似的情况。例如,在下图中,进程1占有资源1并等待由进程2占有的资源2,而进程2等待资源1。
资源死锁的条件:
● 互斥条件。每个资源要么已经分配给一个进程,要么就是可用的。
● 占有和等待条件。已经得到资源的进程可以再请求新的资源。
● 不可抢占条件。已经分配给一个进程的资源不能强制性被抢占,必须拥有它的资源显式释放。
● 死锁环路条件。死锁发生时,有两个或两个以上进程组成一个环路,该环路里的每个进程都在等待下一个进程占有的资源。
死锁建模
资源分配图
圆形表示进程,方形表示资源。箭头指向资源,表示进程在请求资源;箭头指向进程,表示进程占有该资源。
根据给定的资源请求/释放队列,按次序画资源分配图,每一步后检查资源分配图是否包含有向环路,如果包含环路,则会发生死锁;否则,没有死锁。
处理死锁的策略
有三种处理死锁的方式
1)忽略死锁:如果死锁非常罕见,那么让它发生并重新启动系统。这是windows和unix采取的方法。
2)死锁检测和恢复:发生死锁,然后进行抢占处理一次。
3)死锁预防或避免:想法是不让系统进入死锁状态。
1、鸵鸟算法
即忽略该问题,不做任何检测和恢复。
2、 死锁检测和恢复
如何检测死锁
传统的Windows操作系统不会因为时间和空间消耗进程而进行死锁恢复。实时操作系统使用死锁恢复。
每种类型资源个数只有一个的死锁检测
维护一个队列,对资源分配图中的每个节点,以它为一个棵数的根结点,进行深度优先搜索,入队,如果队列中出现重复的结点,说明有环,则有死锁;资源分配图中每个节点的队列没有重复的结点,则没有环。
每种类型资源个数为多个的死锁检测
1)寻找一个系统可用资源满足请求资源的线程,用Pi向量表示,Pi<=A
2)Pi运行完,释放它占有的资源到可用资源池,可用资源池用A向量,A=A+Pi
3)再次寻找为运行的资源请求可被满足的线程,如果没有这样的线程,说明死锁发生
恒等式:Ej=Pj+Aj,Pj表示进程j已分配的资源,Aj表示进程j可用的资源,Ej表示现有资源。
死锁检测的例子
E向量表示系统中四种资源总量,A向量表示四种资源当前数量,C是已经分配给三个进程的资源数量,R表示三个进程的请求资源的数量。首先,,进程3的资源请求能被满足,运行进程3;进程3终止后,释放进程3的资源,接着进程2被满足,运行进程2;等到进程2释放它的资源,进程1资源请求被满足,最后运行进程1.
何时去检测死锁
一种方法是,每当有资源请求时取检测。这种方法太占用CPU时间了
另一种方法是,每隔k分钟检测一次,或者当CPU利用率降到某个阈值时,去检测死锁。
检测到死锁如何恢复
1、利用抢占恢复
人工干预进程管理的一种方式,通常由管理员根据该资源本身的特性,在不通知原进程的情况下,强行剥夺走资源,通过这种方法恢复比较困难,而且不现实。
2、利用回滚恢复
周期性的对进程进行检查点检查,检查点检查就是将某一个时刻的进程的状态写入一个文件。文件中包括存储映像,还有资源状态。新的检查点覆盖旧的检查点。发生死锁时,拥有所需资源的进程恢复到一个较早的状态,这个状态下,它还没占有资源,然后将所需资源分配给死锁进程。
3、通过杀死进程恢复
一种方法是杀掉环中的一个进程,如果杀死一个不够,那就继续杀死别的进程直到破坏死锁。
另一种方法是牺牲环外的一个进程,释放它的资源。
死锁避免
资源轨迹图
横轴表示进程P在运行,进程Q被挂起(1、2轨迹)。纵轴表示进程Q在运行,进程P被挂起(5、6轨迹)。
因为进程的执行不能后退,所以轨迹总是向上或向右的方向,3、4轨迹不可避免的会进入死锁,因为资源互斥使用规则决定了P和Q都不可能进入单斜线区域,所以只能进入交叉斜线区域(死锁区).
银行家算法是资源分配和死锁避免算法,用于对所有请求资源的进程进行测试,检查安全状态,如果在授权请求后,系统仍保持在安全状态,允许请求,如果它们不是安全状态, 不允许进程发出的请求。
安全状态和不安全状态
这两种状态前提是都没有死锁产生。
如果存在一个调度次序使得每一个进程运行完毕,则为安全状态
不安全状态不是死锁,因为进程不一定要最大需求量的资源。但是任何调度次序都不能保证工作完成,比如b)中的状态。
银行家算法的输入
1.每个过程最多需要的资源数量。
2.每个进程目前分配资源数量。
3.系统中最大可用的资源。
请求仅在以下条件下授予:
1.如果进程的请求小于等于该进程的最大值。
2.如果进程的请求小于等于系统中的可用资源。
单个资源的银行家算法
银行家算法是对每一个请求进行检查。检查如果满足这一请求是否会进入安全状态,即是否有足够的资源满足某一进程。如果可以,满足请求;否则,推迟满足请求。
多个资源的银行家算法
检查一个状态是否安全的算法如下:
1)检查右边矩阵是否存在一个进程,其仍需要的资源小于或等于A,如果不存在,则系统会死锁;
2)如果存在这样一个进程,则把该进程标记为终止,并将资源加到向量A上。
3)重复以上两步
这样的算法有两个结果,要么所有的进程都标记为终止,其初始状态是安全的,要么所有进程的资源都不能满足,系统发生死锁。
实际上, 银行家算法缺乏使用价值,一是进程所需资源的最大值很难在运行前确定,二是进程数量在操作系统运行过程中不断变化,三是有些资源可能突然间变得不可用。
死锁预防
我们可以通过消除上述四种条件来防止死锁。
1、破坏互斥条件
资源不被一个进程独享,那么死锁肯定不会产生。但破坏互斥条件是很困难的,因为一些资源(如打印机)本质上是不可共享的。
2、破坏占有和等待条件
禁止已占有资源的进程再请求其他资源。
第一种实现方法是,直到进程所需的全部资可用,系统才分配给该进程。但很多进程直到运行时才知道它需要多少资源,而且这种方法资源利用率不是最优的
第二种实现方法是,当一个进程请求资源时,先释放它占有的资源,然后再尝试一次性获得所需的所有资源。这种方案很可能导致饥饿
3、破坏不可抢占条件
在其他高优先级进程需要资源时,抢占资源。但抢占资源,会引起混乱,虚拟化的方式可以解决这个问题,例如,只有打印机守护进程才能访问真正的打印机。
4、破坏环路等待条件
一种实现方法是,对所有资源进程统一编号。进程必须按照资源编号的升序顺序提出请求,也就是说,一个进程首先要查看自己占有资源的最高编号,不能申请低于最高编号的资源。例如,如果p1进程被分配r5资源,下次如果p1请求r4,r3,它们的编号小于r5,则不会给予这样的请求,所以只会请求超过r5的资源。
实现的困难在于很难找到每个人都满意的编号次序。
小结
通信死锁
假设处于网络中的两个进程,进程A向进程B发送消息,由于网络阻塞等原因,消息丢失,则A一直等待B回复,而B无法收到请求,A和B进程都阻塞。这种通信中断死锁,可以引入超时机制来解决。进程A发送消息的同时,启动计时器,设定一个时间,如果在A确认收到回复前到时,则重新发送消息。
活锁
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。在UNIX中,进程表容纳的进程数有最大限制,如果由于进程表满,而fork失败,程序会等待一段随机长的时间,再执行一次fork
饥饿
由于资源分配策略不当等原因,导致某些进程一直无法获得资源而不能继续执行。比如,有些进程永远无法获得CPU。可以用先来先服务的分配资源策略来避免饥饿。