在多道程序系统中,调度的实质是一种资源分配,处理机调度是对处理机资源进行分配。处理机调度算法是指根据处理机分配策略所规定的处理机分配算法。在多道批处理系统中,一个作业从提交到获得处理机执行,直至作业运行完毕,可能需要经历多级处理机调度。
1. 高级调度(High Level Scheduling)
2. 低级调度(Low Level Scheduling)
3. 中级调度(Intermediate Scheduling)
为提高系统的资源利用率,应使系统中的处理机和其它所有资源都尽可能地保持忙碌状态,其中最重要的处理机利用率可用以下方法计算:
公平性是指应使诸进程都获得合理的CPU 时间,不会发生进程饥饿现象。公平性是相对的,对相同类型的进程应获得相同的服务;但对于不同类型的进程,由于其紧急程度或重要性的不同,则应提供不同的服务。
由于在系统中可能具有多种类型的进程,有的属于计算型作业,有的属于I/O型。为使系统中的CPU和各种外部设备都能经常处于忙碌状态,调度算法应尽可能保持系统资源使用的平衡性。
对所制订的策略其中包括安全策略,只要需要,就必须予以准确地执行,即使会造成某些工作的延迟也要执行。
对每个用户而言,都希望自己作业的周转时间最短。但作为计算机系统的管理者,则总是希望能使平均周转时间最短,这不仅会有效地提高系统资源的利用率,而且还可使大多数用户都感到满意。应使作业周转时间和作业的平均周转时间尽可能短。否则,会使许多用户的等待时间过长,这将会引起用户特别是短作业用户的不满。可把平均周转时间描述为:
为了进一步反映调度的性能,更清晰地描述各进程在其周转时间中,等待和执行时间的具体分配状况,往往使用带权周转时间,即作业的周转时间T与系统为它提供服务的时间Ts之比,即W = T/Ts。而平均带权周转时间则可表示为:
由于吞吐量是指在单位时间内系统所完成的作业数,因而它与批处理作业的平均长度有关。事实上,如果单纯是为了获得高的系统吞吐量,就应尽量多地选择短作业运行。
对于大、中型计算机,CPU价格十分昂贵,致使处理机的利用率成为衡量系统性能的十分重要的指标;而调度方式和算法又对处理机的利用率起着十分重要的作用。如果单纯是为使处理机利用率高,应尽量多地选择计算量大的作业运行。
由上所述可以看出,这些要求之间是存在着一定矛盾的。
在多道批处理系统中,作业是用户提交给系统的一项相对独立的工作。操作员把用户提交的作业通过相应的输入设备输入到磁盘存储器,并保存在一个后备作业队列中。再由作业调度程序将其从外存调入内存。
作业是一个比程序更为广泛的概念,它不仅包含了通常的程序和数据,而且还应配有一份作业说明书,系统根据该说明书来对程序的运行进行控制。在批处理系统中,是以作业为基本单位从外存调入内存的。
通常,在作业运行期间,每个作业都必须经过若干个相对独立,又相互关联的顺序加工步骤才能得到结果,我们把其中的每一个加工步骤称为一个作业步,各作业步之间存在着相互联系,往往是把上一个作业步的输出作为下一个作业步的输入。
例如,一个典型的作业可分成三个作业步: ① “编译”作业步,通过执行编译程序对源程序进行编译,产生若干个目标程序段; ② “连结装配”作业步,将“编译”作业步所产生的若干个目标程序段装配成可执行的目标程序; ③ “运行”作业步,将可执行的目标程序读入内存并控制其运行。
若干个作业进入系统后,被依次存放在外存上,这便形成了输入的作业流;在操作系统的控制下,逐个作业进行处理,于是便形成了处理作业流。
为了管理和调度作业,在多道批处理系统中,为每个作业设置了一个作业控制块JCB,它是作业在系统中存在的标志,其中保存了系统对作业进行管理和调度所需的全部信息。通常在JCB中包含的内容有:作业标识、用户名称、用户账号、作业类型(CPU 繁忙型、I/O 繁忙型、批量型、终端型)、作业状态、调度信息(优先级、作业运行时间)、资源需求(预计运行时间、要求内存大小等)、资源使用情况等。
作业从进入系统到运行结束,通常需要经历收容、运行和完成三个阶段。
相应的作业也就有:
作业调度的主要任务是,根据JCB中的信息,检查系统中的资源能否满足作业对资源的需求,以及按照一定的调度算法,从外存的后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。然后再将新创建的进程排在就绪队列上等待调度。因此,也把作业调度称为接纳调度(Admission Scheduling)。在每次执行作业调度时,都需做出以下两个决定。
作业调度每次要接纳多少个作业进入内存,取决于多道程序度(Degree of Multiprogramming),即允许多少个作业同时在内存中运行。当内存中同时运行的作业数目太多时,可能会影响到系统的服务质量,比如,使周转时间太长。但如果在内存中同时运行作业的数量太少时,又会导致系统的资源利用率和系统吞吐量太低。
应将哪些作业从外存调入内存,这将取决于所采用的调度算法。 最简单的是先来先服务调度算法,这是指将最早进入外存的作业最先调入内存; 较常用的一种算法是短作业优先调度算法,是将外存上最短的作业最先调入内存; 另一种较常用的是基于作业优先级的调度算法,该算法是将外存上优先级最高的作业优先调入内存; 比较好的一种算法是“响应比高者优先”的调度算法。
在批处理系统中,作业进入系统后,总是先驻留在外存的后备队列上,因此需要有作业调度的过程,以便将它们分批地装入内存。 然而在分时系统中,为了做到及时响应,用户通过键盘输入的命令或数据等都是被直接送入内存的,因而无需再配置上述的作业调度机制,但也需要有某些限制性措施来限制进入系统的用户数。即,如果系统尚未饱和,将接纳所有授权用户,否则,将拒绝接纳。类似地,在实时系统中通常也不需要作业调度。
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。 当在作业调度中采用该算法时,每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。在进程调度中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。
FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。下表列出了A、B、C、D四个作业分别到达系统的时间、要求服务的时间、开始执行的时间及各自的完成时间,并计算出各自的周转时间和带权周转时间。
进程名 |
到达时间 |
服务时间 |
开始执行时间 |
完成 时间 |
周转 时间 |
带权周转时间 |
A |
0 |
1 |
0 |
1 |
1 |
1 |
B |
1 |
100 |
1 |
101 |
100 |
1 |
C |
2 |
1 |
101 |
102 |
100 |
100 |
D |
3 |
100 |
102 |
202 |
199 |
1.99 |
从表上可以看出,其中短作业C的带权周转时间竞高达100,这是不能容忍的;而长作业D的带权周转时间仅为1.99。据此可知,FCFS调度算法有利于CPU繁忙型的作业,而不利于I/O繁忙型的作业(进程)。 CPU繁忙型作业是指该类作业需要大量的CPU时间进行计算,而很少请求I/O。通常的科学计算便属于CPU繁忙型作业。 I/O繁忙型作业是指CPU进行处理时需频繁地请求I/O。目前的大多数事务处理都属于I/O繁忙型作业。
短作业(进程)优先调度算法SJ(P)F,是指对短作业或短进程优先调度的算法。而短进程优先(SPF)调度算法则是从就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度。
SJF算法是以作业的长短来计算优先级,作业越短,其优先级越高。作业的长短是以作业所要求的运行时间来衡量的。SJF算法可以分别用于作业调度和进程调度。在把短作业优先调度算法用于作业调度时,它将从外存的作业后备队列中选择若干个估计运行时间最短的作业,优先将它们调入内存运行。
进程名 |
到达 时间 |
服务 时间 |
开始执行时间 |
完成 时间 |
周转 时间 |
带权周转时间 |
A |
0 |
3 |
0 |
3 |
3 |
1 |
B |
1 |
4 |
5 |
9 |
8 |
2 |
C |
2 |
2 |
3 |
5 |
3 |
1.5 |
D |
3 |
5 |
9 |
14 |
11 |
2.2 |
SJF调度算法能有效地降低作业的平均等待时间,提高系统吞吐量
SJF调度算法较之FCFS算法有了明显的改进,但仍然存在不容忽视的缺点: (1) 必须预知作业的运行时间。在采用这种算法时,要先知道每个作业的运行时间。即使是程序员也很难准确估计作业的运行时间,如果估计过低,系统就可能按估计的时间终止作业的运行,但此时作业并未完成,故一般都会偏长估计。 (2) 对长作业非常不利,长作业的周转时间会明显地增长。更严重的是,该算法完全忽视作业的等待时间,可能使作业等待时间过长,出现饥饿现象。 (3) 在采用FCFS算法时,人—机无法实现交互。 (4) 该调度算法完全未考虑作业的紧迫程度,故不能保证紧迫性作业能得到及时处理。
算法 |
进程名 |
A |
B |
C |
D |
E |
平 均 |
到达时间 |
0 |
1 |
2 |
3 |
4 |
||
服务时间 |
4 |
3 |
5 |
2 |
4 |
||
FCFS |
完成时间 |
4 |
7 |
12 |
14 |
18 |
|
周转时间 |
4 |
6 |
10 |
11 |
14 |
9 |
|
带权周转时间 |
1 |
2 |
2 |
5.5 |
3.5 |
2.8 |
|
SJF |
完成时间 |
4 |
9 |
18 |
6 |
13 |
|
周转时间 |
4 |
8 |
16 |
3 |
9 |
8 |
|
带权周转时间 |
1 |
2.67 |
3.1 |
1.5 |
2.25 |
2.1 |
我们可以这样来看作业的优先级,对于先来先服务调度算法,作业的等待时间就是作业的优先级,等待时间越长,其优先级越高。对于短作业优先调度算法,作业的长短就是作业的优先级,作业所需运行的时间越短,其优先级越高。但上述两种优先级都不能反映作业的紧迫程度。
在批处理系统中,FCFS算法所考虑的只是作业的等待时间,而忽视了作业的运行时间。而SJF算法正好与之相反,只考虑作业的运行时间,而忽视了作业的等待时间。高响应比优先调度算法则是既考虑了作业的等待时间,又考虑作业运行时间的调度算法,因此既照顾了短作业,又不致使长作业的等待时间过长,从而改善了处理机调度的性能。
高响应比优先算法是如何实现的呢? 如果我们能为每个作业引入一个动态优先级,即优先级是可以改变的,令它随等待时间延长而增加,这将使长作业的优先级在等待期间不断地增加,等到足够的时间后,必然有机会获得处理机。该优先级的变化规律可描述为:
由于等待时间与服务时间之和就是系统对该作业的响应时间,故该优先级又相当于响应比RP。据此,优先又可表示为:
优先级 = (等待时间+要求服务时间)/要求服务时间=1+等待时间/要求服务时间
由上式可以看出: (1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业。 (2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务。(3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高,从而也可获得处理机。
简言之,该算法既照顾了短作业,又考虑了作业到达的先后次序,不会使长作业长期得不到服务。因此,该算法实现了一种较好的折衷。当然,在利用该算法时,每要进行调度之前,都须先做响应比的计算,这会增加系统开销。 另外,服务时间也是预先估计的,会存在误差。
算法可抢占优点缺点考虑因素
FCFS非抢占式公平,实现简单对短作业不利只考虑等待时间
SJF/SPF抢占、非抢占最短平均等待/周转时间对长作业不利,可能导致饥饿只考虑运行时间
HRRN非抢占式上述两种算法的权衡折中/考虑了等待和运行时间
进程调度是OS中必不可少的一种调度。因此在三种类型的OS中,都无一例外地配置了进程调度。此外它也是对系统性能影响最大的一种处理机调度,相应的,有关进程调度的算法也较多。
进程调度的任务主要有三:
在进程调度进行调度时,首先需要保存当前进程的处理机的现场信息,如程序计数器、多个通用寄存器中的内容等,将它们送入该进程的进程控制块(PCB)中的相应单元。
低级调度程序按某种算法如优先数算法、轮转法等,从就绪队列中选取一个进程,把它的状态改为运行状态,并准备把处理机分配给它。
由分派程序(Dispatcher)把处理器分配给进程。此时需为选中的进程恢复处理机现场,即把选中进程的进程控制块内有关处理机现场的信息装入处理器相应的各个寄存器中,把处理器的控制权交给该进程,让它从取出的断点处开始继续运行。
为了实现进程调度,在进程调度机制中,应具有如下三个基本部分
为了提高进程调度的效率,应事先将系统中所有的就绪进程按照一定的方式排成一个或多个队列,以便调度程序能最快地找到它。
分派器把由进程调度程序所选定的进程,从就绪队列中取出该进程,然后进行上下文切换,将处理机分配给它。
当对处理机进行切换时,会发生两对上下文切换操作。
在采用这种调度方式时,一旦把处理机分配给某进程后,就一直让它运行下去,决不会因为时钟中断或任何其它原因去抢占当前正在运行进程的处理机,直至该进程完成,或发生某事件而被阻塞时,才把处理机分配给其它进程。
这种调度方式允许调度程序根据某种原则,去暂停某个正在执行的进程,将已分配给该进程的处理机重新分配给另一进程。在现代OS中广泛采用抢占方式,这是因为:对于批处理机系统,可以防止一个长进程长时间地占用处理机,以确保处理机能为所有进程提供更为公平的服务。在分时系统中,只有采用抢占方式才有可能实现人—机交互。在实时系统中,抢占方式能满足实时任务的需求。但抢占方式比较复杂,所需付出的系统开销也较大。
在轮转(RR)法中,系统将所有的就绪进程按FCFS策略排成一个就绪队列。系统可设置每隔一定时间(如30 ms)便产生一次中断,去激活进程调度程序进行调度,把CPU分配给队首进程,并令其执行一个时间片。当它运行完毕后,又把处理机分配给就绪队列中新的队首进程,也让它执行一个时间片。这样,就可以保证就绪队列中的所有进程在确定的时间段内,都能获得一个时间片的处理机时间。
在RR调度算法中,应在何时进行进程的切换,可分为两种情况:① 若一个时间片尚未用完,正在运行的进程便已经完成,就立即激活调度程序,将它从就绪队列中删除,再调度就绪队列中队首的进程运行,并启动一个新的时间片。② 在一个时间片用完时,计时器中断处理程序被激活。如果进程尚未运行完毕,调度程序将把它送往就绪队列的末尾。
在时间片轮转算法中,时间片的大小对系统性能有很大的影响, 如选择很小的时间片将有利于短作业,因为它能较快地完成,但会频繁地发生中断、进程上下文的切换,从而增加系统的开销; 反之,如选择太长的时间片,使得每个进程都能在一个时间片内完成,时间片轮转算法便退化为FCFS算法,无法满足交互式用户的需求。
一个较为可取的大小是,时间片略大于一次典型的交互所需要的时间。这样可使大多数进程在一个时间片内完成。
算法 |
进程名 |
A |
B |
C |
D |
E |
平 均 |
到达时间 |
0 |
1 |
2 |
3 |
4 |
||
服务时间 |
4 |
3 |
5 |
2 |
4 |
||
RR q=1 |
完成时间 |
12 |
10 |
18 |
11 |
17 |
|
周转时间 |
12 |
9 |
16 |
8 |
13 |
11.6 |
|
带权周转时间 |
3 |
3 |
3.2 |
4 |
3.25 |
3.29 |
|
RR q=4 |
完成时间 |
4 |
7 |
18 |
13 |
17 |
|
周转时间 |
4 |
6 |
16 |
10 |
13 |
9.8 |
|
带权周转时间 |
1 |
2 |
3.2 |
5 |
3.25 |
优先级进程调度算法,是把处理机分配给就绪队列中优先级最高的进程。这时,又可进一步把该算法分成如下两种。 (1) 非抢占式优先级调度算法。 (2) 抢占式优先级调度算法。
静态优先级是在创建进程时确定的,在进程的整个运行期间保持不变。优先级是利用某一范围内的一个整数来表示的,例如0~255中的某一整数,又把该整数称为优先数。确定进程优先级大小的依据有如下三个: (1) 进程类型。 (2) 进程对资源的需求。 (3) 用户要求。
动态优先级是指在创建进程之初,先赋予其一个优先级,然后其值随进程的推进或等待时间的增加而改变,以便获得更好的调度性能。
如前所述的各种调度算法,尤其在应用于进程调度时,由于系统中仅设置一个进程的就绪队列,即低级调度算法是固定的、单一的,无法满足系统中不同用户对进程调度策略的不同要求,在多处理机系统中,这种单一调度策略实现机制的缺点更显突出,由此,多级队列调度算法能够在一定程度上弥补这一缺点。
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。 该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后, 首先将它放入第一队列的末尾,按FCFS原则排队等待调度。 当轮到该进程执行时,如它能在该时间片(第一队列的时间片)内完成,便可准备撤离系统; 如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片(第二队列的时间片)后仍未完成,再依次将它放入第三队列,……, 如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n队列中便采取按时间片轮转的方式运行。
(3) 按队列优先级调度。 调度程序首先调度最高优先级队列中的诸进程运行,仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。 如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
多级反馈队列调度算法具有较好的性能,能很好地满足各种类型用户的需要。
由于终端型作业用户所提交的作业大多属于交互型作业,作业通常较小,系统只要能使这些作业(进程)在第一队列所规定的时间片内完成,便可使终端型作业用户都感到满意。
对于很短的批处理型作业,开始时像终端型作业一样,如果仅在第一队列中执行一个时间片即可完成,便可获得与终端型作业一样的响应时间。对于稍长的作业,通常也只需在第二队列和第三队列各执行一个时间片即可完成,其周转时间仍然较短。
对于长作业,它将依次在第1,2,…,n个队列中运行,然后再按轮转方式运行,用户不必担心其作业长期得不到处理。
保证调度算法是另外一种类型的调度算法,它向用户所做出的保证并不是优先运行,而是明确的性能保证,该算法可以做到调度的公平性。一种比较容易实现的性能保证是处理机分配的公平性。如果在系统中有n个相同类型的进程同时运行,为公平起见,须保证每个进程都获得相同的处理机时间1/n。
在实施公平调度算法时系统中必须具有这样一些功能: (1) 跟踪计算每个进程自创建以来已经执行的处理时间。 (2) 计算每个进程应获得的处理机时间,即自创建以来的时间除以n。 (3) 计算进程获得处理机时间的比率,即进程实际执行的处理时间和应获得的处理机时间之比。 (4) 比较各进程获得处理机时间的比率。如进程A的比率最低,为0.5,而进程B的比率为0.8,进程C的比率为1.2等。 (5) 调度程序应选择比率最小的进程将处理机分配给它,并让该进程一直运行,直到超过最接近它的进程比率为止。
分配给每个进程相同的处理机时间,显然,这对诸进程而言,是体现了一定程度的公平,但如果各个用户所拥有的进程数不同,就会发生对用户的不公平问题。
在实时系统中,可能存在着两类不同性质的实时任务,即HRT任务和SRT任务,它们都联系着一个截止时间。为保证系统能正常工作,实时调度必须能满足实时任务对截止时间的要求。为此,实现实时调度应具备一定的条件。
为了实现实时调度,系统应向调度程序提供有关任务的下述一些信息:
这是该任务成为就绪状态的起始时间,在周期任务的情况下,它就是事先预知的一串时间序列;而在非周期任务的情况下,它也可能是预知的。
对于典型的实时应用,只须知道开始截止时间,或者知道完成截止时间。
这是指一个任务从开始执行直至完成所需的时间。在某些情况下,该时间也是系统提供的。
这是指任务执行时所需的一组资源。
如果某任务的开始截止时间已经错过,就会引起故障,则应为该任务赋予“绝对”优先级;如果开始截止时间的推迟对任务的继续运行无重大影响,则可为该任务赋予“相对”优先级,供调度程序参考。
在实时系统中,通常都有着多个实时任务。若处理机的处理能力不够强,则有可能因处理机忙不过来而使某些实时任务不能得到及时处理,从而导致发生难以预料的后果。 假定系统中有m个周期性的硬实时任务,它们的处理时间可表示为Ci,周期时间表示为Pi,则在单处理机情况下,必须满足下面的限制条件:
系统才是可调度的。假如系统中有6个硬实时任务,它们的周期时间都是 50 ms,而每次的处理时间为 10 ms,则不难算出,此时是不能满足上式的,因而系统是不可调度的。
解决的方法是提高系统的处理能力,其途径有二:
其一,仍是采用单处理机系统,但须增强其处理能力,以显著地减少对每一个任务的处理时间;
其二,是采用多处理机系统。假定系统中的处理机数为N,则应将上述的限制条件改为:
在含有硬实时任务的实时系统中,广泛采用抢占机制。当一个优先权更高的任务到达时,允许将当前任务暂时挂起,而令高优先权任务立即投入运行,这样便可满足该硬实时任务对截止时间的要求。但这种调度机制比较复杂。 对于一些小型实时系统,如果能预知任务的开始截止时间,则对实时任务的调度可采用非抢占调度机制,以简化调度程序和对任务调度时所花费的系统开销。 但在设计这种调度机制时,应使所有的实时任务都比较小,并在执行完关键性程序和临界区后,能及时地将自己阻塞起来,以便释放出处理机,供调度程序去调度那种开始截止时间即将到达的任务。
为保证要求较高的硬实时任务能及时运行,在实时系统中还应具有快速切换机制,以保证能进行任务的快速切换。该机制应具有如下两方面的能力:
为使在紧迫的外部事件请求中断时系统能及时响应,要求系统具有快速硬件中断机构,还应使禁止中断的时间间隔尽量短,以免耽误时机(其它紧迫任务)。
在完成任务调度后,便应进行任务切换。为了提高分派程序进行任务切换时的速度,应使系统中的每个运行功能单位适当地小,以减少任务切换的时间开销。
可以按不同方式对实时调度算法加以分类:① 根据实时任务性质,可将实时调度的算法分为硬实时调度算法和软实时调度算法;② 按调度方式,则可分为非抢占调度算法和抢占调度算法。
该算法常用于工业生产的群控系统中,由一台计算机控制若干个相同的(或类似的)对象,为每一个被控对象建立一个实时任务,并将它们排成一个轮转队列。调度程序每次选择队列中的第一个任务投入运行。当该任务完成后,便把它挂在轮转队列的末尾,等待下次调度运行,而调度程序再选择下一个(队首)任务运行。这种调度算法可获得数秒至数十秒的响应时间,可用于要求不太严格的实时控制系统中。
如果在实时系统中存在着要求较为严格(响应时间为数百毫秒)的任务,则可采用非抢占式优先调度算法为这些任务赋予较高的优先级。当这些实时任务到达时,把它们安排在就绪队列的队首,等待当前任务自我终止或运行完成后才能被调度执行。这种调度算法在做了精心的处理后,有可能获得仅为数秒至数百毫秒级的响应时间,因而可用于有一定要求的实时控制系统中。
在要求较严格的(响应时间为数十毫秒以下)的实时系统中,应采用抢占式优先权调度算法。可根据抢占发生时间的不同而进一步分成以下两种调度算法。
在某实时任务到达后,如果该任务的优先级高于当前任务的优先级,这时并不立即抢占当前任务的处理机,而是等到时钟中断到来时,调度程序才剥夺当前任务的执行,将处理机分配给新到的高优先权任务。这种调度算法能获得较好的响应效果,其调度延迟可降为几十毫秒至几毫秒。因此,此算法可用于大多数的实时系统中。
在这种调度策略中,要求操作系统具有快速响应外部事件中断的能力。一旦出现外部中断,只要当前任务未处于临界区,便立即剥夺当前任务的执行,把处理机分配给请求中断的紧迫任务。这种算法能获得非常快的响应,可把调度延迟降低到几毫秒至100微秒,甚至更低。
该算法是根据任务的开始截止时间来确定任务的优先级。截止时间愈早,其优先级愈高。该算法要求在系统中保持一个实时任务就绪队列,该队列按各任务截止时间的早晚排序;当然,具有最早截止时间的任务排在队列的最前面。调度程序在选择任务时,总是选择就绪队列中的第一个任务,为之分配处理机,使之投入运行。最早截止时间优先算法既可用于抢占式调度,也可用于非抢占式调度方式中。
下图示出了将该算法用于非抢占调度方式之例。 该例中具有四个非周期任务,它们先后到达。 系统首先调度任务1执行,在任务1执行期间,任务2、3又先后到达。 由于任务3的开始截止时间早于任务2,故系统在任务1后将调度任务3执行。 在此期间又到达作业4,其开始截止时间仍是早于任务2的,故在任务3执行完后,系统又调度任务4执行,最后才调度任务2执行。
EDF算法用于非抢占调度的调度方式下图示出了将最早截止时间优先算法用于抢占调度方式之例。在该例中有两个周期性任务,任务A的周期时间为20 ms,每个周期的处理时间为10 ms;任务B的周期时间为50 ms,每个周期的处理时间为25 ms。 图中的第一行示出了两个任务的到达时间、最后期限和执行时间图。其中任务A的到达时间为0、20、40、…;任务A的最后期限为20、40、60、…;任务B的到达时间为0、50、100、…;任务B的最后期限为50、100、150、…(注:单位皆为ms)。
注意:这里的“最后期限”是指任务必须在该时刻之前完成,即“完成截止时间
为了说明通常的优先级调度不能适用于实时系统,该图特增加了第二和第三行。在第二行中假定任务A具有较高的优先级,所以 在t = 0 ms时,先调度A1执行,在A1完成后(t = 10 ms)才调度B1执行; 在t = 20 ms时,调度A2执行; 在t = 30 ms时,A2完成,又调度B1执行; 在t = 40 ms时,调度A3执行; 在t = 50 ms时,虽然A3已完成,但B1已错过了它的最后期限,这说明了利用通常的优先级调度已经失败。第三行与第二行类似,只是假定任务B具有较高的优先级。
第四行是采用最早截止时间优先算法的时间图。 在t = 0时,A1和B1同时到达,由于A1的截止时间比B1早,故调度A1执行; 在t = 10时,A1完成,又调度B1执行; 在t = 20时,A2到达,由于A2的截止时间比B2早,B1被中断而调度A2执行; 在t = 30时,A2完成,又重新调度B1执行; 在t = 40时,A3又到达,但B1的截止时间要比A3早,仍应让B1继续执行直到完成(t = 45),然后再调度A3执行; 在t = 55时,A3完成,又调度B2执行。在该例中利用最早截止时间优先算法可以满足系统的要求。
该算法是根据任务紧急(或松弛)的程度,来确定任务的优先级。任务的紧急程度愈高,为该任务所赋予的优先级就愈高,以使之优先执行。 例如,一个任务在200 ms时必须完成,而它本身所需的运行时间就有100 ms,因此,调度程序必须在100 ms之前调度执行,该任务的紧急程度(松弛程度)为100 ms。又如,另一任务在400 ms时必须完成,它本身需要运行 150 ms,则其松弛程度为 250 ms。 在实现该算法时要求系统中有一个按松弛度排序的实时任务就绪队列,松弛度最低的任务排在队列最前面,调度程序总是选择就绪队列中的队首任务执行。
该算法主要用于可抢占调度方式中。假如在一个实时系统中,有两个周期性实时任务A和B,任务A要求每 20 ms执行一次,执行时间为 10 ms;任务B只要求每50 ms执行一次,执行时间为 25 ms。 由此可得知任务A和B每次必须完成的时间分别为:A1、A2、A3、…和B1、B2、B3、…。为保证不遗漏任何一次截止时间,应采用最低松弛度优先的抢占调度策略。
任务A要求每 20 ms执行一次,执行时间为 10 ms; 任务B只要求每50 ms执行一次,执行时间为 25 ms。
A和B任务每次必须完成的时间
在刚开始时(t1 = 0),A1必须在20 ms时完成,而它本身运行又需 10 ms,可算出A1的松弛度为10 ms;B1必须在50 ms时完成,而它本身运行就需25 ms,可算出B1的松弛度为25 ms,故调度程序应先调度A1执行。 在t2 = 10 ms时,A2的松弛度可按下式算出: A2的松弛度 = 必须完成时间 - 其本身的运行时间 - 当前时间 = 40 ms-10 ms-10 ms = 20 ms。
类似地,可算出B1的松弛度为15ms,故调度程序应选择B2运行。 在t3 = 30 ms时,A2的松弛度已减为0(即40 - 10 - 30),而B1的松弛度为15 ms(即50 - 5 - 30),于是调度程序应抢占B1的处理机而调度A2运行。 在t4 = 40 ms时,A3的松弛度为10 ms(即60 - 10 - 40),而B1的松弛度仅为5 ms(即50 - 5 - 40),故又应重新调度B1执行。 在t5 = 45 ms时,B1执行完成,而此时A3的松弛度已减为5 ms(即60 - 10 - 45),而B2的松弛度为30 ms(即100 - 25 - 45),于是又应调度A3执行。 在t6 = 55 ms时,任务A尚未进入第4周期,而任务B已进入第2周期,故再调度B2执行。 在t7 = 70 ms时,A4的松弛度已减至0 ms(即80 - 10 - 70),而B2的松弛度为20 ms(即100 - 10 - 70),故此时调度又应抢占B2的处理机而调度A4执行。
当前OS广泛采用优先级调度算法和抢占方式,然而在系统中存在着影响进程运行的资源而可能产生“优先级倒置”的现象,即高优先级进程(或线程)被低优先级进程(或线程)延迟或阻塞。假如P3最先执行,在执行了P(mutex)操作后,进入到临界区CS-3。在时刻a,P2就绪,因为它比P3的优先级高,P2抢占了P3的处理机而运行。
一种简单的解决方法是规定:假如进程P3在进入临界区后P3所占用的处理机就不允许被抢占。
在系统中有许多不同类型的资源,其中可以引起死锁的主要是,需要采用互斥访问方法的、不可以被抢占的资源,即在前面介绍的临界资源。系统中这类资源有很多,如打印机、数据文件、队列、信号量等。
可重用性资源是一种可供用户重复使用多次的资源,它具有如下性质:
(1) 每一个可重用性资源中的单元只能分配给一个进程使用,不允许多个进程共享。
(2) 进程在使用可重用性资源时,须按照这样的顺序:① 请求资源。如果请求资源失败,请求进程将会被阻塞或循环等待。② 使用资源。进程对资源进行操作,如用打印机进行打印;③ 释放资源。当进程使用完后自己释放资源。
(3) 系统中每一类可重用性资源中的单元数目是相对固定的,进程在运行期间既不能创建也不能删除它。
可消耗性资源又称为临时性资源,它是在进程运行期间,由进程动态地创建和消耗的,它具有如下性质:① 每一类可消耗性资源的单元数目在进程运行期间是可以不断变化的,有时它可以有许多,有时可能为0;② 进程在运行过程中,可以不断地创造可消耗性资源的单元,将它们放入该资源类的缓冲区中,以增加该资源类的单元数目。③ 进程在运行过程中,可以请求若干个可消耗性资源单元,用于进程自己的消耗,不再将它们返回给该资源类中。
可把系统中的资源分成两类,一类是可抢占性资源,是指某进程在获得这类资源后,该资源可以再被其它进程或系统抢占。
另一类资源是不可抢占性资源,即一旦系统把某资源分配给该进程后,就不能将它强行收回,只能在进程用完后自行释放。
通常系统中所拥有的不可抢占性资源其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局。 我们可将上面的问题利用资源分配图进行描述,用方块代表可重用的资源(文件),用圆圈代表进程。
除了系统中多个进程对资源的竞争会引发死锁外,进程在运行过程中,对资源进行申请和释放的顺序是否合法,也是在系统中是否会产生死锁的一个重要因素。
在进程P1和P2并发执行时,如果按图中的曲线①所示的顺序推进:P1:Request(R1)→P1:Request(R2)→P1:Releast(R1)→P1:Release(R2)→P2:Request(R2)→P2:Request(R1)→P2:Release(R2)→P2:Release(R1),两个进程可顺利完成。类似地,若按图中曲线②和③所示的顺序推进,两进程也可以顺利完成。我们称这种不会引起进程死锁的推进顺序是合法的。
若并发进程P1和P2按图3-14中曲线④所示的顺序推进,它们将进入不安全区D内。此时P1保持了资源R1,P2保持了资源R2,系统处于不安全状态。此刻,如果两个进程继续向前推进,就可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生了进程死锁,这样的进程推进顺序就是非法的。
定义:在一组进程发生死锁的情况下,这组死锁进程中的每一个进程,都在等待另一个死锁进程所占有的资源。
虽然进程在运行过程中可能会发生死锁,但产生进程死锁是必须具备一定条件的。综上所述不难看出,产生死锁必须同时具备下面四个必要条件,只要其中任一个条件不成立,死锁就不会发生:
(1) 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求该资源,则请求者只能等待,直至占有该资源的进程用毕释放。
(2) 请求和保持条件:指进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
(3) 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
(4) 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,…,Pn}中的P0正在等待一个P1占用的资源; P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
(1) 预防死锁。这是一种较简单和直观的事先预防的方法。该方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来预防发生死锁。 预防死锁是一种较易实现的方法,已被广泛使用。但由于所施加的限制条件往往太严格,因而可能会导致系统资源利用率和系统吞吐量降低。
(2) 避免死锁。该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。 这种方法只需事先施加较弱的限制条件,便可获得较高的资源利用率及系统吞吐量,但在实现上有一定的难度。目前在较完善的系统中常用此方法来避免发生死锁。
(3) 检测死锁。这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,而是允许系统在运行过程中发生死锁。 但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源; 然后,采取适当措施,从系统中将已发生的死锁清除掉。
(4) 解除死锁。这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。
常用的实施方法是撤消或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。 死锁的检测和解除措施有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
预防死锁的方法是通过破坏产生死锁的四个必要条件中的一个或几个,以避免发生死锁。由于互斥条件是非共享设备所必须的,不仅不能改变,还应加以保证,因此主要是破坏产生死锁的后三个条件。
为了能破坏“请求和保持”条件,系统必须保证做到:当一个进程在请求资源时,它不能持有不可抢占资源。该保证可通过如下两个不同的协议实现:
1. 第一种协议
该协议规定,所有进程在开始运行之前,必须一次性地申请其在整个运行过程中所需的全部资源。
2. 第二种协议
该协议是对第一种协议的改进,它允许一个进程只获得运行初期所需的资源后,便开始运行
为了能破坏“不可抢占”条件,协议中规定,当一个已经保持了某些不可被抢占资源的进程,提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着进程已占有的资源会被暂时地释放,或者说是被抢占了,从而破坏了“不可抢占”条件。
这种方法中规定,系统将所有资源按类型进行线性排队,并赋予不同的序号。例如,令输入机的序号为1,打印机的序号为2,磁带机为3,磁盘为4。所有进程对资源的请求必须严格按照资源序号递增的次序提出,这样,在所形成的资源分配图中,不可能再出现环路,因而摒弃了“环路等待”条件。事实上,在采用这种策略时,总有一个进程占据了较高序号的资源,此后它继续申请的资源必然是空闲的,因而进程可以一直向前推进。
这种预防死锁的策略与前两种策略比较,其资源利用率和系统吞吐量都有较明显的改善。但也存在下述严重问题: 首先是为系统中各类资源所分配(确定)的序号必须相对稳定,这就限制了新类型设备的增加。 其次,尽管在为资源的类型分配序号时,已经考虑到大多数作业在实际使用这些资源时的顺序,但也经常会发生这种情况:即作业(进程)使用各类资源的顺序与系统规定的顺序不同,造成对资源的浪费。例如,某进程先用磁带机,后用打印机,但按系统规定,该进程应先申请打印机而后申请磁带机,致使先获得的打印机被长时间闲置。 第三,为方便用户,系统对用户在编程时所施加的限制条件应尽量少。然而这种按规定次序申请的方法,必然会限制用户简单、自主地编程。
避免死锁同样是属于事先预防的策略,但并不是事先采取某种限制措施,破坏产生死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。这种方法所施加的限制条件较弱,可能获得较好的系统性能,目前常用此方法来避免发生死锁。
在死锁避免方法中,把系统的状态分为安全状态和不安全状态。当系统处于安全状态时,可避免发生死锁。反之,当系统处于不安全状态时,则可能进入到死锁状态。
在该方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次资源分配的安全性。所谓安全状态,是指系统能按某种进程顺序(P1,P2,…,Pn)(称〈P1,P2,…,Pn〉序列为安全序列),来为每个进程Pi分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利地完成。如果系统无法找到这样一个安全序列,则称系统处于不安全状态。
假定系统中有三个进程P1、P2和P3,共有12台磁带机。进程P1总共要求10台磁带机,P2和P3分别要求4台和9台。假设在T0时刻,进程P1、P2和P3已分别获得5台、2台和2台磁带机,尚有3台空闲未分配,如下表所示:
如果不按照安全序列分配资源,则系统可能会由安全状态进入不安全状态。例如,在T0时刻以后,P3又请求1台磁带机,若此时系统把剩余3台中的1台分配给P3, 则系统便进入不安全状态。因为此时也无法再找到一个安全序列,例如,把其余的2台分配给P2,这样,在P2完成后只能释放出4台,既不能满足P1尚需5台的要求,也不能满足P3尚需6台的要求,致使它们都无法推进到完成,彼此都在等待对方释放资源,即陷入僵局,结果导致死锁。
类似地,如果我们将剩余的2台磁带机先分配给P1或P3,也同样都无法使它们推进到完成,因此,从给P3分配了第3台磁带机开始,系统便又进入了不安全状态。 由此可见,在P3请求资源时,尽管系统中尚有可用的磁带机,但却不能分配给它,必须让P3一直等待到P1和P2完成,释放出资源后再将足够的资源分配给P3,它才能顺利完成。
最有代表性的避免死锁的算法是Dijkstra的银行家算法。起这样的名字是由于该算法原本是为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。在OS中也可用它来实现避免死锁。
为了实现银行家算法,在系统中必须设置这样四个数据结构,分别用来描述系统中可利用的资源、所有进程对资源的最大需求、系统中的资源分配,以及所有进程还需要多少资源的情况。
(1) 可利用资源向量Available。这是一个含有m个元素的数组,其中的每一个元素代表一类可利用的资源数目,其初始值是系统中所配置的该类全部可用资源的数目,其数值随该类资源的分配和回收而动态地改变。如果Available[j]=K,则表示系统中现有Rj类资源K个。
(2) 最大需求矩阵Max。这是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要Rj类资源的最大数目为K。
(3) 分配矩阵Allocation。这也是一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。如果Allocation[i,j]=K,则表示进程i当前已分得Rj类资源的数目为K。
(4) 需求矩阵Need。这也是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程i还需要R j类资源K个,方能完成其任务。 上述三个矩阵间存在下述关系: Need[i, j]=Max[i, j]-Allocation[i, j]
设Request i是进程Pi的请求向量,如果Request i[j]=K,表示进程P i需要K个R j类型的资源。当P i发出资源请求后,系统按下述步骤进行检查: (1) 如果Request i[j]≤Need[i,j],便转向步骤(2);否则认为出错,因为它所需要的资源数已超过它所宣布的最大值。 (2) 如果Requesti[j]≤Available[j],便转向步骤(3);否则,表示尚无足够资源,Pi须等待。(3) 系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值: Available[j]:= Available[j]-Requesti[j]; Allocation[i,j]:= Allocation[i,j]+Requesti[j]; Need[i,j]:= Need[i,j]-Request i[j]; (4) 系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待。
系统所执行的安全性算法可描述如下: (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都满足,则表示系统处于安全状态;否则,系统处于不安全状态。
假定系统中有五个进程{P0,P1,P2,P3,P4}和三类资源{A,B,C},各种资源的数量分别为10、5、7,在T0时刻的资源分配情况如下所示。
如果在系统中,既不采取死锁预防措施,也未配有死锁避免算法,系统很可能会发生死锁。在这种情况下,系统应当提供两个算法: ① 死锁检测算法。该方法用于检测系统状态,以确定系统中是否发生了死锁。 ② 死锁解除算法。当认定系统中已发生了死锁,利用该算法可将系统从死锁状态中解脱出来。
当系统为进程分配资源时,若未采取任何限制性措施,则系统必须提供检测和解除死锁的手段,为此,系统必须做到: (1) 保存有关资源的请求和分配信息; (2) 提供一种算法,以利用这些信息来检测系统是否已进入死锁状态。
系统死锁可利用资源分配图来描述。该图是由一组结点N和一组边E所组成的一个对偶G=(N1E),它具有下述形式的定义和限制:
(1) 把N分为两个互斥的子集,即一组进程结点P={p1,p2,…,pn}和一组资源结点R={r1,r2,…,rn},N=P∪R。在图所示的例子中,P={p1,p2},R={r1,r2},N={r1,r2}∪{p1,p2}。
(2) 凡属于E中的一个边e∈E,都连接着P中的一个结点和R中的一个结点,e={pi,rj}是资源请求边,由进程pi指向资源rj,它表示进程pi请求一个单位的rj资源。e={rj,pi}是资源分配边,由资源rj指向进程pi,它表示把一个单位的资源rj分配给进程pi。图3-13中示出了两个请求边和两个分配边,即E={(p1,r2),(r2,p2),(p2,r1),(r1,p1)}。 我们用圆圈代表一个进程,用方框代表一类资源。由于一种类型的资源可能有多个,我们用方框中的一个点代表一类资源中的一个资源。此时,请求边是由进程指向方框中的rj,而分配边则应始于方框中的一个点。图3-20示出了一个资源分配图。图中,p1进程已经分得了两个r1资源,并又请求一个r2资源;p2进程分得了一个r1和一个r2资源,并又请求r1资源。
我们可以利用把资源分配图加以简化的方法,来检测当系统处于S状态时是否为死锁状态。简化方法如下: (1) 在资源分配图中,找出一个既不阻塞又非独立的进程结点Pi。在顺利的情况下,Pi可获得所需资源而继续运行,直至运行完毕,再释放其所占有的全部资源,这相当于消去Pi所有的请求边和分配边,使之成为孤立的结点。在图(a)中,将P1的两个分配边和一个请求边消去,便形成图(b)所示的情况。
资源分配图的简化(2) P1释放资源后,便可使P2获得资源而继续运行,直至P2完成后又释放出它所占有的全部资源,形成图(c)所示的情况。 (3) 在进行一系列的简化后,若能消去图中所有的边,使所有的进程结点都成为孤立结点,则称该图是可完全简化的;若不能通过任何过程使该图完全简化,则称该图是不可完全简化的。 对于较复杂的资源分配图,可能有多个既未阻塞,又非孤立的进程结点,不同的简化顺序是否会得到不同的简化图?有关文献已经证明,所有的简化顺序,都将得到相同的不可简化图。同样可以证明:S为死锁状态的充分条件是:当且仅当S状态的资源分配图是不可完全简化的。该充分条件被称为死锁定理。
(1)如果进程-资源分配图中无环路,则此时系统没有发生死锁。 (2)如果进程-资源分配图中有环路,且每个资源类中仅有一个资源,则系统中发生了死锁,此时,环路是系统发生死锁的充要条件,环路中的进程便为死锁进程。 (3)如果进程-资源分配图中有环路,且涉及的资源类中有多个资源,则环路的存在只是产生死锁的必要条件而不是充分条件
死锁检测中的数据结构类似于银行家算法中的数据结构: (1) 可利用资源向量Available,它表示了m类资源中每一类资源的可用数目。 (2) 把不占用资源的进程(向量Allocationi:=0)记入L表中,即L=Li∪L。 (3) 从进程集合中找到一个Requesti≤Work的进程,做如下处理: ① 将其资源分配图简化,释放出资源,增加工作向量Work:=Work + Allocation i。 ② 将它记入L表中。(4) 若不能把所有进程都记入L表中,便表明系统状态S的资源分配图是不可完全简化的。 因此,该系统状态将发生死锁。
Work:=Available;
L:={Li |Allocation i=0∩Request i=0}
for all Li L do
begin
for all Request i≤Work do
begin
Work :=Work + Allocation i;
L=Li∪L;
end
end
deadlock :=┓(L={p1,p2,…,pn});
这是一种最简单的方法,即是终止所有的死锁进程,死锁自然也就解除了,但所付出的代价可能会很大。因为其中有些进程可能已经运行了很长时间,已接近结束,一旦被终止真可谓“功亏一篑”,以后还得从头再来。还可能会有其它方面的代价,在此不再一一列举。
稍微温和的方法是,按照某种顺序,逐个地终止进程,直至有足够的资源,以打破循环等待,把系统从死锁状态解脱出来为止。但该方法所付出的代价也可能很大。因为每终止一个进程,都需要用死锁检测算法确定系统死锁是否已经被解除,若未解除还需再终止另一个进程。另外,在采取逐个终止进程策略时,还涉及到应采用什么策略选择一个要终止的进程。选择策略最主要的依据是,为死锁解除所付出的“代价最小”。但怎么样才算是“代价最小”,很难有一个精确的度量。
假定在死锁状态时,有死锁进程P1,P2,…,Pk。首先,撤消进程P1,使系统状态由S→U1,付出的代价为CU1,然后,仍然从S状态中撤消进程P2,使状态由S→U2,其代价为CU2,…,如此下去可得到状态U1,U2,…,Uk。若此时系统仍处于死锁状态,需再进一步撤消进程,如此下去,直至解除死锁状态为止。这种方法为解除死锁状态可能付出的代价将是k(k-1)(k-2)…/2C。显然,所花费的代价很大,因此,这是一种很不实际的方法。
一个比较有效的方法是对死锁状态S做如下处理:从死锁状态S中先撤消一个死锁进程P1,使系统状态由S演变成U1,将P1记入被撤消进程的集合d(T)中,并把所付出的代价C1加入到rc(T)中;对死锁进程P2、P3等重复上述过程,得到状态U1,U2,…,Ui,Un,然后,再按撤消进程时所花费代价的大小,把它插入到由S状态所演变的新状态的队列L中。显然,队列L中的第一个状态U1是由S状态花最小代价撤消一个进程所演变成的状态。在撤消一个进程后,若系统仍处于死锁状态,则再从U1状态按照上述处理方式再依次地撤消一个进程,得到, ,状态,再从状态中选取一个代价最小的,如此下去,直到死锁状态解除为止。为把系统从死锁状态中解脱出来,所花费的代价可表示为: