操作系统(3)进程调度与死锁
一、处理机调度的层次
在多道程序环境下,主存中有着多个进程,其数目往往多于处理机数目。这就要求系统能按某种算法,动态地把处理机分配给就绪队列中的一个进程,使之执行。分配处理机的任务是由处理机调度程序完成的。由于处理机是最重要的计算机资源,提高处理机的利用率及改善系统性能(吞吐量、响应时间),在很大程度上取决于处理机调度性能的好坏,因而,处理机调度便成为操作系统设计的中心问题之一。
在多道程序系统中,一个作业被提交后必须经过处理机调度后,方能获得处理机执行。对于批量型作业而言,通常需要经历作业调度(又称高级调度或长程调度)和进程调度(又称低级调度或短程调度)两个过程后方能获得处理机;对于终端型作业,则通常只需经过进程调度即可获得处理机。在较完善的操作系统中,为提高内存的利用率,往往还设置了中级调度(又称中程调度)。对于上述的每一级调度,又都可采用不同的调度方式和调度算法。对于一个批处理型作业,从进入系统并驻留在外存的后备队列开始,直至作业运行完毕,可能要经历上述的三级调度。
(1)高级调度
高级调度又称为长程调度或作业调度,它的调度对象是作业。其主要功能是根据某种算法,决定对外存上处于后备队列中的哪几个作业调入内存,为它们创建进程、分配必要的资源,并将它们放入就绪队列。高级调度主要用于多道批处理系统中,而在分时和实时系统中不设置高级调度。
(2)低级调度
低级调度又称为进程调度或短程调度,其所调度的对象是进程(或内核级线程)。其主要功能是,根据某种算法,决定就绪队列中的哪个进程应获得处理机,并由分派程序将处理机分配给被选中的进程。进程调度是最基本的一种调度,在多道批处理、分时和实时三种类型的 OS 中,都必须设置这种调度。
(3)中级调度
中级调度又称为内存调度。引入中级调度的主要目的是为了提高内存利用率和系统吞吐量。为此,应使那些暂时不能运行的进程不再占用宝贵的内存资源,而将它们调至外存上去等待,把此时的进程状态称为就绪驻外存状态或挂起状态。当这些进程重又具备运行条件且内存又稍有空闲时,由中级调度来决定把外存上的那些又具备运行条件的就绪进程重新调入内存,并修改其状态为就绪状态,挂在就绪队列上等待进程调度。中级调度实际上就是存储器管理中的对换功能。
在上述三种调度中,进程调度的运行频率最高,在分时系统中通常是 10~100 ms 便进行一次进程调度,因此把它称为短程调度。为避免进程调度占用太多的 CPU 时间,进程调度算法不宜太复杂。作业调度往往是发生在一个(批)作业运行完毕,退出系统,而需要重新调入一个(批)作业进入内存时,故作业调度的周期较长,大约几分钟一次,因此把它称为长程调度。由于其运行频率较低,故允许作业调度算法花费较多的时间。中级调度的运行频率基本上介于上述两种调度之间,因此把它称为中程调度。
二、进程调度
1.进程调度的任务
(1)保存处理机的现场信息。在进行调度时首先需要保存当前进程的处理机的现场信息,如程序计数器、多个通用寄存器中的内容等。
(2)按某种算法选取进程。调度程序按某种算法从就绪队列中选取一个进程,将其状态改为运行状态,并准备把处理机分配给它。
(3)把处理机分配给进程。由分派程序把处理器分配给该进程,此时需要将选中进程的进程控制块内有关处理机现场的信息装入处理机相应的各个寄存器中,把处理器的控制权交予该进程,让它从上次的断点处恢复运行。
2.进程调度进制
为了实现进程调度,在进程调度机制中,应具有如下三个部分:
- 排队器。为了提高进程调度的效率,应事先将系统中所有的就绪进程按照一定的方式排成一个或多个队列,以便调度程序能最快地找到它。以后每当有一个进程转变为就绪状态时,排队器便将它插入到相应的就绪队列中。
- 分派器(分派程序)。分派器依据进程调度程序所选定的进程,将其从就绪队列中取出,然后进行从分派器到新选出进程间的上下文切换,将处理机分配给新选出的进程。
- 上下文切换器。当对处理机进行切换时,会发生两对上下文切换操作。①在第一对上下文切换时,操作系统将保存当前进程的上下文,即把当前进程的处理机寄存器内容保存到该进程的进程控制块内的相应单元,再装入分派程序的上下文,以便分派程序运行;②在第二对上下文切换时,将移出分派程序的上下文,而把新选进程的 CPU 现场信息装入到处理机的各个相应寄存器中。以便新选进程运行。
在进行上下文切换时,需要执行大量的 load 和 store 等操作指令,以保存集群器的内容。即使是现代计算机,每一次上下文切换大约需要花费几毫秒的时间,该时间大约可执行上千条指令。为此,现在已有通过硬件(采用两组或多组寄存器)的方法来减少上下文切换的时间。一组寄存器供处理机在系统态时使用,另一组寄存器供应用程序使用。在这种条件下的上下文切换只需改变指针,使其指向当前寄存器组即可。
3.进程调度方式
(1)非抢占方式
在采用这种调度方式时,一旦把处理机分配给某进程后,不管它要运行多长时间,都一直让它运行下去,决不会因为时钟中断等原因而抢占正在运行进程的处理机,也不允许
其它进程抢占已经分配给它的处理机。直至该进程完成,自愿释放处理机,或发生某事件而被阻塞时,才再把处理机分配给其他进程。
在采用非抢占调度方式时,可能引起进程调度的因素可归结为如下几个:
- 正在执行的进程执行完毕,或因发生某事件而不能再继续执行;
- 执行中的进程因提出 I/O 请求而暂停执行;
- 在进程通信或同步过程中执行了某种原语操作,如 P 操作(wait 操作)、Block 原语、Wakeup 原语等。
这种调度方式的优点是实现简单,系统开销小,适用于大多数的批处理系统环境。但它难以满足紧急任务的要求——立即执行,因而可能造成难以预料的后果。
显然,在要求比较严格的实时系统中,不宜采用这种调度方式。
(2)抢占方式
这种调度方式允许调度程序根据某种原则去暂停某个正在执行的进程,将已分配给该进程的处理机重新分配给另一进程。抢占方式的优点是,可以防止一个长进程长时间占用处理机,能为大多数进程提供更公平的服务,特别是能满足对响应时间有着较严格要求的实时任务的需求。但抢占方式比非抢占方式调度所需付出的开销较大。
抢占调度方式是基于一定原则的,主要有如下几条:
- 优先权原则。通常是对一些重要的和紧急的作业赋予较高的优先权。当这种作业到达时,如果其优先权比正在执行进程的优先权高,便停止正在执行(当前)的进程,将处理机分配给优先权高的新到的进程,使之执行;或者说,允许优先权高的新到进程抢占当前进程的处理机。
- 短作业(进程)优先原则。当新到达的作业(进程)比正在执行的作业(进程)明显的短时,将暂停当前长作业(进程)的执行,将处理机分配给新到的短作业(进程),使之优先执行;或者说,短作业(进程)可以抢占当前较长作业(进程)的处理机。
- 时间片原则。各进程按时间片轮流运行,当一个时间片用完后,便停止该进程的执行而重新进行调度。这种原则适用于分时系统、大多数的实时系统,以及要求较高的批处理系统。
三、进程调度算法
1.基于时间片的轮转调度算法
在分时系统中,最简单也是较常用的是基于时间片的轮转(Round Robin,RR)调度算法。该算法采取了非常公平的处理机分配方式,即让就绪队列上的每个进程每次仅运行一个时间片。如果就绪队列是上有 n 个进程,则每个进程每次大约都可获得 1/n 的处理机时间。
(1)轮转法的基本原理
在轮转算法中,系统根据 FCFS(先来先服务)策略,将所有的就绪进程排成一个就绪队列,并可设置每隔一定的时间间隔(如30ms)即产生一次中断,激活系统进程中的进程调度程序,完成一次调度,将 CPU 分配给队首进程,令其执行。当该进程的时间片耗尽或者运行完毕时,系统再次将 CPU 分配给新的队首队列(或新到达的紧迫进程)。由此,可保证就绪队列中的所有进程在一个确定的时间段内,都能够获得一次 CPU 执行。
(2)进程切换时机
在 RR 调度算法中,应在何时进行进程的切换,可分为两种情况:① 若一个时间片尚未用完,正在运行的进程便已完成,就立即激活调度程序,将它从就绪队列中删除,再调度就绪队列中队首的进程运行,并启动一个新的时间片。② 在一个时间片用完时,计时器中断处理程序被激活。如果进程尚未运行完毕,调度程序将它送往就绪队列的末尾。
(3)时间片大小的确定
在轮转算法中,时间片的大小对系统性能有很大的影响。若选择很小的时间片,将有利于短作业,因为它能在该事件片内完成。但时间片小,意味着会频繁地执行进程调度和进程上下文的切换,这无疑会增加系统的开销。反之,若时间片选择得太长,且为使每个进程都能在一个时间片内完成,RR 算法便退化为 FCFS 算法,无法满足短作业和交互用户的需求。一个较为可取的时间片大小是略大于一次典型的交互所需要的时间,使大多数交互式进程能在一个时间片内完成,从而可以获得很小的响应时间。
2.优先级调度算法
在时间片轮转调度算法中,假设了系统中所有进程的紧迫性是相同的。但实际情况并非如此,为了能满足实际情况的需要,在进程调度算法中引入了优先级,而形成优先级调度算法。
(1)优先级调度算法的类型
优先级进程调度算法,是把处理机分配给就绪队列中优先级最高的进程。这时,又可进一步把该算法分成两种:
- 非抢占式优先级调度算法。即一旦把处理机分配给就绪队列中优先级最高的进程后,该进程便一直执行下去直至完成,或者因该进程发生某事件而放弃处理机时,系统方可将处理机重新分配给另一优先级最高的进程。
- 抢占式优先级调度算法。即把处理机分配给优先级最高的进程,使之执行。但在其执行期间,只要出现了另一个其优先级更高的进程,调度程序就将处理机分配给新到的优先级最高的进程。因此,在采用这种调度算法时,每当系统中出现一个新的就绪进程 i 时,就将其优先级 Pi 与正在执行的进程 j 的优先级进行比较,如果 Pi ≤ Pj,原进程 Pj 便继续执行;但如果是 Pi > Pj,则立即停止 Pj 的执行,进程进程切换,是 i 进程投入执行。抢占式的优先级调度算法常用于对实时性要求较高的系统中。
(2)优先级的类型
优先级调度算法的关键在于,应如何确定进程的优先级,以及确定是使用静态优先级还是动态优先级。
- 静态优先级。静态优先级是在创建进程时确定的,在进程的整个运行期间保持不变。优先级是利用某一范围内的一个整数来表示的,例如 0~255 中的某一整数,又把该整数称为优先数。确定进程优先级大小的依据有三个:① 进程类型。通常系统进程(如接受进程、对换进程)的优先级高于一般用户进程的优先级。② 进程对资源的需求。对资源要求少的进程应赋予较高的优先级。③ 用户要求。根据进程的紧迫程度及用户所付费用的多少确定优先级。静态优先级简单易行,系统开销小,但不够精确,可能会出现优先级低的进程长期没有被调度的情况。
- 动态优先级。动态优先级是指在创建进程之初,先赋予其一个优先级,然后其值随进程的推进或者等待时间的增加而改变,以便获得更好地调度性能。例如,可以规定在就绪队列中的进程随着等待时间的增长,使其优先级相应提高。若所有的进程都具有相同优先级初值,则最先进入就绪队列的进程会因其优先级变得最高,而优先获得处理机,这相当于 FCFS 算法。若所有的就绪进程具有各不相同的优先级初值,那么对于优先级初始值低的进程,在等待了足够的的时间后,也可以获得处理机。当采用抢占式调度方式时,若再规定当前进程的优先级随时间的推移而下降,则可防止一个长作业长期地垄断该处理机。
3.多队列调度算法
如前所述的各种调度算法,尤其在应用于进程调度时,由于系统中仅设置一个进程的就绪队列,即低级调度算法是固定的、单一的,无法满足系统中不同用户对进程调度策略的不同要求,在多处理机系统中,这种单一调度策略实现机制的缺点更为突出,由此吗,多级队列调度算法能够在一定程度上弥补这一缺点。
该算法将系统中的进程就绪队列从一个拆分为若干个,将不同类型或性质的进程固定分配在不同的就绪队列,不同的就绪队列采用不同的调度算法,一个就绪队列中的进程可以设置不同的优先级,不同的就绪队列本身也可以设置不同的优先级。
多队列调度算法由于设置多个就绪队列,因此对每个就绪队列就可以实施不同的调度算法,因此,系统针对不同用户进程的需求,很容易提供多种调度策略。
在多处理机系统中,该算法由于安排了多个就绪队列,因此,很方便为每个处理机设置一个单独的就绪队列。这样,不仅对每个处理机的调度可以实施各自不同的调度策略,而且对于一个含有多个线程的进程而言,可以根据其要求将其所有线程分配在一个就绪队列,全部在一个处理机上运行。在这,对于一组需要相互合作的进程或线程而言,也可以将它们分配到一组处理机所对应的多个就绪队列,使得它们能同时获得处理机并行执行。
4.多级反馈队列调度算法
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。
(1)调度机制
多级反馈队列调度算法的调度机制如下:
- 设置多个就绪队列。在系统中设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第 i+1 个队列的时间片要比第 i 个队列的时间片长一倍。
- 每个队列都采用 FCFS 算法。当一个新进程进入内存后,首先将它放入第一队列的末尾,按 FCFS 原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按 FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第 n 队列后,在第 n 队列中便采取按时间片轮转调度(RR)的方式运行。
- 按队列优先级调度。调度程序首先调度最高优先级队列中的诸进程运行,仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第 1~(i-1)队列均空时,才会调度第 i 队列中的进程运行。如果处理机正在第 i 队列中为某进程服务时,又有新进程进入优先权较高的队列(第 1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第 i 队列的末尾,把处理机分配给新到的高优先权进程。
(2)调度算法的性能
在多级反馈队列调度算法中,如果规定第一个队列的时间片略大于多数人机交互所需的处理时间时,该算法就具有较好的性能,能很好地满足各种类型用户的需要。
- 终端型作业用户。由于终端型作业用户所提交的作业大多属于交互型作业,作业通常较小,系统只要能使这些作业(进程)在第一队列所规定的时间片内完成,便可使终端型作业用户都感到满意。
- 短批处理作业用户。对于很短的批处理型作业,开始时像终端型作业一样,如果仅在第一队列中执行一个时间片即可完成,便可获得与终端型作业一样的响应时间。对于稍长的作业,通常也只需在第二队列和第三队列各执行一个时间片即可完成,其周转时间仍然较短。
- 长批处理作业用户。对于长作业,它将依次在第 1,2,…,n 个队列中运行,然后再按轮转方式运行,用户不必担心其作业长期得不到处理。
5.基于公平原则的调度算法
以上介绍的几种调度算法所保证的只是优先运行,如优先级算法是优先级最高的作业优先运行,但并不保证作业占用了多少处理机时间。另外也为考虑到调度的公平性。
相对公平的调度算法有两种:
(1)保证调度算法
保证调度算法是另外一种类型的调度算法,它向用户所做出的保证并不是优先运行,而是明确的性能保证,该算法可以做到调度的公平性。一种比较容易实现的性能保证是处理机分配的公平性。如果在系统中有 n 个相同类型的进程同时运行,为公平起见,须保证每个进程都获得相同的处理机时间 1/n。在实施公平调度算法时系统必须具有下面的功能:
- 跟踪计算每个进程自创建以来已经执行的处理时间
- 计算每个进程应获得的处理机时间,即自创建以来的时间除以 n。
- 计算进程获得处理机时间的比率,即进程实际执行的处理时间和应获得的处理机时间之比
- 比较各进程获得处理机时间的比率。如进程 A 的比率最低,为 0.5,而进程 B 的比率为 0.8,进程 C 的比率为 1.2 等。
- 调度程序应选择比率最小的进程将处理机分配给它,并让该进程一致运行,知道超过最接近它的进程比率为止。
(2)公平分享调度算法
分配给每个进程相同的处理机时间,显然,这对诸进程而言,是体现了一定程序的公平,但如果各个用户所拥有的进程数不同,就会发生对用户的不公平问题。加入系统中仅有两个用户,用户 1 启动了 4 个进程,用户 2 只启动了 1 个进程,采用轮转法让每个进程轮流运行一个时间片时间,对进程而言很公平,但用户 1 和用户 2 得到的处理机时间分别为 80% 和 20%,显然对用户 2 而言就有失公平。在该调度算法中,调度的公平性主要是针对用户而言,使所有用户能获得相同的处理机时间,或所要求的时间比例。然而调度又是以进程为基本单位,为此,必须考虑到每一个用户所拥有的进程数目。
例如,系统中有两个用户,用户 1 有 4 个进程 A、B、C、D,用户 2 只有 1 个进程 E。为保证两个用户能获得相同的处理机时间,则必须执行如下所示的强制调度序列:
A E B E C E D E A E B E C E D E ...
如果希望用户 1 所获得的处理机时间是用户 2 的两倍,则必须执行如下所示的强制调度序列:A B E C D E A B E C D E A B E C D E ...
四、死锁
1.死锁的定义
在一组进程发生死锁的情况下,这组死锁进程中的每一个进程,都在等待另一个死锁进程所占有的资源。或者说每个进程所等待的事件是该组中其他进程释放所占有的资源。但由于所有这些进程都已无法运行,因此它们谁也不能释放资源,致使没有任何一个进程可被唤醒。这样这组进程只能无限期地等待下去。
定义:如果一组进程中的每一个进程都在等待仅由该组进程中的其他进程才能引发的事件,那么该组进程是死锁的。
2.产生死锁的必要条件
虽然进程在运行过程中可能会发生死锁,但产生死锁是必须具备一定四个必要条件,只要其中任何一个条件不成立,死锁就不会发生。
(1)互斥条件
进程对所分配到的资源进行排它性使用,即在一段时间内,某资源只能被一个进程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的进程用毕并释放。
(2)请求和保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已经被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(3)不可抢占条件
进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放。
(4)循环等待条件
在发生死锁时,必然存在一个进程--资源的循环链,即进程集合{P0,P1,P2...Pn}中的P0正在等待一个P1占用的资源,P1正在等待P2占用的资源,...Pn正在等待已被P0占用的资源。
3.处理死锁的方法
(1)预防死锁
这是一种较简单和直观的事先预防方法。该方法是通过设置某些限制条件,去破坏产生死锁四个必要条件中的一个或几个来预防产生死锁。
(2)避免死锁
同样是属于事先预防策略,但它并不是事先采取各种限制措施,去破坏产生死锁的四个必要条件,而是在资源的动态分配过程中,用某种方法防止系统进入不安全状态。
(3)检测死锁
这种方法无须事先采取任何限制性措施,允许进程在运行过程中发生死锁。但可通过检测机构及时地检测出死锁的发生,然后采取适当的措施,把进程从死锁中解脱出来。
(4)解除死锁
当检测到系统已发生死锁时,就采取相应措施,将进程从死锁状态中解脱出来。常用的方法时撤销一些进程,回收它们的资源,将它们分配给已处于阻塞状态的进程,使其能继续运行。