预计阅读本页时间:-
2.4.3 交互式系统中的调度
现在考察用于交互式系统中的一些调度算法,它们在个人计算机、服务器和其他类系统中都是常用的。
1.轮转调度
一种最古老、最简单、最公平且使用最广的算法是轮转调度(round robin)。每个进程被分配一个时间段,称为时间片(quantum),即允许该进程在该时间段中运行。如果在时间片结束时该进程还在运行,则将剥夺CPU并分配给另一个进程。如果该进程在时间片结束前阻塞或结束,则CPU立即进行切换。时间片轮转调度很容易实现,调度程序所要做的就是维护一张可运行进程列表,如图2-41a所示。当一个进程用完它的时间片后,就被移到队列的末尾,如图2-41b所示。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

时间片轮转调度中惟一有趣的一点是时间片的长度。从一个进程切换到另一个进程是需要一定时间进行管理事务处理的——保存和装入寄存器值及内存映像、更新各种表格和列表、清除和重新调入内存高速缓存等。假如进程切换(process switch),有时称为上下文切换(context switch),需要1ms,包括切换内存映像、清除和重新调入高速缓存等。再假设时间片设为4ms。有了这些参数,则CPU在做完4ms有用的工作之后,CPU将花费(即浪费)1ms来进行进程切换。因此,CPU时间的20%浪费在管理开销上。很清楚,这一管理时间太多了。
为了提高CPU的效率,我们可以将时间片设置成,比方说,100ms,这样浪费的时间只有1%。但是,如果在一段非常短的时间间隔内到达50个请求,并且对CPU有不同的需求,那么,考虑一下,在一个服务器系统中会发生什么呢?50个进程会放在可运行进程的列表中。如果CPU是空闲的,第一个进程会立即开始执行,第二个直到100ms以后才会启动,以此类推。假设所有其他进程都用足了它们的时间片的话,最不幸的是最后一个进程在获得运行机会之前将不得不等待5秒钟。大部分用户会认为5秒的响应对于一个短命令来说是缓慢的。如果一些在队列后端附近的请求仅要求几毫秒的CPU时间,上面的情况会变得尤其糟糕。如果使用较短的时间片的话,它们将会获得更好的服务。
另一个因素是,如果时间片设置长于平均的CPU突发时间,那么不会经常发生抢占。相反,在时间片耗费完之前多数进程会完成一个阻塞操作,引起进程的切换。抢占的消失改善了性能,因为进程切换只会发生在确实逻辑上有需要的时候,即进程被阻塞不能够继续运行。
可以归结如下结论:时间片设得太短会导致过多的进程切换,降低了CPU效率;而设得太长又可能引起对短的交互请求的响应时间变长。将时间片设为20ms~50 ms通常是一个比较合理的折中。
2.优先级调度
轮转调度做了一个隐含的假设,即所有的进程同等重要,而拥有和操作多用户计算机系统的人对此常有不同的看法。例如,在一所大学里,等级顺序可能是教务长首先,然后是教授、秘书、后勤人员,最后是学生。这种将外部因素考虑在内的需要就导致了优先级调度。其基本思想很清楚:每个进程被赋予一个优先级,允许优先级最高的可运行进程先运行。
即使在只有一个用户的PC机上,也会有多个进程,其中一些比另一些更重要。例如,与在屏幕上实时显示视频电影的进程相比,在后台发送电子邮件的守护进程应该被赋予较低的优先级。
为了防止高优先级进程无休止地运行下去,调度程序可以在每个时钟滴答(即每个时钟中断)降低当前进程的优先级。如果这个动作导致该进程的优先级低于次高优先级的进程,则进行进程切换。一个可采用的方法是,每个进程可以被赋予一个允许运行的最大时间片,当这个时间片用完时,下一个次高优先级的进程获得机会运行。
优先级可以是静态赋予或动态赋予。在一台军用计算机上,可以把将军所启动的进程设为优先级100,上校为90,少校为80,上尉为70,中尉为60,以此类推。或者,在一个商业计算中心,高优先级作业每小时费用为100美元,中优先级每小时75美元,低优先级每小时50美元。UNIX系统中有一条命令nice,它允许用户为了照顾别人而自愿降低自己进程的优先级。但从未有人用过它。
为达到某种目的,优先级也可以由系统动态确定。例如,有些进程为I/O密集型,其多数时间用来等待I/O结束。当这样的进程需要CPU时,应立即分配给它CPU,以便启动下一个I/O请求,这样就可以在另一个进程计算的同时执行I/O操作。使这类I/O密集型进程长时间等待CPU只会造成它无谓地长时间占用内存。使I/O密集型进程获得较好服务的一种简单算法是,将其优先级设为1/f,f为该进程在上一时间片中所占的部分。一个在其50ms的时间片中只使用1ms的进程将获得优先级50,而在阻塞之前用掉25ms的进程将具有优先级2,而使用掉全部时间片的进程将得到优先级1。
可以很方便地将一组进程按优先级分成若干类,并且在各类之间采用优先级调度,而在各类进程的内部采用轮转调度。图2-42给出了一个有4类优先级的系统,其调度算法如下:只要存在优先级为第4类的可运行进程,就按照轮转法为每个进程运行一个时间片,此时不理会较低优先级的进程。若第4类进程为空,则按照轮转法运行第3类进程。若第4类和第3类均为空,则按轮转法运行第2类进程。如果不偶尔对优先级进行调整,则低优先级进程很可能会产生饥饿现象。

3.多级队列
CTSS(Compatible TimeSharing System),M.I.T.在IBM 7094上开发的兼容分时系统(Corbató等人,1962),是最早使用优先级调度的系统之一。但是在CTSS中存在进程切换速度太慢的问题,其原因是IBM 7094内存中只能放进一个进程,每次切换都需要将当前进程换出到磁盘,并从磁盘上读入一个新进程。CTSS的设计者很快便认识到,为CPU密集型进程设置较长的时间片比频繁地分给它们很短的时间片要更为高效(减少交换次数)。另一方面,如前所述,长时间片的进程又会影响到响应时间,其解决办法是设立优先级类。属于最高优先级类的进程运行一个时间片,属于次高优先级类的进程运行2个时间片,再次一级运行4个时间片,以此类推。当一个进程用完分配的时间片后,它被移到下一类。
作为一个例子,考虑有一个进程需要连续计算100个时间片。它最初被分配1个时间片,然后被换出。下次它将获得2个时间片,接下来分别是4、8、16、32和64。当然最后一次它只使用64个时间片中的37个便可以结束工作。该进程需要7次交换(包括最初的装入),而如果采用纯粹的轮转算法则需要100次交换。而且,随着进程优先级的不断降低,它的运行频度逐渐放慢,从而为短的交互进程让出CPU。
对于那些刚开始运行一段长时间,而后来又需要交互的进程,为了防止其永远处于被惩罚状态,可以采取下面的策略。只要终端上有回车键(Enter键)按下,则属于该终端的所有进程就都被移到最高优先级,这样做的原因是假设此时进程即将需要交互。但可能有一天,一台CPU密集的重载机器上有几个用户偶然发现,只需坐在那里随机地每隔几秒钟敲一下回车键就可以大大提高响应时间。于是他又告诉所有的朋友……这个故事的寓意是:在实践上可行比理论上可行要困难得多。
已经有许多其他算法可用来对进程划分优先级类。例如,在伯克利制造的著名的XDS 940系统中(Lampson,1968),有4个优先级类,分别是终端、I/O、短时间片和长时间片。当一个一直等待终端输入的进程最终被唤醒时,它被转到最高优先级类(终端)。当等待磁盘块数据的一个进程就绪时,将它转到第2类。当进程在时间片用完时仍为就绪时,它一般被放入第3类。但如果一个进程已经多次用完时间片而从未因终端或其他I/O原因阻塞,那么它将被转入最低优先级类。许多其他系统也使用类似的算法,用以讨好交互用户和进程,而不惜牺牲后台进程。
4.最短进程优先
对于批处理系统而言,由于最短作业优先常常伴随着最短响应时间,所以如果能够把它用于交互进程,那将是非常好的。在某种程度上,的确可以做到这一点。交互进程通常遵循下列模式:等待命令、执行命令、等待命令、执行命令,如此不断反复。如果我们将每一条命令的执行看作是一个独立的“作业”,则我们可以通过首先运行最短的作业来使响应时间最短。这里惟一的问题是如何从当前可运行进程中找出最短的那一个进程。
一种办法是根据进程过去的行为进行推测,并执行估计运行时间最短的那一个。假设某个终端上每条命令的估计运行时间为T0 。现在假设测量到其下一次运行时间为T1 。可以用这两个值的加权和来改进估计时间,即aT0 +(1-a)T1 。通过选择a的值,可以决定是尽快忘掉老的运行时间,还是在一段长时间内始终记住它们。当a=1/2时,可以得到如下序列:
T0 ,T0 /2+T1 /2,T0 /4+T1 /4+T2 /2,T0 /8+T1 /8+T2 /4+T3 /2
可以看到,在三轮过后,T0 在新的估计值中所占的比重下降到1/8。
有时把这种通过当前测量值和先前估计值进行加权平均而得到下一个估计值的技术称作老化(aging)。它适用于许多预测值必须基于先前值的情况。老化算法在a=1/2时特别容易实现,只需将新值加到当前估计值上,然后除以2(即右移一位)。
5.保证调度
一种完全不同的调度算法是向用户作出明确的性能保证,然后去实现它。一种很实际并很容易实现的保证是:若用户工作时有n个用户登录,则用户将获得CPU处理能力的1/n。类似地,在一个有n个进程运行的单用户系统中,若所有的进程都等价,则每个进程将获得1/n的CPU时间。看上去足够公平了。
为了实现所做的保证,系统必须跟踪各个进程自创建以来已使用了多少CPU时间。然后它计算各个进程应获得的CPU时间,即自创建以来的时间除以n。由于各个进程实际获得的CPU时间是已知的,所以很容易计算出真正获得的CPU时间和应获得的CPU时间之比。比率为0.5说明一个进程只获得了应得时间的一半,而比率为2.0则说明它获得了应得时间的2倍。于是该算法随后转向比率最低的进程,直到该进程的比率超过它的最接近竞争者为止。
6.彩票调度
给用户一个保证,然后兑现之,这是个好想法,不过很难实现。但是,有一个既可给出类似预测结果而又有非常简单的实现方法的算法,这个算法称为彩票调度(lottery scheduling)(Waldspurger和Weihl,1994)。
其基本思想是向进程提供各种系统资源(如CPU时间)的彩票。一旦需要做出一项调度决策时,就随机抽出一张彩票,拥有该彩票的进程获得该资源。在应用到CPU调度时,系统可以掌握每秒钟50次的一种彩票,作为奖励每个获奖者可以得到20ms的CPU时间。
为了说明George Orwell关于“所有进程是平等的,但是某些进程更平等一些”的含义,可以给更重要的进程额外的彩票,以便增加它们获胜的机会。如果出售了100张彩票,而有一个进程持有其中的20张,那么在每一次抽奖中该进程就有20%的取胜机会。在较长的运行中,该进程会得到20%的CPU。相反,对于优先级调度程序,很难说明拥有优先级40究竟是什么意思,而这里的规则很清楚:拥有彩票f份额的进程大约得到系统资源的f份额。
彩票调度具有若干有趣的性质。例如,如果有一个新的进程出现并得到一些彩票,那么在下一次的抽奖中,该进程会有同它持有彩票数量成比例的机会赢得奖励。换句话说,彩票调度是反应迅速的。
如果希望协作进程可以交换它们的彩票。例如,有一个客户进程向服务器进程发送消息后就被阻塞,该客户进程可以把它所有的彩票交给服务器,以便增加该服务器下次运行的机会。在服务器运行完成之后,该服务器再把彩票还给客户机,这样客户机又可以运行了。事实上,如果没有客户机,服务器根本就不需要彩票。
彩票调度可以用来解决用其他方法很难解决的问题。一个例子是,有一个视频服务器,在该视频服务器上若干进程正在向其客户提供视频流,每个视频流的帧速率都不相同。假设这些进程需要的帧速率分别是10、20和25帧/秒。如果给这些进程分别分配10、20和25张彩票,那么它们会自动地按照大致正确的比例(即10∶20∶25)划分CPU的使用。
7.公平分享调度
到现在为止,我们假设被调度的都是各个进程自身,并不关注其所有者是谁。这样做的结果是,如果用户1启动9个进程而用户2启动1个进程,使用轮转或相同优先级调度算法,那么用户1将得到90%的CPU时间,而用户2只得到10%的CPU时间。
为了避免这种情形,某些系统在调度处理之前考虑谁拥有进程这一因素。在这种模式中,每个用户分配到CPU时间的一部分,而调度程序以一种强制的方式选择进程。这样,如果两个用户都得到获得50%CPU时间的保证,那么无论一个用户有多少进程存在,每个用户都会得到应有的CPU份额。
作为一个例子,考虑有两个用户的一个系统,每个用户都保证获得50%CPU时间。用户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两倍的CPU时间,我们会有
A B E C D E A B E C D E…
当然,大量其他的可能也存在,可以进一步探讨,这取决于如何定义公平的含义。