计算机四级操作系统-8-死锁

8章死锁

在多道程序系统中,同时有多个进程并发运行,共享系统资源,从而提高了系统资源利用率,提高了系统的处理能力。但是,若对资源的管理、分配和使用不当,也会产生一种危险,即在一定条件下会导致系统发生一种随机性错误——死锁Dead lock)。这是因为进程在运行时要使用资源,在一个进程申请与释放资源的过程中,其他的进程也不断地申请资源与释放资源。由于资源总是有限的,因而异步前进的诸进程会因申请、释放与资源顺序安排不当,造成一种僵局。另外,在进程使用某种同步或通信工具时发送、接收次序安排不当,也会造成类似现象。

死锁问题的研究工作是从理论上和实践上处理操作系统问题的一个成功的、有代表性的例子。对死锁问题的研究涉及计算机科学中并行程序的终止性问题,死锁是计算机操作系统中的一个很重要的问题,有必要专门进行讨论。

8.1死锁基本概念

本节介绍死锁的基本概念,同时将它与活锁、饥饿两个概念进行了对比。本节还给出产生死锁的必要条件,并通过实例描述了死锁产生的场景。在本节中主要讨论死锁的产生与资源的特性、使用之间的关联。

8.1.1死锁的概念

死锁现象并不是计算机操作系统环境下所独有的,在日常生活乃至各个领域是屡见不鲜的。例如,一条河上有一座独木桥,过河的人总是沿着自己过河的方向前进而不后退,并且没有规定两岸的人必须谁先过河,则在此独木桥上就有可能发生死锁现象——如果有两个人同时从河的两岸过河的话。

在曰常生活中,十字路口交通堵塞可以看作是死锁的例子。假设,十字路口有向东、南、西、北四个方向行驶的车流,排头的四辆车几乎同时到达该十字路口,并且相互交叉停了下来;按照道路行驶的一般规则,停在十字路口的车应该给立即右拐的车让路,于是北行的车给西行的车让路,西行的车给南行的车让路……从而谁都无法前进,这就产生了死锁。

现有AB两个进程竞争R1R2两个资源,每个进程都要独占使用这两个资源一段时间。

进程A为:

{

获得R1

获得R2

释放R1

释放R2

}

进程B为:

{

获得R2

获得R1

释放R2

释放R1

}

8-1描述了这种情况,其中X轴表示A的执行进展,y轴表示B的执行进展,两个进程的共同进展由从原点开始向东北方向的路径表示。对单处理器系统,一次只有一个进程执行,其路径由交替的水平段和垂直段组成。水平段表示A执行而B等待,垂直段表示B执行而A等待。可以有6种不同的执行路径:

1B获得R2然后获得R1然后释放R2AR1A恢复执行时它可以获得全部资源。

2B获得R2然后获得R2A执行并阻塞在对R的请求上B释放R2R1A恢复执行时,它可以获得全部资源。

3B获得R2然后A获得R1由于在继续执行时B阻塞在R1上而A阻塞在R2上,死锁是不可避免的。

4A获得R1然后B获得R2由于在继续执行时B阻塞在R1上而A阻塞在R2上,死锁是不可避免的。

5A获得R1,然后获得R2;B执行并阻塞在对R2的请求上;A释放R1R2;B恢复执行时,它可以获得全部资源。

6A获得R1,然后获得R2然后释放R1R2B恢复执行时,它可以获得全部资源。

死锁的发生与否还取决于动态执行和应用程序的细节,上例中,假如A为:

进程A为:

{

获得R1

获得R2

释放R1

释放R2

}

A不是同时需要两个资源,此时不论两个进程如何执行,总不会发生死锁,如图8-2所示。

 

所谓死锁,是指在多道程序系统中,一组进程中的每一个进程均无限期地等待被该组进程中的另一个进程所占有且永远不会释放的资源,这种现象称系统处于死锁状态,简称死锁。处于死锁状态的进程称为死锁进程。

当死锁发生后,死锁进程将一直等待下去,除非有来自死锁进程之外的某种干预。系统发生死锁时,死锁进程的个数至少为两个;所有死锁进程都在等待资源,并且其中至少有两个进程已占有资源。系统发生死锁不仅浪费了大量的系统资源,甚至会导致整个系统崩溃,带来灾难性的后果。因为,系统中一旦有一组进程陷人死锁,那么要求使用被这些死锁进程所占用的资源或者需要它们进行某种合作的其他进程就会相继陷入死锁,最终可能导致整个系统处于瘫痪状态。

2.活锁与饥饿

如果进入临界区互斥的时间很短,而阻塞等待的时间开销很大,那么在某种情形下,可采用轮询(忙等待)原语进入临界区或存取资源。

假设有一对进程使用两种资源,每个进程需要两种资源,它们利用轮询原语enter_region去尝试取得必要的锁,如果尝试失败,则该进程继续尝试。如果进程A先运行并得到资源1,然后进程2运行且得到资源2以后不管哪一个进程运行,都不会有任何进展,但是哪一个进程也没有被阻塞。结果是两个进程总是一再消耗完分配给它们的时间片,但是没有进展也没有阻塞。因此,没有出现死锁现象(因为没有进程阻塞),但是从现象上看,好像死锁发生了,这就是活锁Livelock)。

 

再举另一个例子。在一些系统中,进程表中容纳的进程数决定了系统允许的最大进程数量。因此进程表属于有限的资源。如果由于进程表满了而导致一次fork运行失败,那么一个合理的方法是:该程序等待一段随机长的时间,然后再次尝试运行fork

现在假设一个UNIX系统有100个进程槽,10个程序正在运行,每个程序需要创建12子)进程。在每个进程创建了9个进程后,10个源进程和90个新的进程就占满了进程表。此时10个源程序便进入了活锁,即不停地进行分支循环和运行失败。

与死锁和活锁非常相似的一个问题是饥饿Starvation)。在动态运行的系统中,对资源的请求在任何时刻都可能发生。这就需要一些策略来决定在什么时候谁获得什么资源。

以打印机分配为例。假设系统采用了某种算法保证打印机分配过程不会产生死锁。现在假设若干进程同时都请求打印机,究竟谁能获得打印机呢?一个可能的分配方案是把打印机分配给打印最小文件的进程。这个方法让尽量多的客户满意,并且看起来很公平。然而,如果出现下面的情况:一个打印工作繁忙的系统中,有一个进程要打印一个很大的文件,而每当打印机空闲时,系统便把打印机分配给打印最小文件的进程。如果始终存在一个固定的进程流,其中的进程都是只打印小文件,那么要打印大文件的进程永远也得不到打印机,该进程饥饿而死,尽管它没有被阻塞,但被无限推后。

饥饿现象可以通过先来先服务资源分配策略来避免。在这种机制下,等待得最久的进程会是下一个被调度的进程。随着时间的推移,所有进程都最终能够获得资源而完成。

8.1.2死锁产生的原因

产生死锁的原因主要有两个:一是竞争资源,系统提供的资源数量有限,不能满足每个进程的需求;二是多道程序运行时,进程推进顺序不合理。

1.资源的概念

死锁是若干进程因使用资源不当而造成的现象。按照资源的使用性质,一般把系统中的资源分成两类:永久性资源(可重用资源),是指系统中那些可供进程重复使用、长期存在的资源,如内存、外部设备、CPU等硬件资源,以及各种数据文件、表格、共享程序代码等软件资源。临时性资源(消耗性资源,是指由某个进程所产生、只为另一个进程使用一次或经过短暂时间后便不再使用的资源,如I/O和时钟中断、同步信号、消息等。

永久性资源和临时性资源都可能导致死锁发生。

2.死锁的例子

下面举几个例子,并通过它们来分析,归纳出产生死锁的必要条件。

1申请不同类资源产生死锁

例如,进程P1P2运行中都使用设备,假定系统中只有一台输入设备,一台输出设备。

P1

申请一台输入设备;

申请一台输出设备;

使用之;

释放输入设备;

释放输出设备;

P2

申请一台输入设备;

申请一台输出设备;

使用之;

释放输入设备;

释放输出设备;

当进程A申请并获得了输入设备后,由于某种原因,停止前进。此时P2到达,P2进程完成了对输出设备的申请,接下来再申请输入设备,必将被阻塞且进入等待队列等待,若进程A重新获得运行机会,接下来便要申请输出设备,同样,它也被阻塞进入等待该设备的等待队列。进程P1P2彼此无限地等待对方释放资源,形成了僵局,如图8-3所示

 

2申请同类资源产生死锁

假设有一类可重用资源/?,如主存(或磁盘),它包含有m个页面(或扇区),由n个进程P2Pn(2<m<n享。假定每个进程按下述顺序申请和释放页面或扇区):

申请一页(或扇区)

申请一页(或扇区)

释放一页(或扇区)

释放一页(或扇区)

这里每次申请和释放只涉及R的一个分配单元(页面或扇区)。因此,当所有单元全部分配完毕时,很容易发生死锁;占有R的单元的所有进程(m个进程会永远封锁在第二次申请上,而有些进程(n-m个进程类似地会封锁在它们的第一次申请上。图8-4说明了n=3m=2时系统的状态。这类死锁是相当普遍的。例如,在若干输入和输出进程竞争磁盘空间的SPOOLing系统中就可能发生这类死锁

 

8-4同类资源共享时的死锁现象

如果磁盘空间完全分配给等待装入的作业输入文件和已部分运行的作业输出记录,则系统就发生了死锁。

3PV操作使用不当产生死锁

进程在相互同步与通信中也可能产生死锁现象。例如,第5章描述的生产者和消费者问题,若把生产者和消费者程序中前面两个P操作位置颠倒,如下所示:

生产者进程:

P(mutex)

P(empty)

消费者进程:

P(mutex);

P(full);

当进行了n次生产后(或n个生产者,每人都送了一个产品后),缓冲区全部占满empty=0若生产者执彳了Pmutex后(此时mutex=0,又执行了Pempty,由于empty=-1,使生产者因无可用缓冲区而在empty上等待。若又有一个消费者进程到达,并执行了PmuteX使mutex=-1。因此,消费者也阻塞,并且在mutex上等待。此时,生产者、消费者都彼此等待对方来唤醒自己,处于循环等待状态。生产者等待消费者释放一个空缓冲区,而消费者等待生产者释放互斥信号量mutex这样便形成了死锁局面。

4对临对性资源的使用不加限制而引起的死锁

在进程通信时使用的信件可以看作是一种临时性资源,如果对信件的发送和接收不加限制的话,则可能引起死锁。比如,进程&等待进程P3的信件士到来后再向进程P2送信件S1P2又要等待的信件S1到来后再向P3发送信件S2,而P3也要等待P2的信件S2来到后才能发出信件S3:在这种情况下就形成了循环等待,永远结束不了,产生死锁。

8.1.3产生死锁的必要条件

死锁的产生与各并发进程的相对速度有关,一般不可重现,它涉及进程的并发执行、资源共享和资源分配等因素。对于永久性资源,产生死锁有如下四个必要条件:

1互斥条件

资源是独占的且排他使用。进程互斥使用资源,即任一时刻一个资源只能给一个进程使用,其他进程若请求一个资源而该资源被另一进程占有时,则申请者等待,直到资源被占用者释放。

2不可剥夺条件

又称不可抢占或不可强占。进程所获得的资源在未使用完毕之前,不能被其他进程强行剥夺,而只能由获得该资源的进程自愿释放。

3请求和保持条件

又称部分分配或占有申请。进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。

4循环等待条件

又称环路等待。在发生死锁时,必然存在一个进程等待队列P1,P2···Pn,其中P1等待P2占有的资源,P2等待P3占有的资源,Pn等待P1占有的资源,形成一个进程等待环路。环路中每一个进程已占有的资源同时被另一个进程所申请,即前一个进程占有后一个进程所请求的资源。

以上给出了导致死锁的四个必要条件。只要系统发生死锁,则以上四个条件一定成立。事实上,第四个条件(即循环等待的成立蕴涵了前三个条件的成立,似乎没有必要全部列出。然而,分别考虑这些条件对于死锁的预防是有利的,因为可以通过破坏这四个条件中的任何一个来预防死锁的发生,这就为死锁预防提供了多种途径。

8.1.4解决死锁的方法

操作系统中对待死锁问题的一种方式是忽视它,这是因为考虑死锁所带来的危害与解决死锁所花费的代价哪一个更重要后的决策,这种方式通常称为鸵鸟算法;另一种方式是通过各种手段解决死锁问题。

目前用于解决死锁的方法有两类,一类是不让死锁发生;另一类是让死锁发生,再加以解决,具体为以下四种:

1预防死锁。通过设置某些严格限制,破坏产生死锁的条件(除第一个条件外的其他条件)以防止死锁发生。这一方法会导致系统资源利用率过低。

2避免死锁。在资源的动态分配过程中,采取某种方法防止系统进入不安全状态,从而避免死锁的发生。这种方法只需以较弱的限制条件为代价,并可获得较高的资源利用率。

3检测死锁。允许系统运行过程中发生死锁,即事先不采取任何预防、避免措施。但通过在系统中设置检测机构,可以及时检测出死锁是否真的发生,并能精确地确定与死锁有关的进程与资源,然后采取措施解除死锁。

4解除死锁。这是与死锁检测相配套的措施,用于将进程从死锁状态下解脱出来。

以下各节分别介绍每一种方法。

8.2死锁预防

在系统设计时确定资源分配算法,限制进程对资源的申请,从而保证不发生死锁。具体的做法是破坏产生死锁的四个必要条件之一。在8.1.3小节中讨论了产生死锁的四个必要条件。如果设法使其中的一个条件不成立,那么就破坏了死锁产生的条件,从而可以预防死锁的发生。这是一种保证系统不进入死锁状态的静态策略。

8.2.1破坏互斥条件

如果资源不被一个进程所独占,那么死锁肯定不会产生。以打印机为例,允许两个进程同时使用打印机会造成混乱,但是可以通过采用假脱机SPOOLing技术,允许若干个进程同时产生输出其设计思想是创建管理真正打印机的打印机守护进程,由于守护进程决不会请求别的资源,从而可以消除因打印机而产生的死锁。

8.2.2破坏不可剥夺条件

在允许进程动态申请资源的前提下规定:一个进程在申请新资源的要求不能立即得到满足时便处于等待状态,而一个处于等待状态的进程的全部资源可以被剥夺。也就是说,这些资源隐式地释放了,被剥夺的资源重新加入到资源表中。仅当该进程重新获得它原有的资源以及得到新申请的资源时,才能重新启动执行。

具体实施方案为若一个进程申请某些资源,首先系统应检查这些资源是否可用,如果可用,就分配给该进程。否则,系统检查这些资源是否分配给另外某个等待进程。若是,则系统将剥夺所需资源,分配给这个进程。如果资源没有被等待进程占有,那么该进程必须等待。在其等待过程中,其资源也有可能被剥夺。

破坏不可剥夺条件以预防死锁的方法适用于这样一些资源,它们的状态是容易保存和恢复的,例如CPU内存等。

这种策略实现起来较复杂,而且代价太大。因为,一个资源在使用一段时间后被强行剥夺,会造成前阶段工作失效。而且,在极端情况下,可能出现某个进程反复申请和释放资源,使进程执行无限推迟,还增加系统开销,延长了进程的周转时间,降低系统的吞吐量和性能。

8.2.3破坏请求和保持条件

第一种方法是每个进程必须在开始执行前就申请它所需要的全部资源,仅当系统能满足进程的资源申请要求且把资源一次性分配给进程后,该进程才能开始执行。这是静态分配资源策略,而资源的动态分配是指进程需要使用资源时才提出申请,系统再进行分配。

采用静态分配资源的策略后,进程在执行过程中不再申请资源,故不可能出现占有了某些资源再等待其他资源的情况,即请求和保持的条件不成立,从而防止死锁的发生。静态分配资源策略的优点是简单、安全、容易实施;但其缺点是严重浪费系统资源,会造成一些资源在很长时间内得不到使用,降低资源利用率。另外,因为当进程获得其所需全部资源后方能开始运行。但由于有些进程长期占用资源,致使进程推迟,甚至得不到运行。

第二种方法是仅当迸程没有占用资源时才允许它去申请资源,如果进程已经占用了某些资源而又要再申请资源,则它应先归还所占的资源后再申请新资源。

这种资源分配策略仍会使进程处于等待状态,这是因为进程所申请的资源可能已被其他进程占用,只能等占用者归还资源后才可分配给申请者,但是,申请者是在归还资源后才申请新资

源的,故不会出现占有了部分资源再等待其他资源的现象。这种方法也存在着资源利用率低的缺点。

8.2.4破坏循环等待条件

采用资源有序分配策略,其基本思想是将系统中所有资源顺序编号,一般原则是,较为紧缺、稀少的资源的编号较大。进程申请资源时,必须严格按照资源编号的顺序进行,否则系统不予分配。即一个进程只有得到编号小的资源,才能申请编号较大的资源;释放资源时,应按编号递减的次序进行。

例如,设扫描仪、打印机、磁带机、磁盘的编号依次为1245这样,所有进程对资源的申请,严格地按编号递增的次序提出。

可以证明,按照资源有序分配策略分配资源,破坏了循环等待条件。

下面以解决经典的进程同步问题——哲学家就餐问题来考查如何利用资源有序分配法防止死锁。

5个哲学家以思考、用餐交替进行的方式生活,他们坐在一张圆桌边,桌子上有5个盘子和5只筷子。如图8-5所示。当一个哲学家思考时,他与邻座的哲学家没有任何联系。当一个哲学家感觉到饿了,他就试图拿起他左右两边的筷子用餐。如果他的邻座已经拿到筷子,则他可能只拿到一支甚至一支筷子也拿不到。当一个饥饿的哲学家得到了两支筷子,他就可以用餐。当他用餐完毕,他就放下筷子并再次开始思考。

 

对上述问题的一个简单的解决方案是为每只筷子设置一个信号量,一个哲学家通过在相应信号量上执行P操作抓起一支筷子,通过执行V操作放下一支筷子。5个信号量构成一个数组VAR8-5哲学家就餐问题chopstick ARRAY [0..4] OF semaphore每个信号量都置初值为1。于是,哲学家进程可描述如下:

while(true) {

思考;

P(chopstick[1];{拿左手筷子}

P(chopstick[(i+1) mod 5];{拿右手模子}

用餐;

V(chopstick[i]){放左手筷子}

V(chopstick[(i+1) mod 5]){放右手筷子}

}

以上解法虽然可以保证互斥使用筷子,但有可能产生死锁。假设5个哲学家同时抓起左手的筷子,于是5个信号量的值都为0,此时当每一个哲学家企图拿起他右手的筷子时,便出现了循环等待的局面——死锁。

为了防止死锁的产生,还可以有以下一些措施:

1至多只允许4个哲学家同时坐在桌子的周围。

2仅当一哲学家左右两边的筷子都可用时,才允许他抓起筷子。

3让所有哲学家顺序编号。对于奇数号的哲学家必须首先抓起左手的筷子,然后抓起右手的;而对偶数号哲学家则反之。

下面介绍(3所提出的解决方案。

为了防止死锁的产生,可以采用资源的有序分配法,规定每个哲学家想用餐时总是先拿编号小的筷子再拿编号大的筷手就不会出现死锁现象。因此,哲学家进程的程序仍然不变,而第5个哲学家进程的程序可作如下改进:

while(true) {

思考;

P(chopstick[1];{拿右筷子}

P(chopstick[5];{拿左手筷子}

用餐;

V(chopstick[1];{放右手筷子}

V(chopstick[5];{放左手筷子}

下面分析一下,当每个哲学家都想就餐时,可能有4个哲学家都已拿到了左手的一支筷子,而剩下的第5个哲学家拿不到编号小的筷子(一定被某个哲学家拿走了而等待,该哲学家不可能去拿另一支筷子。因此,4个哲学家中的一个哲学家就有机会拿起其右边的筷子而可以用餐,就餐后放下一双筷子,以使另一个哲学家又可得到右边的筷子去用餐。同样地,其他哲学家也都先后可吃到饭,从而防止了死锁。

一般,为了提高资源的利用率,通常应当按照大多数进程使用资源的次序对资源进行编号。先使用者编号小,后使用者编号大。

这种硬性规定申请资源的方法,会给用户编程带来限制,按照编号顺序申请资源增加了资源使用者的不便;此外,如何合理编号是一件困难的事情,特别当系统添加新设备类型时,会造成不灵活、不方便;如果有进程违反规定,则仍可能发生死锁。资源有序分配法与资源静态分配策略相比,显然提高了资源利用率,进程实际需要申请的资源不可能完全与系统所规定的统一资源编号一致,为遵守规定,暂不需要的资源也要提前申请,仍然会造成资源的浪费。

8.3死锁避免

上面讨论的几种死锁预防策略总体上是增加了较强的限制条件,从而使实现较为简单,但却严重地影响了系统性能。本节将讨论施加较少的限制条件,因而会获得较满意的系统性能,从而避免系统发生死锁的方法。

死锁避免的基本思想是:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源;如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。

死锁避免和死锁预防的区别在于,死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格地防止死锁的出现。而死锁避免则不那么严格地限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。

8.3.1安全与不安全状态

由于在避免死锁的策略中允许进程动态地申请资源,因而,系统需提供某种方法在进行资源分配之前先分析资源分配的安全性,当估计到可能有死锁发生时就应设珐避免死锁的发生。

如果操作系统能保证所有的进程在有限时间内得到所需的全部资源,则称系统处于安全状态,否则说系统是不安全的。

所谓安全状态是指,如果存在一个由系统中所有进程构成的安全序列{P1Pn},则系统处于安全状态。一个进程序列{P1Pn}是安全的,如果对于其中每一个进程Pi1≤i≤n),

以后尚需要的资源量不超过系统当前剩余资源量与所有进程pj(j<i前占有资源量之和。系统处于安全状态则不会发生死锁。

如果不存在任何一个安全序到,则系统处湯不安全状态。不安全状态一定导致死锁,但不安全状态不一定是死锁状态。即系统若处于不安全状态则可能发生死锁。

例如:现有12个同类资源供3个进程共享,进程P1总共需要9个资源,但第一次先申请2;进程P2总共需要10个资源,第一次要求分配5个资源;进程P3总共需要4个资源,第一次请求2个资源。经第一次分配后,系统中还有3个资源未被分配,系统状态如图8-6所示。

 

目前占有量

最大需求量

尚需要量

p1

2

9

7

P2

5

10

5

p3

2

4

2

系统剩余资源量

3

8-6第一次分配后的系统状态

这时,系统处于安全状态,因为还剩余3个资源,可把其中的2个资源再分配给进程P3,系统还剩余1个资源。进程&已经得到了所需的全部资源,能执行到结束且归还所占的4个资源,现在系统共有5个可用资源,可分配给进程P2同样地,进程尽得到了所需的全部资源,执行结束后可归还10个资源,最后进程P1能得到尚需的7个资源而执行到结束,然后归还9个资源。这样,三个进程都能在有限的时间内得到各自所需的全部资源,执行结束后系统可收回所有资源。其安全序列为P3P2P1

若进程A提出再申请一个资源的要求,系统从剩余的资源中分配1个给进程P1后尚剩余2个资源,新的系统状态如图8-7所示。

 

目前占有量

最大需求量

尚需要量

P1

3

9

6

P2

5

10

5

P3

2

4

2

系统剩余资源量

2

8-7第二次分配后,系统处于不安全状态

虽然剩余的2个资源可满足进程P3的需求,但当进程P3得到全部资源且执行结束后,系统最多只有4个可用资源,而进程P1和进程P2还分别需要6个资源和5个资源,显然,系统中的资源已不能满足剩下两个进程的需求了,即这两个进程巳经不能在有限的时间里得到需要的全部资源,因此找不到安全序列,系统已从安全状态转到了不安全状态。

只要能使系统总是处于安全状态就可避免死锁的发生。每当有进程提出资源申请时,系统可以通过分析各个进程已占有的资源数目、尚需资源的数目以及系统中可以分配的剩余资源数目,以决定是否为当前的申请进程分配资源。如果能使系统处于安全状态,则可为进程分配资源,否则暂不为申请进程分配资源。

8.3.2银行家算法

最著名的死锁避免算法是由DijkstmHaberaiarm提出来的银行家算法。这一名称的来历是基于该算法将操作系统比作一个银行家,操作系统的各种资源比作周转资金,申请资源的进程比作向银行贷款的顾客。那么操作系统的资源分配问题就如同银行家利用其资金贷款的问题,一方面银行家能贷款给若干顾客,满足顾客对资金的要求;另一方面,银行家可以安全地收回其全部贷款而不至于破产。就像操作系统能满足每个进程对资源的要求,同时整个系统不会产生死锁。

为保证资金的安全,银行家规定:

1当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;

2顾客可以分期贷款,但贷款的总数不能超过最大需求量;

3当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款;

4当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金。

操作系统按照银行家的规定为进程分配资源,进程首先提出对资源的最大需求量,当进程在执行中每次申请资源时,系统测试该进程已占用的资源与本次申请的资源数之和是否超过了该进程对资源的最大需求量。若超过则拒绝分配资源,若没有超过则系统再测试系统现存的资源能否满足该进程尚需的最大资源量,若能满足则按当前的申请量分配资源,否则也要推迟分配。这样做,能保证在任何时刻至少有一个进程可以得到所需要的全部资源而执行结束,执行结束后归还的资源加人到系统的剩余资源中,这些资源又至少可以满足另一个进程的最大需求……于是,可以保证系统中所有进程都能在有限的时间内得到需要的全部资源。

1.数据结构

为实现银行家算法,系统中须设置若干数据结构。

1可使用资源向量如

它是一个具有m个元素的数组,其中,每一个元素代表一类可使用资源的数目,其初值为系统中所配置的该类全部可使用资源的数目。其数值随该类资源的分配与回收而动态改变。若Available[j]=1表示系统中现有krj类资源。

2最大需求矩阵Max

一个nxm的矩阵,它定义了系统中n个进程中每一个进程对m类资源的最大需求。如果Max(fy)=I表示进程Pi至多需要krj类资源。

3分配矩阵Allocation

一个nxm矩阵,它定义了系统中每类资源当前已分配给一个进程的资源数。如果Allocation(i,j)=t表示进程A当前已分得krj类资源。

4需求矩阵Need

一个nxm矩阵,用以表示每一个进程尚需的各类资源数目。如果Need[ij]=k示进程Pi还需要krj类资源才能完成任务。显然有

Need(ij=Max(i,j)-Allocation(ij

5工作向量Work

它是一个具有w个元素的数组,其中,每一个元素代表系统可提供给进程继续运行所需的各类资源数目。在执行安全性检查开始时,Work=Available

6状态标志Finish

它是一个具有n个元素的数组,其中,每一个元素表示进程Pi已获得足够的资源可以执行完毕并能释放全部获得资源的状态。其初始值为false,达到上述要求时其值为true

7进程申请资源向量Request

Requesti[j]=k,表示进程Pi需要申请krj类资源。

为了简化算法的表述,我们将矩阵AllocationNeed中的每一行元素看作一个向量,分别记为AllocationiNeedi表示当前分配给进程的资源表示进程尚需的资源。再引进一些记号,令XY表示长度为n的向量,说X≤Y当且仅当X[i]≤Y[i],对于所有的i=l23n都成立。例如,如果X=(1732)Y=(0321),则Y≤X;Y≤XY≠X,则Y<X

2.算法介绍

1银行家算法

当进程Pi申请一个资源时,系统完成以下工作:

a.如果Requesti大于Needi示出错,因为进程申请的资源多于它自己申报的最大量;

b.如果Requesti大于Available,则Pi必须等待;

c.否则,系统假设已分给Pi所申请的资源(试探性分配),并修改系统状态:

Available=Available-Requesti

Allocationi=Allocationi+Requesti

Needi=Needi-Requesti

d.调用安全性算法,判断现在的系统状态是否仍处于安全状态,若是,则真正实施分配;否则,拒绝此次分配,恢复原来的系统状态,进程Pi等待。

Available=Available+Requesti;

Allocationi:=Allocationi-Requesti;

Needi=Needi+Requesti.

2安全性算法

安全性算法是银行家算法的子算法,是由银行家算法调用的。判断一个状态是否安全的算法如下:

a.WorkFinish分别是长度为n的向量,初始化:

Work:=Available

Finish[i]=false(i=1,2,•••,n);

b.寻找符合下列条件的i

Finish[i]=false并且Needi≤Work

如果没有这样的i存在,转到步骤d;

c.Work=Work+Allocationi

Finish[i]=true;

转到步骤b

d.如果对所有的i,Finish[i]=true都成立,则系统处于安全状态;否则系统是不安全的。

3.银行家算法应用实例

下面通过一个例子来说明怎样应用银行家算法进行资源分配。假定某系统有三类资源ABCA类资源共有10个资源实例B类资源共有5个资源实例,C类资源共有7个资源实例,现有5个进程P1P2P3P4P5,它们对各类资源的最大需求量和第一次分配后已占有的资源量如图8-8所。

 

8-8银行家算法应用例子:第一次分配后的系统状态

应用银行家算法,找到一个进程安全序列P2,P4,P5,P3,P1,可以得出结论:图8-8中的系统状态是安全状态。在此状态下,进程P2提出新的资源申请A1个,B0个,C2个,进行试探性分配后,系统状态如图8-9所示。

      资源申请

进程

目前占有量

最大需求量

尚需要量

A  B  C

A  B  C

A  B  C

P1

0  1  0

7  5  3

7  4  3

P2

3  0  2

3  2  2

0  2  0

P3

3  0  2

9  0  2

6  0  0

P4

2  1  1

2  2  2

0  1  1

P5

0  0  2

4  3  3

4  3  1

系统剩余资源量

A  B  C

2  3  0

8-9银行家算法应用例子:新的系统状态

应用银行家算法,仍然可以找到一个进程安全序列P2,P4,P5,P1,P3,表明该系统状态是安全状态,可以真正实施资源分配。

在图8-9所示状态下,进程P5又申请资源A3个,B3个,C0个,此时系统不能实施分配,因为该申请超过了系统当前剩余的资源量。若进程P1提出新的资源申请:A0,B2个,C0个,也不能实施资源分配,因为根据银行家算法,若进行了分配,将导致系统进入不安全状态,则在新的系统状态下找不到进程安全序列。

银行家算法是通过动态地检测系统中资源分配情况和进程对资源的需求情况来决定如何分配资源的,在能确保系统处于安全状态时才把资源分配给申请者,从而避免系统发生死锁。由于银行家算法是在系统运行期间实施的,要花费相当多的时间,该算法需要mxn2操作。银行家算法要求每类资源的数量是固定不变的,而且必须事先知道资源的最大需求量,这难以做到。不安全状态并非一定是死锁状态,如果一个进程申请的资源当前是可用的,但该进程必须等待,这样资源利用率会下降。

8.4死锁检测与解除

以上两节讨论了死锁预防和死锁避免的几种方法,但是这些方法都比较保守,并且都是以牺牲系统效率和浪费资源为代价的,这恰恰与操作系统的设计目标相违背。假如系统为进程分配资源时,不采取任何限制性措施来保证系统不进入死锁状态,即允许死锁发生,但操作系统不断地监督进程的进展路径,判定死锁是否真的发生,并且一旦死锁发生,则采取专门的措施解除死锁,并以最小代价使整个系统恢复正常,这就是死锁检测和解除。

8.4.1死锁检测

操作系统可定时运行一个死锁检测程序,该程序按一定的算法去检测系统中是否存在死锁。检测死锁的实质是确定是否存在循环等待条件,检测算法确定死锁的存在并识别出与死锁有关的进程和资源,以供系统采取适当的解除死锁措施。

下面介绍一种死锁检测机制:

1为每个进程和每个资源指定唯一编号;

2设置一张资源分配状态表,每个表目包含资源号和占有该资源的进程号两项,资源分配表中记录了每个资源正在被哪个进程所占有;

3设置一张进程等待分配表,每个表目包含进程号和该进程所等待的资源号两项;

4死锁检测算法:当任一进程&申请一个已被其他进程占用的资源r,时,进行死锁检测。检测算法通过反复查找资源分配表和进程等待表,来确定进程Pj对资源ri的请求是否导致形成环路,若是,便确定出现死锁。

例如,系统中有进程P1,P2P3共享资源r1r2r3在某一时刻资源使用情况如图8-10a所示。此后先后发生P1请求r2P2请求r3,P3请求r1.当执行死锁检测算法后,得到图8-10b;再执行死锁检测算法,得到图8-10c;再执行死锁检测算法,得到图8-10d)。检查图8-10d与图8-10a,确定出现死锁。

 

8.4.2死锁解除

一旦检测到死锁,便要立即设法解除死锁。一般说来,只要让某个进程释放一个或多个资源就可以解除死锁。死锁解除后,释放资源的进程应恢复它原来的状态,才能保证该进程的执行不会出现错误。因此,死锁解除实质上就是如何让释放资源的进程能够继续运行。为解除死锁就要剥夺资源,此时,需要考虑以下几个问题:

1选择一个牺牲进程,即要剥夺哪个进程的哪些资源?

2重新运行或回退到某一点幵始继续运行。若从一个进程那里剥夺了资源,要为该进程做些什么事情?显然,这个进程是不能继续正常执行了,必须将该进程回退到起点或某个状态,以后再重新开始执行。令进程夭折的方法虽然简单,但代价大;而更有效的方法是只让它退回到足以解除死锁的地步即可。那么,问题转换成进程回退的状态由什么组成?怎样才能方便地确定该状态,这就要求系统保持更多的有关进程运行的信息。

3怎样保证不发生饿死现象。即如何保证并不总是剥夺同一进程的资源,而导致该进程处于饥饿状态。

4最小代价。即最经济合算的算法,使得进程回退带来的开销最小。但是,最小开销是很不精确的,进程重新运行的开销包括如下很多因素:

进程的优先级;

进程已经运行了多长时间,该进程完成其任务还需要多长时间?

该进程使用的资源种类和数量?这些资源能简单地被剥夺吗?

为完成其任务,进程还需要多少资源?

有多少进程要被撤销?

该进程被重新启动运行的次数?

一旦决定一个进程必须回退,就一定要确定这个进程回退多少。最简单的办法是从头来,让其重新运行,这将会使一个进程的工作前功尽弃

死锁解除法可归纳为两大类。

1.剥夺资源

使用挂起/激活机制挂起一些进程,剥夺它们占有的资源给死锁进程,以解除死锁,待以后条件满足时,再激活被挂起的进程。

由于死锁是由进程竞争资源而引起的,所以,可以从一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态。剥夺的顺序可以是以花费最小资源数为依据。每次剥夺后,需要再次调用死锁检测算法。资源被剥夺的进程为了再得到该资源,必须重新提出申请。为了安全地释放资源,该进程就必须返回到分配资源前的某一点。经常使用的方法有:

1还原算法,即恢复计算结果和状态。

2建立检查点。主要是用来恢复分配前的状态。这种方法对实时系统和长时间运行的数据处理来说是一种常用技术。在实时系统中,经常在某些程序地址处插入检查的程序段,即采用检查点的技术来验证系统的正确性,如发现故障,可从检查点重新启动。因此,在有些实时系统中,一旦发现死锁,可以在释放某进程的资源后,从检查点重新启动。

2.撤销进程

撤销死锁进程,将它们占有的资源分配给另一些死锁进程,直到死锁解除为止。

可以撤销所有死锁进程,或者逐个撤销死锁进程,每撤销一个进程就检测死锁是否继续存在,若已没有死锁,就停止进程的撤销。

如果按照某种顺序逐渐地撤销已死锁的进程,直到获得为解除死锁所需要的足够可用的资源为止,那么在极端情况下,这种方法可能造成除一个死锁进程外,其余的死锁进程全部被撤销的局面。

按照什么原则撤销进程?较实用而又简便的方法是撤销那些代价最小的进程,或者使撤销进程的数目最小。以下几点可作为衡量撤销代价的标准:

1进程优先数,即被撤销进程的优先数。

2进程类的外部代价。不同类型的进程可以规定出各自的撤销代价。系统可根据这些规定,撤销代价最小的进程,达到解除死锁的目的。

3运行代价,即重新启动进程并运行到当前撤销点所需要的代价。这一点可由系统记账程序给出。

撤销法的优点是简单明了,但有时可能不分青红皂白地撤销一些甚至不影响死锁的进程。

对死锁的处理始终缺少令人满意的完善的解决办法。Howard1973年提出一个建议,他建议把前面介绍的几种基本方法结合起来,使得系统中各级资源都以最优的方式加以利用。提出这种方法是基于资源可以按层编号,分成不同的级别。在每一级别内部可采用最合适的处理死锁的技术。这样,系统运用这种综合策略将可能不受到死锁的危害。即使出现死锁,由于采用了资源编号技术,死锁也只能出现在某一级别中。而且,在每一级别中,可采用某种基本处理技术。这样一来,操作系统可以缩小死锁危害的范围。按其思想可将系统中的资源分为四级:

1内部资源:由系统使用,如PCB表。

2内存:由用户作业使用。

3作业资源:指可分配的设备和文件。

4对换空间:指每个用户作业在辅助存储器上的空间。

对每一个级别可以采用下述方法:

1内部资源:利用资源编号可以预防死锁,因为在运行时对各种不能确定的申请不必进行选择。

2内存:可用抢占式进行预防,因为作业始终是可换出内存的,而内存是可抢占的。

3作业资源:可采用死锁避免措施,因为有关资源申请的信息可从作业说明书或作业控制说明中得到。

4对换空间:采用预先分配方式,因为通常知道最大存储需求量。

这一思想说明了可以借助不同的基本技术综合处理死锁。

总之,死锁是一种人们不希望发生的,它对计算机系统的正常运行有较大的损害,但它又是一种随机的、不可避免的现象。当然,还有一种最简单的方法来处理死锁,即像鸵鸟一样对死锁视而不见。每个人对死锁的看法都是不同的。数学家认为要彻底防止死锁的产生,不论代价有多大;工程师想要了解死锁发生的频率、系统因各种原因崩溃的频率以及死锁的严重程度,如果死锁每5年平均产生一次,而每个月系统都会因硬件故障编译器出错误或者操作系统故障而崩溃一次,那么大多数的工程师不会不惜代价地去清除死锁。

8.5分配图

刻画进程的资源分配的有效方法是资源分配图Resource Allocation Graph它是一种有向图,用于描述系统中资源和进程的状态。本节介绍资源分配图的概念、死锁定理以及通过资源分配图化简来判断系统中是否出现了死锁。

8.5.1死锁的表示——资源分配图

进程的死锁问题可以用有向图更加准确而形象地描述,这种有向图称为系统资源分配图。一个系统资源分配图SRAGSystem Resource Allocation Graph可定义为一个二元组,即SRAG=V,E其中V是顶点的集合,而E是有向边的集合。顶点集合可分为两个部分P=(P1P2Pn,是由系统内的所有进程组成的集合,每一个Pi代表一个进程;R=(r1r2rm),是系统内所有资源组成的集合,每一个代表一类资源。

边集E中的每一条边是一个有序对<Pi,ri><ri,Pi>Pi是进程Pi∈P是资源类型ri∈R)。如果<Pi,ri>∈E,则存在一条从Pi指向ri的有向边,它表示Pi提出了一个要求分配ri类资源中的一个资源的请求,并且当前正在等待分配。如果<ri,Pi>∈E则存在一条从ri类资源指向进程Pi的有向边,它表示ri类资源中的某个资源已分配给了进程Pi。有向边<Pi,ri>叫作请求边,而有向边<ri,Pi>则叫作分配边。

在有向图中,用圆圈表示进程,用方框表示每类资源。每一类资源ri有多个实例,可用方框中的圆点(实心圆点)表示各个资源实例。申请边为从进程到资源的有向边,表示进程申请一个资源,但当前该进程在等待该资源。分配边为从资源到进程的有向边,表示有一个资源实例分配给进程。注意:一条申请边仅指向代表资源类ri的方框,表示申请时不指定哪一个资源实例,而分配边必须由方框中的圆点引出,表明哪一个资源实例已被占有。

当进程尽请求资源类ri的一个实例时,将一条请求边加入资源分配图,如果这个请求是可以满足的,则该请求边立即转换成分配边;当进程随后释放了某个资源时,则删除分配边。图8-11是一个资源分配图的示例。

 

8-11给出的内容如下:

集合PRE分别为:

P={P1P2P3}             R={r1r2r3r4}

E={<P1r1><P2r3><r2P1><r2P2><r3P3>}

资源实例个数为:

|r1|=1|r2|=2|r3|=1|r4|=3

进程状态如下

进程p1已占用一个r2类资源,且正在等待获得一个r1类资源;

进程p2已占用r1r2类资源各一个,且正在等待获得一个r3类资源;

进程P3已占用一个r3类资源。

8.5.2死锁判定法则

基于上述资源分配图的定义,可给出判定死锁的法则,又称为死锁定理。

1如果资源分配图中没有环路,则系统没有死锁。

2如果资源分配图中出现了环路,则系统中可能存在死锁。

如果处于环路中的每个资源类中均只包含一个资源实例,则环路的存在即意味着死锁的存在。此时,环路是死锁的充分必要条件。

如果处于环路中的每个资源类中资源实例的个数不全为1,则环路的存在是产生死锁的必要条件而不是充分条件。

以图8-11中的资源分配图为例,假设此时进程P3申请一r2类资源,由于此时r2已没有可用资源,于是在图中加入一条新的申请边<P3r2>如图8-12所示。

此时,资源分配图中有两个环路:

P1→r1→P2→r3→P3→r2→P1

P2→r3→P3→r2→P2

显然,进程P1,P2,P3都陷入了死锁,因为进程P1正在等待进程P2释放r2类资源中的一个实例,但P2又在等待P3释放r3P3又在等待P1P2r1

在图8-13所示的资源分配图中也存在一个环路:

P1→r1→P3→r2→P1

但系统没有产生死锁,因为当P4释放了一个r2类资源后,可将它分给P3或者P2释放一个r1类资源后将它分给P1,这两种情况下环路都消失了,因而不会发生死锁。

由此可见,资源分配图中有环路,则可能发生死锁,也可能没有死锁。

 

8.5.3资源分配图化简法

可以利用简化资源分配图的方法,来检测系统是否为死锁状态。

所谓化简,是指若一个进程的所有资源请求均能被满足的话,可以设想该进程得到其所需的全部资源,最终完成任务,运行完毕,并释放所占有的全部资源。这种情况下,则称资源分配图可以被该进程化简。假如一个资源分配图可被其所有进程化简,那么称该图是可化简的,否则称该圈是不可化简的。

化简方法如下:

1在资源分配图中,找出一个既非等待又非孤立的进程结点Pi,由于Pi可获得它所需要的全部资源,且运行完后释放它所占有的全部资源,故可在资源分配图中消去所有的申请边和分配边,使之成为既无申请边又无分配边的孤立结点。

2Pi所释放的资源分配给申请它们的进程,即在资源分配图中将这些进程对资源的申请边改为分配边。

3重复(1、(2两步骤,直到找不到符合条件的进程结点。

经过化简后,若能消去资源分配图中的所有边,使所有进程都成为孤立结点,则该图是可完全化简的;否则为不可化简的。

对于较复杂的资源分配图,可能有多个既非等待又非孤立的进程结点,不同的化简过程是否会得到不同的化简图呢?可以证明,所有的化简顺序将导致相同的不可化简图。同样可以证明,系统处于死锁状态的充分条件是,当且仅当该系统的资源分配图是不可完全化简的。

以图8-12和图8-13为例,说明资源分配图的化简过程以及得出的结论。在图8-12中,找不到任何一个既非等待又非孤立的进程结点,所以该资源分配图是不可化简的,根据上述介绍,该系统发生了死锁。在图8-13中,首先找到既非等待又非孤立的P4结点,消去其分配边,得到一个可用的资源r2,由于P3有一条对r2的申请边,可将该资源分配给P2,即将P2的申请边改为分配边。在得到的新的资源分配图中,可以找到既非等待又非孤立的进程结点P3,继续将相应的分配边消去,并将P1r1的申请边改为分配边;,最终资源分配图中的所有进程结点都变成孤立结点,资源分配图是可化简的,所以可以得出结论,该系统没有发生死锁。在图8-12中,可以先挑出进程结点P2,结论是一致的。

猜你喜欢

转载自www.cnblogs.com/jtd666/p/12505378.html
今日推荐