死锁

死锁

在多道程序环境下,多个进程可能竞争一定数量的资源。某个进程申请资源,如果这时资源不可用,那么该进程进入等待状态。如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变其状态。这种情况称为死锁(deadlock)。

系统模型

    正常操作模式下,进程只能按如下顺序使用资源:

    申请:如果申请不能立即被允许(例如,所申请资源正在为其他进程所使用),那么申请进程必须等待,直到它获得该资源为止。

    使用:进程对资源进行操作(例如,如果资源是打印机,那么进程就可以在打印机上打印了)。

    释放:进程释放资源。

    如前面讲的资源的申请与释放为系统调用,例如系统调用:request()/release()(设备)、open()/close()(文件)、allocate()/free()(内存)。其他资源的申请与释放可以通过信号量的wait与signal操作或通过互斥锁的获取与释放来完成。因此,对于进程或线程的每次使用,操作系统会检查以确保使用进程已经申请并获得了资源。系统表记录了每个资源是否空闲或已被分配,分配给哪个进程。如果进程所申请的资源正在为其他进程所使用,那么该进程会增加到该资源的等待队列。

    当一组进程的每个进程都在等待一个事件,而这一事件只能由这一组进程的另一进程引起,那么这组进程就处于死锁状态。这里所关心的主要事件是资源获取和释放。资源可能是物理资源(例如,打印机、磁带驱动器、内存空间和CPU周期)或逻辑资源(例如,文件、信号量和管程)。

    开发多线程应用程序的程序员必须特别关注这个问题,因为多个线程可能因为竞争共享资源而容易产生死锁。

死锁特征

必要条件

如果在一个系统中下面4个条件同时满足,那么会引起死锁。

互斥:至少有一个资源处于非共享模式,即一次只有一个进程使用。如果另一进程申请该资源,那么申请进程必须等到该资源被释放为止。

占有并等待:一个进程必须占有至少一个资源,并等待另一资源,而该资源为其他进程所占有。

非抢占:资源不能被抢占,即资源只能在进程完成任务后自动释放。

循环等待:有一组等待进程{p0,p1,p2,….pn},p0等待的资源为p1所占有,p1等待的资源为p2所占有,…..,pn-1等待的资源为pn所占有,pn等待的资源为p0所占有。

    在此,强调所有4个条件必须同时满足才会出现死锁。

资源分配图

    死锁问题可用称为系统资源分配图的有向图进行更为精确地描述。这种图由一个节点集合V和一个边集合E组成。节点集合V可分成两种类型的节点:P={p1,p2,…..,pn}(系统活动进程的集合)和R={R1,R2,….,Rn}(系统所有资源类型的集合)

    由进程pi到资源Rj的有向边记为Pi->Rj,它表示进程pi已经申请到资源类型Rj的一个实例,并在等待该资源。由资源类型Rj到进程Pi的有向边记为Rj->pi,它表示资源类型Rj一个实例已经分配到进程Pi。有向边Pi->Rj称为申请边,有向边Rj->Pi称为分配边。

    根据资源分配图的定义,可以证明:如果分配图没有环,那么系统就没有进程死锁如果分配图有环,那么可能存在死锁。

    如果每个资源类型刚好有一个实例,那么有环就意味着已经出现死锁。如果涉及一组资源类型,而每个类型只有一个实例,那么就出现死锁。环所涉及的进程就死锁。在这种情况下,图中的环就是死锁存在的充分必要条件。

    如果每个资源类型有多个实例,那么有环并不意味着已经出现了死锁。在这种情况下,图中的环就是死锁存在的必要条件而不是充分条件。

    

总而言之,如果资源分配图没有环,那么系统就不处于死锁状态。另一方面,如果有环,那么系统可能处于死锁状态。在处理死锁问题时,这点是很重要的。

死锁处理方法

从原理上来说,有三种方法可处理死锁问题::

  • 可使用协议以预防或避免死锁,确保系统不会进入死锁状态。
  • 可允许系统进入死锁状态,然后检测它,并加以恢复。
  • 可忽视这个问题,认为死锁不可能在系统内发生。

为了确保死锁不会发生,系统可以采用死锁预防或死锁避免方案。死锁预防(deadlock prevention)是一组方法,以确保至少一个必要条件不成立。这些方法通过限制如何申请资源的方法来预防死锁。

死锁避免(deadlock avoidance)要求操作系统事先得到有关进程申请资源和使用资源的额外信息。有了这些额外信息,系统可确定:对于一个申请,进程是否应等待。为了确定当前申请时允许还是延迟,系统必须考虑现有可用资源、已分配给每个进程的资源、每个进程将来申请和释放的资源。

    如果系统不使用死锁预或死锁避免算法,那么可能发生死锁情况。在这种情况下系统可提供一个算法来检查系统状态以确定死锁是否发生,并提供另一个算法来从死锁中恢复(如果死锁确实已发生)。

死锁预防

出现死锁有4个必要条件。只要确保至少一个必要条件不成立,就能预防死锁发生。

互斥

对于非共享资源,必须要有互斥条件。通常不能通过否定互斥条件来预防死锁:有的资源本身就是非共享的。

占有并等待

为了确保占有并等待不会再系统内出现,必须保证:当一个进程申请一个资源时,它不能占有其他资源。一种可以使用的协议是每个进程在执行前申请并获得所有资源可以实现通过要求申请资源的系统调用在所有其他系统调用之前进行。

    另一种协议允许进程在没有资源时才可申请资源。一个进程可申请一些资源并使用它们。然而,在它申请更多其他资源之前,它必须释放其现已分配的所有资源。

    这两种协议有两个主要缺点。第一,资源利用率(resource utilization)可能比较低,因为许多资源可能已分配,但是很长时间没有被使用。第二,可能发生饥饿。一个进程如需要多个常用资源,可能会永久等待,因为其所需要的资源中至少有一个已分配给其他进程。

非抢占

    第三个必要条件是对已分配的资源不能抢占,为了确保这一条件不成立,可以使用如下协议:如果一个进程占有资源并申请另一个不能立即分配的资源,那么其现已分配的资源都可被抢占。换句话说,这些资源都被隐式地释放了。抢占资源分配到进程所等待的资源的链表上。只有当进程获得其原有资源和所申请的新资源时,进程才可以重新执行。

    换句话说,如果一个进程申请一些资源,那么首先检查它们是否可用。如果可用,那么就分配他们。如果不可用,那么检查这些资源是否已分配给其他等待额外资源的进程。如果是,那么就从等待进程中抢占这些资源,并分配给申请进程。如果资源不可用且也不被其他等待进程占有,那么申请进程必须等待,当一个进程处于等待时,如果其他进程申请其拥有资源,那么该进程的部分资源可以被抢占。一个进程要重新执行,它必须分配到其所申请的资源,并恢复其在等待时被抢占的资源。

    这个协议通常应用于状态可以保存和恢复的资源,如CPU寄存器和内存。它一般不适用于其他资源,如打印机和磁带驱动器。

循环等待

    死锁的第4个也是最后一个条件是循环等待。一个确保此条件不成立的方法是对所哟资源类型进行完全排序,且要求每个进程按递增顺序来申请资源。

    设R={R1,R2,R3,…..,Rm}为资源类型的集合。为每个资源类型分配一个唯一整数来允许比较两个资源以确定其先后顺序。可以定义一个函数F: R->N,其中N是自然数的集合。例如,如果资源类型R的集合包括磁带驱动器、磁盘驱动器和打印机,那么函数F可以定义如下:

F(tape drive)=1

F(disk drive)=5

F(printer)=12

可以采用如下协议以预防死锁: 每个进程只按递增顺序申请资源,即一个进程开始可以申请任何数量的资源Ri的实例。之后,当且仅当F(Rj)>F(Ri)时,该进程可以申请资源类型Rj的实例。如果需要同一资源类型的多个实例,那么对他们必须一起申请。例如,对于以上给定函数,一个进程需要同时使用磁带驱动器和打印机,那么就必须先申请磁带驱动器,再申请打印机。换句话说,要求当一个进程申请资源类型Rj时,它必须先释放所有资源Ri(F(Ri)>=F(Rj))。如果使用这两个协议,那么循环等待就不可能成立。

    设计一个完全排序或层次并不能防止死锁,而是要靠应用程序员来按顺序编写程序。另外,函数F应该根据系统内资源使用的正常顺序来定义。

死锁避免

    在上一节中,通过限制资源申请的方法来预防死锁。这种限制确保4个必要条件之一不会发生,因此死锁不成立。然而,通过这种方法预防死锁的副作用是低设备使用率和系统吞吐率。

    避免死锁的另一个方法是,获得以后如何申请资源的附加信息。有了关于每个进程的申请与释放的完全顺序,可决定进程是否因申请而等待。每次申请要求系统考虑现有可用资源,现已分配给每个进程的资源和每个进程将来申请与释放的资源,以决定当前申请是否满足或必须等待,从而避免死锁发生的可能性。

    根据这种方法,有不同的算法,它们在所要求的信息量和信息的类型上有所不同。最为简单和最为有用的模型要求每个进程说明可能需要的每种资源类型实例的最大需求。根据每个进程可能申请的每种资源类型实例的最大需求的事先信息,可以构造一个算法以确保系统绝不会进入死锁状态。这种算法定义为死锁避免(deadlock-avoidance)方法。死锁避免算法动态地检测资源分配状态以确保循环等待条件不可能成立。资源分配状态是由可用资源已分配资源,以及进程最大需求所决定的。

安全状态

    如果系统能按某个顺序为每个进程分配资源(不超过其最大值)并能避免死锁,那么系统状态就是安全的。更为准确地说,如果存在一个安全序列,那么系统处于安全状态。进程顺序<P1,P2,…,Pn>,如果对于每个Pi,Pi仍然可以申请的资源数小于当前可用资源加上所有进程Pj(其中j<i)所占有的资源,那么这一顺序称为安全序列。在这种情况下,进程Pi所需的资源即使不能立即可用,那么Pi可等待直到所有Pj释放其资源。当它们完成时,Pi可得到其所需要的所有资源,完成其给定任务,返回其所分配的资源并终止。当Pi终止时,Pi+1可得到其所需要的资源,如此进行。如果没有这样的顺序存在,那么系统状态就处于不安全状态。

    安全状态不是死锁状态。相反,死锁状态是不安全状态。然而,不是所有不安全状态都能导致死锁状态。不安全状态可能导致死锁。只要状态为安全,操作系统就能避免不安全(和死锁)状态。在不安全状态下,操作系统不能阻止进程以会导致死锁的方式申请资源。进程行为控制了不安全状态。

    避免算法其思想是简单地确保系统始终处于安全状态。开始,系统处于安全状态。当进程申请一个可用的资源时,系统必须确定这一资源申请时可以立即分配还是等待。只有分配后使系统仍处于安全状态,才允许申请。采用这种算法,如果进程申请一个现已可用的资源,那么它可能必须等待。因此,与没有采用死锁避免算法相比,这种情况下的资源使用率可能更低。

资源分配图算法

    如果一个资源分配系统,每种资源类型只有一个实例。除了申请边和分配边外,引入了一新类型的边,称为需求边。需求边Pi--->Rj表示进程Pi可能在将来某个时候申请资源Rj。这种边类似于同一方向的申请边,但是用虚线表示。当进程Pi申请资源Rj时,需求边Pi->Rj

变成了申请边。

    假设进程Pi申请资源Rj。只有在将申请边变成分配边而不会导致资源分配图形成环时,才允许申请。注意,通过采用环检测算法,检测安全性。检测图中是否有环的算法需要n^2级的操作,其中n是系统的进程数量。

    如果没有环存在,那么资源分配会使系统处于安全状态。如果有环存在,那么分配会导致系统处于不安全状态。因此,进程Pi必须等待其资源申请被满足。

银行家算法

    对于每种资源类型有多个实例的资源分配系统,资源分配图算法就不适用了。下面所要描述的死锁避免算法适用于这种系统,但是其效率要比资源分配图方案低。这一算法通常称为银行家算法。该算法如此命名是因为它可用于银行系统,当它不能满足所有客户的需要时,银行绝不会分配其现金。

    当新进程进入系统时,它必须说明其可能需要的每种类型资源实例的最大数量,这一数量不能超过系统资源的总和。当用户申请一组资源时,系统必须确定这些资源的分配是否仍会使系统处于安全状态。如果是,就可分配资源;否则,进程必须等待知道某个其他进程释放足够资源为止。

    为了实现银行家算法,必须有几个数据结构。这些数据结构对资源分配系统的状态进行了记录。设n为系统进程的个数,m为资源类型的种类,需要如下数据结构:

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

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

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

Need:n*m矩阵,表示每个进程还需要的剩余的资源。如果Need[i][j]=k,那么进程Pi还可能申请k个资源类型Rj的实例。注意,Need[i][j]=Max[i][j]-Allocation[i][j]。

    这些数据结构的大小和值会随着时间而改变。

    为了简化银行家算法的描述,可以采用一些符号。设X和Y为长度为n的向量,则X<=Y当且仅当对所有i=1,2,….,n, X[i]<=Y[i]。可以将矩阵Allocation和Need的每行作为向量,并分别用Allocation[i]和Need[i]来表示。

安全性算法

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

注:这个算法可能需要m*n^2数量级的操作以确定系统状态是否安全。这个算法是在某一个时刻用来检测系统是否处于安全状态的,即Available开始的时候是已经分配后还剩下可以分配的数量。

资源请求算法

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

死锁检测

    如果一个系统既不采用死锁预防算法也不采用死锁避免算法,那么可能会出现死锁。在这种环境下,系统应提供:

  • 一个用来检查系统状态从而确定是否出现了死锁的算法。
  • 一个用来从死锁状态中恢复的算法。

每种资源类型只有单个实例

如果所有资源类型只有单个实例,那么可以定义这样一个死锁检测算法,该算法使用了资源分配图的一个变种,称为等待(wait-for)图。删除所有资源类型节点,合并适当边,就可以得到等待图。

    与以前一样,当且仅当等待图中有一个环,系统中存在死锁。为了检测死锁,系统需要维护等待图,并周期性地调用在图中进行搜索的算法。从图中检测环的算法需要n^2级别操作,其中n为图中的节点数。

每种资源类型可有多个实例

关于第二步,是处于乐观的认为,如果假定不正确,那么稍后会发生死锁。下次调用死锁算法时,就检测到死锁状态。

死锁恢复

    当死锁检测算法确定死锁已存在,那么可以采取多种措施。一种措施是通知操作员死锁已发生,以便操作人员人工处理死锁。另一种措施是让系统从死锁状态中恢复过来。打破死锁有两个方法。一个方法是简单地终止一个或多个进程以打破循环等待。另一个方法是从一个或多个死锁进程那里抢占一个或多个资源。

进程终止

    有两种方法通过终止进程以取消死锁。不管采用哪种方法,系统都会收回分配给终止进程的所有资源。

    终止所有死锁进程:这种方法显然终止了死锁循环,但其代价也大。这些进程可能已计算了很长时间,这些部分计算结果必须放弃,以后可能还要重新计算。

    一次只终止一个进程直到取消死锁循环为止:这种方法的开销会相当大,这是因为每次终止一个进程,都必须调用死锁检测算法以确定进程是否处于死锁。

    

资源抢占

    通过抢占资源以取消死锁,逐步从进程中抢占资源给其他进程使用,直到死锁环被打破为止。

    如果要求使用抢占来处理死锁,那么有三个问题需要处理:

    选择一个牺牲品、回滚、饥饿

小结

如果两个或更多的进程永久等待某个事件,而该事件只能由这些等待进程的某一个引起,那么会出现死锁状态。从原理上来说,有三种方法可能处理死锁:

  • 使用一些协议来预防或避免死锁,确保系统永远都不会进入死锁状态。
  • 允许系统进入死锁状态,检测死锁,并恢复。
  • 忽略这个问题,并假设系统中永远都不会出现死锁。

当且仅当系统内的4个必要条件同时成立时(互斥、占有等待、非抢占、循环等待),才会发生死锁。为了预防死锁,要确保这4个必要条件中的至少一个不成立。

死锁避免算法要比预防算法要求低,只要事先了解进程使用资源的情况即可。例如,应银行家算法需要知道每个进程所请求的每种资源的最大数量。。采用这种信息,可以采用死锁避免算法。

如果不采用协议以确保死锁不会发生,那么就必须使用检测并恢复方案。必须调用检测算法以确定是否出现死锁。如果检测到死锁,那么系统必须通过终止某些死锁进程或通过抢占某些死锁进程的资源从死锁中恢复。

如通过抢占来处理死锁,那么有三点必须要考虑:选择一个牺牲品、回滚以及饥饿。如果系统主要根据代价以及选择牺牲进程来进行回滚,那么可能会出现饥饿线性,导致所选择进程永远不能完成指定任务。

最后,在这些方法有没有一个可独自处理操作系统的所有资源分配问题上,研究人员有些争议。不过,通过将这些方法组合起来,可以最佳地处理系统的各种类型资源的分配问题。

posted on 2018-11-10 13:06  kexinxin  阅读(207)  评论(0编辑  收藏  举报

导航