3.4 Linux进程调度
在Linux内核v2.6.23版本的发布中,完全公平(CFS)调度算法成为默认的进程调度算法。
1 普通进程调度
(1)CFS调度算法并不会采用严格的规则来为一个任务分配某个长度的时间片,而是会为每个任务分配一定比例的CPU处理时间。
(2)CFS调度算法没有使用离散的时间片,而是采用了目标延迟,是每个可运行任务应当运行一次的时间间隔。
(3)CFS调度算法没有直接分配优先级,它通过每个任务的变量vruntime来维护虚拟运行时间(virtual run time),进而记录每个任务运行了多长时间。
2 实时进程调度
Linux系统也实现了实时调度。采用SCHED_FIFO或SCHED_RR实时策略来调度的任何任务,具有更高的优先级。
SCHED_FIFO:不基于时间片,当有更高优先级的SCHED_FIFO或SCHED_RR才能抢占它的任务,如果存在两个相同,则会轮流执行。
SCHED_RR:和SCHED_FIFO相同,只是在它耗尽时间片后,需要接收CPU的调度。
3.5 死锁概述
3.5.1 资源问题
1 可重用资源和可消耗资源
(1)可重用资源。一种可供用户重复使用多次的资源。每个可重用资源中的单元,只能分配一个进程使用,而不允许多个进程共享。对资源的请求和释放通常都是通过系统调用来实现的。计算机系统中大多数资源属于可重用资源。
(2)可消耗资源。又称临时性资源,它在进程运行期间由进程动态创建和消耗的。可消耗资源通常是由生产者进程创建、由消费者进程消耗的,最典型的可消耗资源就是用于进程间通信的消息。
2 可抢占资源和不可抢占资源
(1)可抢占资源。某进程在获得这类资源后,这类资源可以在被其他进程或系统抢占。处理机和内存均属于可抢占资源。这类资源不会引起死锁。
(2)不可抢占资源。一旦系统把这类资源分配给进程后,就不能将它强行收回,只能在进程用完后等待其自行释放。刻录机、磁带机、打印机等都属于不可抢占资源。
3.5.2 计算机系统中的死锁
1 竞争不可抢占资源引起死锁
通常,系统中所拥有的不可抢占资源的数量不足以满足多个进程运行的需要,这使得进程在运行过程中,会因争夺资源而陷入僵局。例:系统中有两个进程P1和P2,他们都准备写两个文件F1和F2,而这两个文件都属于可重用和不可抢占资源。进程P1先打开F1,后打开文件F2;进程P2先打开文件F2,后打开F1。代码如下。
P1 P2
...... ......
open(F1,w); open(F2,w);
open(F2,w); open(F1,w);
两个进程在并发执行时,如果P1先打开F1和F2,然后P2才去打开F1(或F2),这种情况可以正常运行。
但如果在P1打开F1的同时,P2去打开F2,每个进程都占有一个打开的文件,此时就会出现问题。因为当P1尝试打开F2,而P2试图打开F1时,这两个进程都会因文件已被打开而阻塞,两个进程会无限期等待下去,从而形成死锁。
2 竞争可消耗资源引起死锁
竞争可消耗资源也可引起死锁。
例:3个进程在利用消息通信机制进行通信时,所形成的死锁情况。图中m1、m2和m3是可消耗资源。进程P1,一方面产生消息m1,利用send(P2,m1)原语将它发送给P2;另一方面,他又要求从P3接收消息m3。而进程P2,一方面产生消息m2,利用send(P3,m2)原语将它发送给P3;另一方面,他又要求从P1接收消息m1。类似地,进程P3也产生消息m3,利用send(P1,m3)原语将它发送给P1;另一方面,他又要求从P2接收消息m2。代码如下:
P1:...send(P2,m1);receive(P3,m3);...
P2:...send(P3,m2);receive(P1,m1);...
P3:...send(P1,m3);receive(P2,m2);...
如果采用上述代码,这3个进程都可以先将消息发送给下一个进程,也能够接收到从上一个进程发来的消息,不会发生死锁。
P1:...receive(P3,m3);send(P2,m1);...
P2:...receive(P1,m1);send(P3,m2);...
P3:...receive(P2,m2);send(P1,m3);...
如果采用下面的代码,3个进程将会永远阻塞在它们的receive操作上,形成死锁。
3 进程推进顺序不当引起死锁
进程在运行中,对资源进程申请和释放的顺序是否合法,也是在系统中是否会产生死锁的一个重要因素。
(1)进程推进顺序合法。
(2)进程推进顺序非法。
3.5.3 死锁的定义、必要条件与处理方法
1 死锁的定义
如果一组进程中的每个进程都在等待仅由该组进程中的其他进程才能引发的事件发生,那么该组进程是死锁的。
2 产生死锁的必要条件
虽然进程在运行过程中可能会发生死锁,但产生死锁是要具备一定条件的。产生死锁必须同时满足下列4个必要条件,只要有一个不满足,都不会发生死锁。
(1)互斥条件。
(2)请求和保持条件。
(3)不可抢占条件。
(4)循环等待条件。
3 死锁的处理条件
从原理上,处理死锁有3种主要策略:
(1)采用某个协议来预防或避免死锁,确保系统永远不会进入死锁状态。 包含预防死锁方法和避免死锁方法。
(2)允许系统进入死锁状态,但是会检测它,然后恢复。 包含检测死锁方法和解除死锁方法。
(3)完全忽略这个问题,并假设系统永远不会出现死锁。 大多数系统(Linux和Windows等)采用。
实现上述策略的4种方法:
(1)预防死锁。就是通过破坏死锁产生的四个必要条件中的一个或几个。
(2)避免死锁。在资源的动态分配中,用某种方法防止系统进入不安全状态,从而避免发生死锁。
(3)检测死锁。
(4)解除死锁。撤销一些进程,回收他们的资源。
3.5.4 资源分配图
可通过资源分配图描述系统死锁。
3.6 死锁预防
死锁预防主要是通过破坏死锁产生的4个必要条件中的一个或几个来实现的,同时也可以使用假脱机技术。由于互斥条件是非共享设备所必须具有的条件,不能改变且需加以维持。
3.6.1 破坏“请求和保持”条件
1 第一种协议
该协议规定,所有进程在开始运行之前,必须一次性地申请在整个运行过程中所需的全部资源。该进程在整个运行期间便不会再提出资源要求,从而破坏了“请求”条件。系统分配资源时,只要有一种资源不能满足进程的要求,则即使所需的其他资源都空闲也不分配给该进程而让其等待,从而破坏了“保持”条件。
优点:简单、易行、安全。
缺点:资源被严重浪费,降低资源利用率。进程经常会发生饥饿现象。
2 第二种协议
该协议规定,允许一个进程只获得运行初期所需的资源后便开始运行。对第一种协议的改进。
3.6.2 破坏“不可抢占”条件
协议中规定,当一个已经保持了某些不可抢占资源的进程提出新的资源请求而不能得到满足时,他必须释放已经保持的所有资源,待以后需要时再重新申请,从而破坏了“不可抢占”条件。
3.6.3 破坏“循环等待”条件
方法:对系统的所有资源类型进行线性排序,并赋予它们不同的序号,同时规定每个进程必须按序号递增的顺序请求资源。
在使用这种策略后形成的资源分配图中,不可能再出现环路,从而破坏了“循环等待”条件。
3.7 死锁避免
避免死锁属于事先预防策略,在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁的。目前常用此方法避免发生死锁。
3.7.1 系统安全状态
在避免死锁方法中,把系统的状态分为安全状态和不安全状态两种。当系统处于安全状态时可避免发生死锁,而当系统处于不安全系统时,则可能进入死锁状态。
1 安全状态
安全状态是系统按某种进程推进顺序(P1,P2,......,Pn),为每个进程Pi分配其所需的资源,直至满足每个进程对资源的最大需求,进而使每个进程都可顺利完成的一种系统状态。称进程推进顺序(P1,P2,......,Pn)为安全序列。
不安全状态是在系统中无法找到这样一个安全序列的系统状态。
避免死锁的实质是,是系统在进行资源分配时不进入不安全状态。
2 安全状态举例
假定系统中有3个进程P1、P2、P3,共有12台磁带机。进程P1总共要用10台磁带机,进程P2和进程P3分别要用4台和9台磁带机。假设在t0时刻,进程P1、P2、P3已分别获得5台、2台、2台磁带机,尚有3台磁带机空闲(未分配)。
经分析发现,在t0时刻系统是安全的,因为这时存在一个安全序列(P2、P1、P3),即只要系统按此序列分配资源,就能使每个进程都顺利完成。
3 由安全状态进入不安全状态
如果不按照安全序列分配资源,则系统可能会由安全状态进入不安全状态。避免死锁的基本思想,即确保系统始终处于安全状态。
3.7.2 利用银行家算法避免死锁
最具代表性的避免死锁的算法是由迪杰斯特拉提出的银行家算法。原本为银行系统设计,以确保银行在发放现金贷款时不会发生不能满足所有客户需要的情况。在OS中也可用来避免死锁。
为实现银行家算法,每个新进程在进入系统时,其都必须申明在运行过程中可能需要每种资源类型的最大单元数目,该数目不应超过系统所拥有的资源总量。
1 银行家算法中的数据结构
为了实现银行家算法,必须在系统中设置4个数据结构,它们分别描述:系统中可利用的资源、所有进程对资源的最大需求、系统中的资源分配情况以及所有进程还需要多少资源。
(1)可利用资源向量Available。这是一个含有m个元素的数组,其中的每个元素代表一类可利用的资源数目,其初值是系统中所配置的该类全部可用资源数目,该数目会随对应资源的分配和回收而动态改变。
(2)最大需求矩阵Max。这是一个n×m的矩阵,它定义了系统中n个进程中的每个进程对m类资源的最大需求。
(3)分配矩阵Allocation。这是一个n×m的矩阵,它定义了系统中每类资源当前已分配给每一进程的资源数。
(4)需求矩阵Need。这是一个n×m的矩阵,用于表示每个进程尚需的各类资源数。
它们之间有如下关系:
Need[i,j]=Max[i,j] - Allocation[i,j]
2 银行家算法
设Request i是进程Pi的请求向量,如果Request i[j] = K,则表示进程Pi需要K个Rj类型的资源。
(1)如果Request i[j] <= Need[i,j],则转向步骤(2);否则认为出错,因为它所需要的资源数已超过它所宣布的最大值。
(2)如果Request i[j] <= Available[j],则转向步骤(3);否则表示尚无足够资源,Pi需等待。
(3)系统试探着把资源分配给进程Pi,并修改下列数据结构中的数值:
Available[j] = Available[j] - Request i[j];
Allocation[i,j] = Allocation[i,j] + Request i[j];
Need[i,j] = Need[i,j] - Request i[j];
(4)系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。
3 安全性算法
(1)设置两个向量。
工作向量Work:表示系统可提供给进程继续运行所需的各类资源数目,它含有m个元素,在开始执行安全算法时,Work = Available。
完成向量Finish:表示系统是否有足够的资源分配给进程。开始时先令Finish[i] = FALSE;当有足够的资源可分配给进程时,再令Finish[i] = TRUE。
(2)从进程集合中寻找一个能满足下列条件的进程:Finish[i] = FALSE;Need[i,j] = Work[j]。若能找到,执行步骤(3);否则,执行步骤(4)。
(3)当进程Pi获得资源后,可顺利执行直至完成,并释放分配给它的资源,故应执行:
Work[j] = Work[j] + Allocation[i,j];
Finish[i] = TRUE;
go to step 2;
(4)如果所有进程都满足Finish[i] = True,则表示系统处于安全状态;否则为不安全状态。
4 银行家算法举例
3.8 死锁的检测与解除
3.8.1 死锁的检测
1 死锁定理
我们可以通过简化资源分配图来检测系统所处的某状态是否为死锁状态。
(1)在资源分配图中找出一个及不阻塞又非独立的进程节点Pi。
图a到图b就是P1运行完毕后释放其占有的所有资源后的结果。
(2)P1释放资源后,便可使P2获得资源而继续运行,直到P2完成后释放出它占有的全部资源。
(3)若能消除所有的边,是所有的进程节点都成为孤立的节点,则该图是可以简化的;否则不能。
死锁定理:证明S状态为死锁状态的充分条件:当且仅当S状态的资源分配图是不可完全简化的。
2 死锁检测中的数据结构
(1)可利用资源向量Available,表示m类资源中每类资源的可用数目。
(2)把不占用资源的进程(Available = 0)计入L表。
(3)从进程集合中找到一个Request i
(4)若不能把所有进程都计入L表,则表明系统状态S的资源分配图是不可完全简化的。因此,该系统将发生死锁。
3.8.2 死锁的解除
1 终止死锁进程的方法
(1)终止所有死锁进程
(2)逐个终止死锁进程
应考虑的因素:进程优先级;进程已执行多长时间;进程已使用多少资源;进程的性质是交互式的还是批处理式的。
2 代价最小的死锁解除算法
从死锁状态中先终止一个死锁进程,将该死锁进程放入一个集合中,然后循环对所有的死锁进程依次放入集合中。之后按照终止进程时所花费代价多少,把他们放入队列中。