系列文章
第一章 操作系统概述
第二章 进程管理之进程描述与控制
第二章 进程管理之进程调度
第二章 进程管理之进程同步
第二章 进程管理 死锁
一组相互竞争系统资源或进行通信的进程间的永久阻塞
2.19 死锁原理
当一组进程中的每个进程都在等待某事件,而只有同组进程中阻塞的其他进程能够促发该事件时,死锁发生
2.19.1 资源分类
- 可重用资源:一次仅供一个进程使用且不因使用而消耗的资源,如cpu
- 可消耗资源:可被创建(生产)和销毁(消耗)的资源,如中断、消息
竞争这两种资源时,如果存在循环等待,可能会引起死锁
2.19.2 死锁的条件
- 必要条件
- 互斥:一次只有一个进程可以使用一个资源
- 占有且等待:当进程等待其他资源时继续占有已有的资源
- 不可抢占:进程不能强行占有其他进程占有的进程
- 充分条件
- 循环等待:存在一个闭合的进程链,每个进程至少占有此链中下一个进程所需的一个资源
2.19.3 死锁的解决
- 死锁预防:防止死锁的四个条件之一
- 死锁避免:防止循环等待的出现
- 死锁检测与解除
2.20 死锁预防
- 间接方法:防止死锁的三个必要条件之一
- 防止互斥:不可防止
- 防止占有且等待:要求进程一次性请求所有需要的资源(效率低、需要估计所需要的资源)
- 防止不可抢占:一个占有资源的进程请求资源不得则释放已占有资源或操作系统要求另一个进程释放资源(需要容易保存资源状态的结构)
- 直接方法:防止循环等待的出现
- 防止循环等待:定义一个请求资源的顺序,所有进程对资源的请求必须严格按资源序号递增的顺序提出(低效)
2.21 死锁避免
- 避免进入不安全状态
- 与死锁预防相比:允许三个必要条件
- 动态检查:运行过程中检查进程申请资源的结果是否安全,若会死锁则阻塞,否则予以分配
- 死锁避免两种方式
- 资源分配拒绝:若一个进程增加的资源请求导致死锁,则拒绝该请求
- 进程启动拒绝:若一个进程请求导致死锁,则拒绝启动该进程
2.21.1 银行家算法
属于资源分配拒绝策略,由Dijkstra于1965年提出
思想
用户申请一组资源时,系统判断分配后是否还处于安全状态,若是则允许分配,否则不分配,阻塞用户进程
安全状态
至少有一个资源分配安全序列,按照这个序列分配资源,可以不导致死锁地运行结束,相应的,不存在安全序列就是不安全状态
安全状态的判定
- Claim矩阵(后简称C矩阵):各个进程声明想要的资源数量
- Allocation矩阵(后简称A矩阵):已经分配给各个进程的资源数量
- C-A矩阵:又称Request矩阵:各个进程还需要资源的数量
- Resource向量(后简称R向量):各种资源的初始总量
- Available向量(后简称A向量):各种资源剩余可用的量
方法:
- 查看当前A向量能否满足C-A其中一行
- 满足则将A矩阵对应行加到A向量上,继续3,否则不安全
- 重复1
- C-A为0则得到安全序列
图解:
得到安全序列为P2,P1,P3,P4
- 安全序列不唯一,A向量满足C-A多行时可以尝试多个序列
- 安全状态也不一定不会死锁,安全状态时不按照安全序列分配就会死锁
- 并非所有不安全状态都是死锁状态
//全局数据结构
struct state {
int resource [ m ] ;
int available [ m ];
int claim [ n ][m ] ;
int alloc [ n ][m ] ;
}
//资源分配算法主体
if (alloc [ i,* ] + request [*]> claim [ i,* 〕)
<error >; /*total request > claim* /
else if (request [ * ] > available [ * ])
<suspend process >;
else { /*simulate alloc */
<define newstate by :
alloc [ i,* ]= alloc [ i,* ]+request [ * ];
available [*] = available [ * ] - request [* ] >;
}
if (safe ( newstate ) )
< carry out allocation >;
else {
<restore original state >;
<suspend process >;
}
//银行家算法测试安全性
boolean safe (state s) {
int currentavail[m];
process rest [<number of processes>];
currentavail = available;
rest = {
all processes};
possible = true;
while (possible) {
<find a process Pk in rest such that
claim [k,* ] - alloc [k,* ] <= currentavail;>
if (found) {
/*simulate execution of Pk */
currentavail = currentavail + alloc [ k,*];
rest = rest - {
Pk};
}
else possible = false;
}
return ( rest ==null );
}
2.21.2 评价死锁避免
- 优点
- 无需死锁预防中的抢占和回滚
- 比死锁预防限制更少
- 缺点
- 必须先得到每个进程声明请求的资源,即要有Claim矩阵
- 进程执行顺序必须没有要求,否则不能按照安全序列来
- 分配的资源数量必须固定
- 占有资源的进程不能退出
2.22 死锁检测与解除
2.22.1 算法步骤
- 标记A矩阵中一全零行
- 初始化W向量=A向量
- 在C-A矩阵查找下标i的进程行,未被标记且这一行<=W向量,若找不到则终止算法
- 若找到这样的行,标记进程i,把A矩阵中的相应行加到W向量中,返回3
- 当且仅当3终止时,仍有未标记的进程,才存在死锁,未标记的进程都是死锁状态
2.22.1 资源分配图检测方法
资源分配图的化简
- 找出全部请求都满足的节点Pi,消去其所有请求边和分配边,使之成为孤立节点
- 重复1直至无法化简
示例
可完全简化的,则不存在死锁
不能完全简化时,存在死锁(如下图)
2.22.2 死锁解除
- 撤销死锁进程,直到不再存在死锁
- 回退,回退到前面定义的某些检查点,重新启动所有进程
- 抢占,连续抢占直到不再有死锁
那么撤销哪些进程?又该抢占哪些进程的资源?
选择原则:输出少、剩余时间长、优先级低、目前为止消耗cpu时间少的
2.22.3 三种策略的比较
2.23 哲学家就餐问题
1965年由Dijkstra提出
问题描述
- 5个哲学家围坐圆桌
- 5只餐叉间隔摆放
- 哲学家行为为思考或进餐
- 哲学家进餐必须同时使用两边的餐叉
- 思考时蒋餐叉放回原处
- 两个哲学家不使用同一把叉子
- 尽量避免死锁和饥饿
如图
方案一 取左叉
都去拿左边的叉子,死锁
semaphore fork[5] = {
1, 1, 1, 1, 1};
void main()
{
cobegin {
philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think; //思考
wait(fork[i]); //拿起左边的叉子
wait(fork[(i+1)%5]); //拿起右边的叉子
eat();
signal(fork[i]); //放回左边的叉子
signal(fork[(i+1)%5]); //放回右边的叉子
}
}
方案二
请求同时拿两个叉子,被占则等待随机事件再请求,可能活锁
semaphore fork[5] = {
1, 1, 1, 1, 1};
void main()
{
cobegin {
philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think; //思考
wait(fork[i]); //拿起左边的叉子
timeout(wait(fork[(i+1)%5], [0, T]) //若右边的叉子被占用,则放下左边叉,等待一段随机时间后再拿
eat();
signal(fork[i]); //放回左边的叉子
signal(fork[(i+1)%5]); //放回右边的叉子
}
}
方案三 资源分级
为资源(这里是餐叉)分配一个偏序(partial order)或者分级(hierarchy)的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放,而且保证不会有两个无关资源同时被同一项工作所需要
① 为餐叉编号
- 就餐前,先取用编号较低的餐叉,再取用编号较高的餐叉
- 就餐毕,先放下编号较高的餐叉,再放下编号较低的餐叉
semaphore fork[5] = {
1, 1, 1, 1, 1};
void main()
{
cobegin {
philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think(); //思考
if (i != 4) {
wait(fork[i]); wait(fork[(i+1)%5]);} //先左后右
else {
wait(fork[(i+1)%5]); wait(fork[i]);} //先右后左
eat();
if (i != 4) {
signal(fork[(i+1)%5]); signal(fork[i]);} //先右后左
else {
signal(fork[i]); signal(fork[(i+1)%5]);} //先左后右
}
}
② 为哲学家编号
- 奇数号的哲学家必须首先拿左边的餐叉
- 偶数号的哲学家必须首先拿右边的餐叉
semaphore fork[5] = {
1, 1, 1, 1, 1};
void main()
{
cobegin {
philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think(); //思考
if (i % 2 != 0) {
wait(fork[i]); wait(fork[(i+1)%5]);} //先左后右
else {
wait(fork[(i+1)%5]); wait(fork[i]);}
eat();
signal(fork[(i+1)%5]); //先右后左
signal(fork[i]);
}
}
方案四 服务生方法
- 引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉
- 最多允许4个哲学家同时进食
semaphore fork[5] = {
1, 1, 1, 1, 1}, room = 4;
void main()
{
cobegin {
philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think; //思考
wait(room); //占据就餐位置
wait(fork[i]); //拿起左边的叉子
wait(fork[(i+1)%5]); //拿起右边的叉子
signal(fork[i]); //放回左边的叉子
signal(fork[(i+1)%5]); //放回右边的叉子
signal(room); //释放就餐位置
}
}
引申:And型信号量集
- 在一个原语中申请需要的多个临界资源,要么全部分,要么一个都不分配
- AND型信号量集P原语为Swait(Simultaneous Wait),V原语为Ssignal(Simultaneous Signal)。
- Swait(S1, S2, …, Sn)
- Ssignal(S1, S2, …, Sn)
管程方案
monitor dining controller;
cond ForkReady [5]; /*condition variable for synchronization */
boolean fork[ 5]={
true } ; /*availability status of each fork*/
void get_forks (int pid) /*pid is the philosopher id number */
{
int left = pid;
int right =(++pid)%5;
/*grant the left fork*/
if( ! fork [ left])
cwait(ForkReady [ left] ); /*queue on condition variable */
fork [ left]= false ;
/*grant the right fork*/
if( ! fork [ right] )
cwait(ForkReady [right] );
/*queue on condition variable */
fork [ right] = false;
}
void release_forks ( int pid){
int left = pid;
int right =(++pid)%5;
/*release the left fork*/
if( empty (ForkReady [ leftl) /*no one is waiting for this fork */
fork [ left] = true;
else /*awaken a process waiting on this fork */
csignal (ForkReady [ left]);
/*release the right fork*/
if (empty (ForkReady [right]) /*no one is waiting for this fork*/
fork [ right]= true;
else /*awaken a process waiting on this fork*/
csignal (ForkReady [right] );
}
void philosopher [ k=0 to 4] /*the five philosopher clients */
{
while (true){
<think>;
get_forks (k); /*client requests two forks via monitor * /
<eat spaghetti>;
release_forks (k); /*client releases forks via the monitor */
}
}