死锁与饥饿


死锁类型:

1.竞争资源

2.进程通信

3.其他原因

死锁的条件:

资源独占(mutual exclusion):一个资源在同一个时刻只能被分配给一个进程。
不可剥夺(non preemption):资源申请者不能强行地从资源占有者手中夺取资源,即资源只能由其占有者在使用完后自愿地释放。
保持申请(hold-while-applying):进程在占有部分资源后还可以申请新的资源,而且在申请资源的时候并不释放它已经占有的资源。

循环等待(circular wait):存在一个进程等待序列{P1,P2,…Pn},其中,P1等待P2占有的某一资源,P2等待P3所占有的某一资源,…,Pn等待P1所占有的某一资源。

死锁的处理:

死锁预防(deadlock prevention):静态,是指对于进程有关资源的活动按某种协议加以限制,如果所有进程都遵守此协议,即可保证不发生死锁。
死锁避免(deadlock avoidance):动态,是指对于进程有关资源的申请命令加以实时检测,拒绝不安全的资源请求命令,以保证死锁不会发生。
死锁检测(deadlock detection)

死锁恢复(deadlock recovery)

资源分配图:

定义: 一个系统资源分配图是一个二元组G=(V,E), 其中V是节点集,E是边集。V=PR, P={p1,p2,…,pn}, R={r1,r2,…,rm},
          E={(pi,rj)}{(rj,pi)}, piP, rjR.
          申请边(pi,rj): pi申请rj;

          分配边(rj,pi): rj分配pi;

资源分配图约减:

1.寻找一个非孤立且没有请求边的节点pi, 若无算法结束
2.去除pi的所有分配边使其成为一个孤立节点;
3.寻找所有请求边都可满足的进程pj, 将pj的所有请求边全部改为分配边;

4.转步骤1

算法结束时,所有节点并非孤立节点,该资源分配图为不可约简图。==》故存在死锁。

死鎖预防方法:

预先分配法

进程:运行前申请所需全部资源;
系统:
   能够满足,全部分配,
   否则,一个也不分配。
由于进程在投入运行前已经占有所需要的全部资源,因而在运行期间不会再发出新的资源申请命令

有序分配法:通過破壞循環等待條件

资源集:R={r1,r2,…,rn},事先将所有资源类排序
   函数:F:RN  每个资源类赋予唯一的整数
   规定进程必须按照资源编号从小到大的次序申请资进程pi可以申请资源rj中的实例rl,pi当前已占有rl, F(rl)F(rj)
证明无死锁(deadlock free):
方法:反证,假定死锁
   时刻t1: p1无限等待rk1中的资源实例,被p2占有;
        t2: p2无限等待rk2中的资源实例,被p3占有;
                   …
        tn: pn无限等待rkn中的资源实例,被p1占有;
        根据有序申请假设:
               F(rk1)<F(rk2)<…<F(rkn)<F(rk1)
矛盾。

安全状态

  系统安全指存在一个执行序列,所有进程都能完成,这个序列被称为安全序列。下面来举一个例子:

系统有三个进程P1、P2和P3,共有12台打印机。
进程P1总共要求10台打印机,P2和P3分别要求4台和9台
假定T0时刻,进程P1、P2和P3已分别获得5台、2台和2台,尚有3台空闲未分,如下表:

可以看出,存在一个安全序列P2、P1、P3,所以说T0时刻是系统安全的。

  安全状态不是死锁状态。相反,死锁状态是不安全状态。然而,不是所有不安全状态都能导致死锁状态。不安全状态可能导致死锁。系统可以从安全状态转换为不安全状态。

  有了安全状态的概念,可定义避免算法以确保系统不会死锁。其思想是简单地确保系统始终处于安全状态。开始,系统处于安全状态。当进程申请一个可用的资源时,系统必须确定这一资源申请是可以立即分配还是等待。只有分配后使系统仍处于安全状态,才允许申请。

  采用这种方案,如果进程申请一个现已可用的资源,那么它可能必须等待。因此,与没有采用死锁避免算法相比,这种情况下资源使用率可能更低。

·资源分配图算法(适用于每种资源类型只有一个实例)

  该算法是在资源分配图的基础上,引入一新类型的边,称为需求边。需求边Pi->Rj表示进程Pi可能在将来某个时候申请资源Rj。这种边类似于同一方向的申请边,但用虚线表示。当申请资源时,需求边变为申请边;释放资源时,申请边变为需求边。

  假设进程Pi申请资源Rj。只有在将申请边Pi->Rj变为分配边Rj->Pi而不会导致资源分配图形成环时,才允许申请。

 

·银行家算法(适用于每种资源类型有多个实例

为了实现银行家算法,必须要有几个数据结构:

注:设n为系统进程的个数,m为在资源类型的种类。

Available长度为m的向量。表示每种资源的现有实例的数量。如果Available[j]=k,那么资源Rj有k个实例有效。

Maxn * m矩阵。定义每种进程的最大需求。如果Max[i][j]=k,那么进程Pi可以最多请求资源Rj的k个实例。

Allocationn * m矩阵。定义每个进程现在所分配的各种资源类型的实例数量。如果Allocation[I,j]=k,那么进程Pj当前分配了k个资源Rj的实例。

Needn * m矩阵。表示每个进程还需要的剩余的资源。如果Need[i][j]=k,那么进程Pj还需要资源Rj的k个实例。
注:Need[i][j] = Max[i][j] – Allocation [i][j]

为了描述方便,我们采用一些简化的表示方法:

  设X和Y为长度为n的向量,则X <= Y当且仅当对所有i = 1,2,...,n,X[i] <= Y[i]。例如,如果X = (1,7,2,3)而Y = (0,3,2,1),那么Y <= X。如果Y <= X且Y != X,那么Y < X。

  可以将AllocationNeed的每行作为向量,并分别用Allocation i和Need i来表示,向量Allocation i表示分配给进程Pi的资源;向量Need i表示进程为完成其任务可能仍然需要申请的额外资源。

银行家算法可整体分成两个部分:

1.安全性算法

确认计算机系统是否处于安全状态的算法分为如下几步:

(1)设WorkFinish分别为长度为mn的向量。按如下方式进行初始化,Work Avaliable且对于i = 0,1,...,n - 1,Finish[i] =    false

(2)查找这样的i使其满足

  ·Finish[i] = false

  ·Need i <= Work

  如果没有这样的i,那么就转到第(4)步。

(3)Work = Work + Allocation i

   Finish[i] = true

   返回第(2)步

(4)如果对所有i,Finish[i] = true,那么系统则处于安全状态。

该算法可能需要m * n^2数量级的操作以确定系统是否处于安全状态。

2.资源请求算法

现在,描述如何判断是否可安全允许请求的算法。

  设Request i为进程Pi的请求向量。如果Request i[j] == k,那么进程Pi需要资源类型Rj的实例数量为k。当进程Pi作出资源请求时,采取如下动作:

(1)如果Request i <= Need i,那么转到第(2)步。否则,产生出错条件,这是因为进程Pi已超过了其最大请求。

(2)如果Request i <= Available,那么转到第(3)步。否则,Pi必须等待,这是因为没有可用资源。

(3)假定系统可以分配给进程Pi所请求的资源,并按如下方式修改状态:

   Available = Available - Request i;

   Allocation i = Allocation i + Request i;

   Need i = Need i - Request i;

  如果所产生的资源分配状态是安全的(通过上面的安全性算法),那么Pi可分配到它所请求的资源。但是,如果新状态不安全,则进程Pi必须等待Request i并恢复到原来的资源分配状态。

 

上面都是一些表达式,可能有些枯燥,现在我们来练习一道例题:

  考虑这样一个系统,有5个进程P0~P4,3种资源类型A、B、C。资源类型A有10个实例,资源类型B有5个实例,资源类型C有7个实例。假定在时刻T0,系统状态如下:

         Allocation      Max     Avaliable
          A  B  C      A  B  C    A  B  C
P0       0  1  0      7  5  3    3  3  2
P1       2  0  0      3  2  2  
P2       3  0  2      9  0  2
P3       2  1  1      2  2  2
P4       0  0  2      4  3  3

  矩阵Need的内容定义成Max-Allocation

           Need
          A  B  C
P0       7  4  3
P1       1  2  2
P2       6  0  0
P3       0  1  1
P4       4  3  1

  可认为系统现在处于安全状态,因为存在一个安全序列<P1,P3,P4,P2,P0>。

  现在假定进程P1再请求1个A类资源和两个C类资源,这样Request1 = (1,0,2)。为了确定这个请求是否可以立即允许,首先检测Request1 <= Available(即,(1,0,2) <= (3,3,2)),其值为真。接着假定这个请求被满足,会产生如下新状态:

         Allocation     Need     Avaliable
          A  B  C      A  B  C    A  B  C
P0       0  1  0      7  4  3    2  3  0
P1       3  0  2      0  2  0  
P2       3  0  2      6  0  0
P3       2  1  1      0  1  1
P4       0  0  2      4  3  1

  必须确定这个状态是否安全。为此,执行安全算法,并找到顺序<P1,P3,P4,P0,P2>满足安全要求。因此,可以立即允许进程P1的这个请求。

  然而,可以发现当系统处于这一状态时,是不能允许P4的请求(3,3,0)的,因为没有那么多资源可用。也不能允许P0的请求(0,2,0);虽然有资源可用,但是这会导致系统处于不安全状态

死锁检测时刻:

1.进程等待时

2.定时检测

3.资源利用率降低时检测

死锁的恢复

1. 重新启动:是最简单、最常用的死锁消除方法,但代价很大,因为在此之前所有进程已经完成的计算工作都将付之东流,不仅包括死锁的全部进程,也包括未参与死锁的全部进程。
2. 终止进程(process termination):终止参与死锁的进程并回收它们所占资源。
    (1) 一次性全部终止;(2) 逐步终止(优先级,代价函数)
3. 剥夺资源(resource preemption):剥夺死锁进程所占有的全部或者部分资源。
    (1) 逐步剥夺:一次剥夺死锁进程所占有的一个或一组资源,如果死锁尚未解除再继续剥夺,直至死锁解除为止。
    (2) 一次剥夺:一次性地剥夺死锁进程所占有的全部资源。
4. 进程回退(rollback):让参与死锁的进程回退到以前没有发生死锁的某个点处,并由此点开始继续执行,希望进程交叉执行时不再发生死锁。但是系统开销很大:
     (1) 要实现“回退”,必须“记住”以前某一点处的现场,而现场随着进程推进而动态变化,需要花费大量时间和空间。
    (2) 一个回退的进程应当“挽回”它在回退点之间所造成的影响,如修改某一文件,给其它进程发送消息等,这些在实现时是难以做到的。

饥饿:

饥饿(starvation):当 等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿.饥饿到一定程度的进程所赋予的使命即使完成也不再具有实际意义时称该进程被饿死(starved to death).
没有时间上界的等待
排队等待
忙式等待

忙式等待条件下发生的饥饿,称为活锁(live lock).

饥饿 vs 死锁

死锁进程处于等待状态,忙式等待的进程并非处于等待状态, 但却可能被饿死;
死锁进程等待永远不会释放的资源, 饿死进程等待可能被释放,但却不会分给自己的资源,其等待时间没有上界;
死锁一定发生了循环等待,饿死不然;
死锁至少涉及两个进程, 饿死进程可能只有一个.

简单组合资源死锁的静态分析

可复用资源(reusable resource)

一次只能分配给一个进程使用的资源
组合资源
由相对独立的若干资源构成的资源集合,其中每个相对独立的资源称为子资源
同种组合资源

由相同类型的子资源构成的组合资源


同一进程间不连线




同种组合资源死锁:


猜你喜欢

转载自blog.csdn.net/liudongdong19/article/details/80294055
今日推荐