0%

第6章:死锁

如果有两个进程A和B需要将扫描的文档记录到CD上,进程A请求扫描仪并被授权使用,请求CD时发现已被B占用了,于是被拒绝,同理,B请求扫描仪也会被拒绝,于是产生死锁。在一个数据库系统中,为了避免竞争,可对若干记录加锁,如果进程A对R1加锁了,进程B对R2加了锁,接着这两个进程又试图把各自对方的记录也加锁,这是也会产生死锁。所以,软硬件资源都可能出现死锁

资源和死锁条件

资源分为两类,一类是可抢占资源(preemptable resource) 可以从拥有它的进程中枪战而不产生任何副作用,比如存储器。一个系统拥有256M的用户内存和一条打印机,如果有两个256M内存的进程都想打印,进程A获得了打印机,而B战友内存,但是幸运的是可以通过把进程B患处内存、把进程A换入内存可以实现抢占B的内存,这样,进程A继续运行并执行打印任务,然后释放打印机和内存。另一类是不可抢占资源(nonpreemptable resource)是指在不引起相关的计算失败的情况下,无法把它从占有它的进程中抢占过来,如CD刻录机。如果一个进程已经在开始刻盘,突然将刻录机分配给另一个进程,那么将划坏CD盘。

因此,总的来说,死锁和不可抢占资源有关,有关可抢占资源的潜在死锁通常可以通过在进程之间重新分配资源而化解,所以我们主要关注不可抢占资源上。当然,Coffman等人总结了发生(资源)死锁需要具备四个必要条件:

  • 不可抢占

    已经分配给一个进程的资源不能强制性地被抢占,他只能被占有它的进程显式地释放。

  • 互斥条件。

    每个资源要么已经分配了一个进程,要么就是可用的。

  • 占有和等待

    已经得到了某个资源的进程还可以再请求新的资源。

  • 环路等待

    死锁发生时,系统中一定有由两个或者以上的进程组成一条环路,该环路中每个进程都在等待着下一个进程所占有的资源。

注意:死锁发生时,以上四个条件一定是同时满足的。任何一个不成立就不会发生死锁

死锁建模

在讨论死锁解决方案之前,讨论如何对死锁建模是有意义的。有个叫Holt的人指出可以利用有向图建立死锁四个条件的模型——在有向图中有两类节点:用圆形表示进程,用方形表示资源。从资源节点到进程节点的有向边代表该资源已被请求、授权并被进程占用,由进程节点到资源节点的有向边表明当前进程正在请求该资源。以下是一个示意图:

死锁建模示意图

图中,当前资源R整备进程A占用,进程B正等待着资源S,图c)进入了死锁状态,进程C等待着资源T,资源T被进程D占用,进程D又等待着由进程C占用的资源U。

处理方法

总而言之,有四种处理死锁的策略:

  • 忽略该问题。也许如果你忽略它,他也会忽略你。

  • 检测死锁并恢复。

    让死锁发生,检测它们是否发生,一旦发生死锁,采取行动解决问题。

  • 仔细对资源进行分配,动态地避免死锁。

  • 破坏引起死锁的四个必要条件之一,防止死锁产生。

以下对这四种策略分别阐述。

忽略问题

最简单的方法是鸵鸟算法:把头埋进沙子里,假装根本没有问题发生。不同人对于该方法的看法不同,数学家认为这种方法不能接受,不管代价多大,都要彻底防止死锁产生;而对于工程师,它们会考量死锁发生的频率和严重性,如果平均5年一次死锁,那么大多数工程师不会以性能损失和可用性代价去防止死锁。

检测死锁和死锁恢复

每种类型一个资源的死锁检测

从最简单的例子开始,假定每种类型的资源只有一个,即排除了同时有两台打印机的情况。我们假设一个系统包括A到G共7个进程,R到W共6中资源,资源的占有情况和进程对资源的请求情况如下:
(1)A进程持有R资源,且需要S资源。
(2)B进程不持有任何资源,但需要T资源。
(3)C进程不吃油任何资源,但需要S资源。
(4)D进程持有U资源,且需要S资源和T资源。
(5)E进程持有T资源,且需要V资源。
(6)F进程持有W资源,且需要S资源。
(7)G进程持有V资源,且需要U资源。

问:系统是否存在死锁?如果存在,涉及哪些进程?

回答这一问题,初看很难,但是建模构造一张资源分配图之后,可以直观地看到图中包含了一个环,如下图所示:

对问题建模

在换种,可以看出进程D、E、G已经死锁,A、C、F没有死锁,因为可以把资源S分配给它们中的任意一个。

每种类型有多个资源的死锁检测

如果一类资源可能存在多个,就需要采用另一个方法来检测死锁。现在我们提供一种基于矩阵的算法来检测从P1到Pn这n个进程中的死锁。假设资源类型数为m,E1代表资源类型1,E2代表资源类型2,以此类推。E是现有资源向量,代表每种已存在的资源总数,比如资源类型1代表磁带机,那么E1=2就表示系统有两台磁带机。

在任意时刻,某些资源已被分配所以不可用,假设A是可用资源向量,那么Ai表示当前可供使用的资源数(即没有被分配的资源)。如果仅有的两台磁带机都已经分配出去了,那么A1的值为0 。

现在我们需要两个数组:C代表当前当前分配矩阵,R代表请求矩阵。C的第i行代表Pi当前所持有的每一种类型资源的资源数,所以Cij代表进程i所持有的资源j的数量,同理Rij代表Pi所需要的资源j的数量,数据结构如下图:

矩阵数据结构

这四种数据结构之间有一个重要的恒等式,具体地说,某种资源要么已分配,要么可用,这个结论意味着:

资源等式

换言之,如果我们将所有已分配的资源j的数量加起来在和所有可供使用的资源数相加,结果就是该类资源的资源总数。死锁检测算法就是给予向量的比较,我们定义向量A和向量B之间的关系为A小于或等于B以表明A的每一个分量要么等于要么小于和B向量对应的分量。

每个进程起初是没有被标记的,算法开始会对进程做标记,进程被标记后就表明它们能够被执行,不会进入死锁,死锁检测算法如下:

  1. 寻找一个没有标记的进程Pi,对于它而言,R矩阵的第i行向量小于或等于A。
  2. 如果找到了这样一个进程,那么将C矩阵的第i行向量加到A中,标记该进程,并转到第1步。
  3. 如果没有这样的进程,算法终止。

算法的第1步是寻找可以运行完毕的进程,该进程有资源请求并且该请求可被当前的可用资源满足。这一选中的进程随后就被运行完毕,在这段时间内它释放自己持有的所有资源并将它们返回到可用资源库中,然后,这一进程被标记为完成,如果所有的进程最终都能运行完的话,就不存在死锁,如果进程一直不能被运行,那它们就是死锁进程。

从死锁恢复

我们讨论各种从死锁中恢复的方法,尽管这些方法看起来都不那么令人满意:

  • 利用抢占恢复。

    临时将资源从当前所有者哪里转移到另一个进程,许多情况下这是需要人工敢于的。比如,要将激光打印机从它持有的进程那里拿走,管理员可以收集已打印好的文档,然后该进程被挂起,接着打印机被分配给另一个进程。

  • 利用回滚恢复。

    周期性地将进程的状态写入一个文件以备重启。一旦检测到死锁,拥有所需要资源的进程会回滚到一个时间点,在此时间点之前该进程获得了一些其他资源,在该检查点之后所做的工作都丢失。实际上,是将该进程复位到一个更早的状态,那时候它还没取得所需的这个资源。接着就把这个资源分配给一个死锁进程。

  • 杀死进程恢复。

    杀死环中的一个进程。或者杀死环外带有该资源的一个进程。

动态避免死锁

利用资源轨迹图、安全状态和不安全状态、银行家算法去解决。这里略复杂,暂时先不深入研究。

破坏引起死锁的四个条件之一

  • 破坏互斥条件,如果资源不被一个进程独占,那么死锁肯定不会产生。

    当然,允许两个进程同时使用打印机会造成混乱,通过采用假脱机(spooling printer)技术可以允许若干个进程同时产生输出。该模型中唯一真正请求使用物理打印机的进程是打印机守护进程,由于守护进程绝不会请求其他资源,因此不会产生死锁。

  • 破坏占有和等待条件。只要禁止已持有资源的进程再等待其他资源便可以消除死锁。

    一种实现方法是规定所有进程在开始执行前请求所需的全部资源。如果所需的全部资源可用,那么就将它们分配给这个进程,于是该进程肯定能够运行结束。如果一个或者多个资源正被使用,那么就不分配,进程等待。另一种方案是,要求当一个进程请求资源时,先暂时释放其当前占用的所有资源,然后再尝试一次获得所需要的全部资源。

  • 破坏不可抢占条件。

    假如一个进程已分配到一台打印机且正在进行打印输出,如果由于它需要的绘图仪无法获得而强制性把打印机抢占掉,则会引起混乱。但是,一些资源可以通过虚拟化的形式来避免发生这样的情况,假脱机打印机向磁盘输出,并且只允许打印机守护进程访问真正的物理打印机,就可以消除涉及打印机的死锁。然而,不是所有资源都可以进行类似的虚拟化,比如数据库中的记录在操作的时候必须要锁定,因此存在死锁的可能

  • 破坏环路等待。

    消除环路有几种方法。比较靠谱的方案是,对所有的资源统一编号,现在的规则是,进程可以在任何时刻提出资源请求,但是所有请求必须按照资源编号的顺序(升序)提出,如下图所示,进程可以先请求打印机后请求磁带机,但不可以先请求绘图仪后请求打印机。

资源排序

最后,总结一张图用于预防死锁:

预防死锁的方法汇总

谢谢你的鼓励