死锁

死锁的定义:

一组进程中,每个进程都无限等待被该组中另一进程所占有的资源,因而永远无法得到的资源,这种现象称为进程死锁,这一组进程就称为死锁进程

死锁例子:

1车想通过ab区域,2车想通过bc区域,3车想通过cd区域,4车想通过da区域,如果没有引导将导致:

1车等待2车让出b区域,2车等待3车让出c区域,3车等待4车让出d区域,4车等待1车让出a区域,若4辆车中没有一辆退步的话,所有车将无限等待下去,这边产生了死锁

 

死锁产生原因:

 资源数量有限锁和信号量错误使用

 对于资源可分成:

可重用资源(可被多个进程多次使用的资源):分成可抢占资源(例如CPU)不可抢占资源

可消耗资源(只可使用一次,可以创建和销毁):中断,信号,消息

 

因为可重用资源产生的死锁:

P1,P2先各自分别得到了80kbytes和70Kbytes,之后两个进程要想接着得到所需资源,就要等待对方释放占有资源,但是两者均在等待对方完成但又不释放自己所占有的资源,导致了死锁

 

因为可消耗资源导致的死锁:

P1等待P2进程发给自己的信息,而P2进程等待P1进程发给自己的信息,导致互相等待产生死锁

 

活锁和饥饿:

活锁:进程有上CPU执行,但执行的是忙等待,即无进展也没有阻塞

饥饿:进程没有上过CPU执行过,其由资源分配策略决定

 

 产生死锁的必要条件:

1.互斥使用(资源一次只能由一个进程独占

2.占有且等待(进程在申请新的资源的同时保持对原有资源的占有)

3.不可抢占(资源申请者不能强行的从资源占有者手中夺取资源资源只能由占有者自愿释放

4.循环等待(存在一个进程队列(P1,P2,P3,P4...Pn),P1等待P2占有的资源,P2等待P3.........Pn等待P1所占有的资源,形成进程等待环路)

 

 资源分配图:

用有向图描述系统资源和进程的状态

系统由若干类资源构成,一类资源称为一个资源类;每个资源类中包含若干个同种资源,称为资源实

资源类:用方框表示

资源实例:用方框中的黑圆点表示

进程:用圆圈中加进程名表示

死锁资源分配图:

对于资源分配图中死锁定理:

1.如果资源分配图中没有环路,则系统中没有死,如果图中存在环路则系统中可能存在死锁
2.如果每个资源类中只包含一个资源实例,则环路是死锁存在的充分必要条件

 

右图中,P4使用后会将R2资源实例返回给P3使用,P3使用后会将R1资源实例返回给P1使用,之后P1,P2执行完,并没有产生死锁。

资源分配图化简:

1.找一个非孤立、且只有分配边的进程结点去掉分配边,将其变为孤立结点(上面右图P4进程)

2.再把相应的资源分配给一个等待该资源的进程即将该进程的申请边变为分配边

重复1、2

 

练习:

P1只有分配边,所以将P1去掉,r1,r2资源实例给P4进程,之后进程P4执行完,但是之后出现死锁,P2需要r3资源,但是r3资源被P5资源占有,P5资源又等待被P3占用的r4资源,所以产生死锁。

 

死锁预防:

解决死锁方法:

1.不考虑此问题(鸵鸟算法
2.不让死锁发生
   死锁预防
      静态策略:设计合适的资源分配算法,不让死锁发生
   死锁避免
  动态策略:以不让死锁发生为目标,跟踪并评估资源分配过程,根据评估结果决策是否分配
3.让死锁发生
    死锁检测与解除

 

死锁预防:

在设计系统时,通过确定资源分配算法,排除发生死锁的可能性,具体的实现就是破坏死锁的四个必要条件:

1.破坏“互斥使用/资源独占”条件把独占资源变为共享资源,例如:

Spooling技术的引入
解决不允许任何进程直接占有打印机的问题
实现:设计一个“守护进程/线程”负责管理打印机,进程需要打印时,将请求发给该daemon,由它完成打印任务

2.破坏“占有且等待”条件:

实现:

静态策略:

要求每个进程在运行前必须一次性申请它所要求的所有资源,且仅当该进程所要资源均可满足时才给予一次性分配

缺点:资源利用率低(可能大部分时间都没用到某资源);“饥饿”现象(可能由于资源没有一次都满足而进入执行,即一直被推迟上CPU)

动态策略:

在允许进程动态申请资源前提下规定,一个进程在申请新的资源不能立即得到满足而变为等待状态之前,必须释放已占有的全部资源,若需要再重新申请

3.破坏“不可抢占”条件:

实现:

当一个进程申请的资源被其他进程占用时,可以通过操作系统抢占这一资源(根据两个进程优先级不同),但只适用于那些状态易于保存和恢复的资源例如CPU、内存

4.破坏“循环等待”条件

实现:资源有序分配法
把系统中所有资源编号,进程在申请资源时必须严格按资源编号的递增次序进行,否则操作系统不予分配

为何这样可以预防死锁产生?

原因:因为每次我们可以根据申请的资源号最大来找对应的进程,然后因为资源号最大的后面资源均没有被占用,而且进程申请资源也是按照递增序列,所以让那个进程线执行完,之后便释放掉他占有的资源,便产生不了死锁。

 

死锁避免:

定义:
在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统发生死锁或可能发生死锁,则不予分配,否则予以分配

安全状态:
如果系统中存在一个由所有进程构成的安全序列P1,…,Pn,则称系统处于安全状态

安全序列:

一个进程序列{P1,…,Pn}是安全的,如果对于每一个进程Pi(1≤i≤n):
它以后还需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和则称系统处于安全状态,安全状态一定没有死锁发生

 

银行家算法:

定义:仿照银行发放贷款时采取的控制方式而设计的一种死锁避免算法

应用条件:

1. 在固定数量的进程中共享数量固定的资源
2. 每个进程预先指定完成工作所需的最大资源数量
3. 进程不能申请比系统中可用资源总数还多的资源
4. 进程等待资源的时间是有限的
5. 如果系统满足了进程对资源的最大需求,那么,进程应该在有限的时间内使用资源,然后归还给系统

执行过程: 

当进程Pi提出资源申请时,系统执行下列步骤:
(1)若Request[i](现在申请的资源数) ≤ Need[i](进程现在需要的资源总数),转(2);否则,报错返回;

(2)若Request[i] ≤ Available(系统空闲资源数),转(3);否则,进程等待;

(3)假设系统分配了资源,则有:---------------新状态
  Available = Available - Request[i];
  Allocation[i] = Allocation[i] + Request[i];
  Need[i] = Need[i] - Request[i];

若系统新状态安全的,则分配完成
若系统新状态是不安全的,则恢复原来状态,进程等待

安全性的检查:

安全性检查的步骤:
(1) Work = Available;
   Finish = false;

(2) 寻找满足条件的i:

   a. Finish[i]==false;
   b. Need[i]≤Work;
  如果不存在,则转(4)

(3) Work = Work + Allocation[i];//假设资源归还给系统

      Finish[i] = true;转(2)

(4) 若对所有i,Finish[i]==true,则系统处于安全状态,否则系统处于不安全状态

 

死锁检测和解除:

死锁检测:
1.允许死锁发生,但是操作系统会不断监视系统进展情况,判断死锁是否真的发生
2.一旦死锁发生则采取专门的措施,解除死锁并以最小的代价恢复操作系统运行

检测时机:
1.当进程由于资源请求不满足而等待时检测死锁
  缺点:系统开销大
2.定时检测
3.系统资源利用率下降时检测死锁

 

简单检测方法:

1.每个进程、每个资源指定唯一编号

2.设置一张资源分配表记录各进程与其占用资源之间的关系

3.设置一张进程等待表记录各进程与要申请资源之间的关系

最终化成各个进程关系图:

PS.最终P1等待P1自己,最终产生死锁

死锁解除:

方法如下:
1.撤消所有死锁进程
2.进程回退(Roll back)(系统会保存进程执行过程数据)再启动
3.按照某种原则逐一撤消死锁进程
4.按照某种原则逐一抢占资源(资源被抢占的进程必须回退到之前的对应状态)

 

哲学家就餐问题:

 

5个哲学家平时就思考,饥饿时便拿起左右两边叉子(叉子的互斥使用、不能出现死锁现象)吃饭。

实际对应的模型:应用程序中并发线程执行时,协调处理共享资源

 

解决方案1:

semaphore fork [5] = {1};
int i;
void philosopher (int i)
{
    while (true) {
        think();
        P (fork[i]);
        P (fork [(i+1) mod 5]);
       eat();
        V (fork [(i+1) mod 5]);
        V (fork[i]);
    }
}
void main()
{
   parbegin (philosopher (0), philosopher (1),philosopher (2), philosopher (3),philosopher (4));
}

但是该方案会出现问题,一旦进程在第一次P(fork[i])时就被切换下CPU,那么最终将导致哲学家都拿左边或者右边叉子,最终产生死锁。

 

解决措施:

1.最多允许4个哲学家同时坐在桌子周围
2.仅当一个哲学家左右两边的筷子都可用时,才允许他拿筷子
3.给所有哲学家编号,奇数号的哲学家必须首先拿左边的筷子,偶数号的哲学家则反之等等方案。

对于第一种措施的方案:

semaphore fork [5] = {1};
semaphore room = {4};
int i;
void philosopher (int i)
{
    while (true) {
        think();
        P (room);                             //用room限定住人数最多4人,这样当进程在此被切换时,尽管之前进程都拿左边或者右边,但是最终还是有进程会拿到量边叉子,之后释放,避免产生死锁
        P (fork[i]);
        P (fork [(i+1) mod 5]);
        eat();
        V (fork [(i+1) mod 5]);
        V (fork[i]);
        V(room);
    }
}
void main()
{
   parbegin (philosopher (0), philosopher (1),philosopher (2), philosopher (3),philosopher (4));
} 

(4人)

 

对于措施二解决方案:

用管程方法实现:

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 */
    }

用PV操作实现:

void philosopher (int i)
{ 
  while (true)   {     思考;     P(&mutex);     state[i] = HUNGRY;     test(i);     V(&mutex);     P(&s[i]);//若test中哲学家i左右有人在吃饭,那么s[i]加不了1,哲学家拿不了左右叉子,所以进程阻塞     拿左叉子;     拿右叉子;     进食;     放左叉子;     放右叉子;     P(&mutex)     state[ i ] = THINKING;     test([i-1] % 5);     test([i+1] % 5);     V(&mutex);   }  } state[ i ] = THINKING; s[ i ] = 0;
void test(int i)
{
    if (state[ i ] == HUNGRY)
    && (state [(i-1) % 5] != EATING)
    && (state [(i+1) % 5] != EATING)//只有当左右两哲学家没进餐时,拿起左右叉子
    {
        state[ i ] = EATING;//并把哲学家状态记成吃饭态
        V(&s[ i ]);//s[i]加1,防止之后的P(&s[i])让进程阻塞
    }
}    

哲学家就餐问题讨论:

何时发生死锁?

防止哲学家都拿一边叉子
怎样从死锁中恢复?

让其中一哲学家放下一只叉子

怎样避免死锁的发生?

使用上面提到的措施

 

  作者水平有限,文章肯定有错还请各位指点!!!感谢!!!

OS原理课到此结束!                   ✪ω✪!

 

posted on 2017-07-29 22:56  chaunceyctx  阅读(513)  评论(0编辑  收藏  举报

导航