目录
一、死锁的定义:
如果一个进程集合中的 每个进程 都在等待 只能由进程集合中的其他进程 才能引发的事件,那么,该进程集合就是死锁的。
进程的数量以及占有或者请求的资源数量和种类都是无关紧要的,而且无论资源是何种类型(软件或者硬件)都会发生这种结果。这种死锁称为 资源死锁(resource deadlock),这是常见的类型。
由于所有的进程都在等待,所有没有一个进程能引发可以唤醒该进程集合中的其他进程的事件, 这样, 所有的进程都 会无限期的等待下去。
二、发生资源死锁的四个必要条件:
1. 互斥条件
每个资源要么已经分配给一个进程,要么就是可用的。
2. 占有和等待条件
已经得到了某个资源的进程,可以再请求新的资源。
3. 不可抢占条件
已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
4. 环路等待条件
死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,该环路中的每个进程都在等待着下一个进程所占有的资源。
死锁发生时,以上四个条件一定是同时满足的。 如果其中任何一个条件不成立,死锁就不会发生。
三、死锁的模型
用圆形表示 进程, 用方形表示 资源。 从资源节点到进程节点的有向边,代表该资源已被请求、授权并被进程占用。 从进程节点到资源节点的有向边,代表当前进程正在请求该资源,并且该进程已被阻塞,处于等待该资源的状态。
左图含义是: 当前资源R正被进程A占用 ; 右图含义是: 进程B正在等待着资源S
死锁的模型示例如下:
上图所示的进程都进入了死锁的状态:
进程C等待这资源T, 资源T被进程D占用着, 进程D又等待着由进程C占用的资源U。
这样两个进程都得等待下去, 也就是两个进程 在争夺 两个资源,最终形成了 ,谁都无法使用资源的状态。
进程对资源的请求,与 资源的占用模型,只要能形成环, 环上的所有进程都是死锁进程。
四、四种处理死锁的策略:
1.忽略该问题。 也许如果你忽略它,它也会忽略你
2.检测死锁并恢复。 让死锁发生,检测他们是否发生,一旦发生死锁,采取行动解决问题。
3.仔细对资源进行分配,动态地避免死锁。
4.通过破坏死锁的四个必要条件之一,防止死锁的产生。
五、经典的IPC问题 哲学家就餐问题
问题描述:
哲学家的生活中有两种交替活动时段:即吃饭和思考。 当一个哲学家觉得饿了时,他就试图分两次去取其左边和右边的叉子,就开始吃饭,吃完后放下叉子继续思考。
能为下图中五个哲学家,每一个哲学家写一段描述其行为的程序,且决不会死锁吗?
现在操作系统 ,提供的一种可行的解决方案:
#define N 5 /*哲学家数目*/
#define LEFT (i+N-1)%N /*i的左邻居编号*/
#define RIGHT (i+1)%N /*i的右邻居编号*/
#define THINKING 0 /*哲学家在思考*/
#define HUNGRY 1 /*哲学家试图拿起叉子*/
#define EATING 2 /*哲学家进餐*/
typedef int semaphore; /*信号量 是一种特殊的整型数据*/
int state[N]; /*数组用来跟踪记录每位哲学家的状态*/
semaphore mutex = 1; /*临界区的互斥*/
semaphore s[N]; /*每个哲学家一个信号量*/
void philosopher(int i) /*i:哲学家编号,从0 到 N-1*/
{
while(true) /*无限循环*/
{
}
}
void take_forks(int i) /*i:哲学家编号,从 0到 N-1*/
{
down(&mutex); /*进入临界区*/
state[i] = HUNGRY; /*记录哲学家i处于饥饿的状态*/
test(i); /*尝试获取2把叉子*/
up(&mutex); /*离开临界区*/
down(&s[i]); /*如果得不到需要的叉子则阻塞*/
}
void put_forks(i) /*i:哲学家编号,从0到N-1*/
{
down(&mutex); /*进入临界区*/
state[i] = THINKING; /*哲学家已经就餐完毕*/
test(LEFT); /*检查左边的邻居现在可以吃吗*/
test(RIGHT); /*检查右边的邻居现在可以吃吗*/
up(&mutex); /*离开临界区*/
}
void test(i) /*i:哲学家编号,从0到N-1*/
{
if(state[i] == HUNGRY && state[LEFT]!=EATING && state[RIGHT]!=EATING)
{
state[i] = EATING;
up(&s[i]);
}
}
算法中使用一个数组state跟踪每一个哲学家是在进餐、思考还是饥饿状态, 一个哲学家只有在两个邻居都没有进餐时,才允许进入到进餐状态。
程序使用了一个信号量数组, 每个信号量对应一位哲学家,这样在所需的叉子被占用时,想进餐的哲学家就被阻塞。
注意,每个进程将过程philosopher作为主代码运行,而其他过程take_forks、put_forks 和 test 只是普通的过程,而非单独的进程。