规则3:当工作进入系统时,它被放置在最高优先级(最顶层队列)。规则4a:如果一个作业在运行时占用了整个时间片,那么它的优先级就会降低(即:,它向下移动一个队列)规则4b:如果一个工作在时间片结束之前放弃了CPU,它将保持在相同的优先级。
示例1:单个长时间运行的作业。
让我们来看一些例子。首先,我们将看看在系统中有长期运行的工作时会发生什么。图8.2显示了在三队列调度器中超时工作的情况。正如您在示例中看到的,作业以最高优先级(Q2)进入。在单个时间片10毫秒之后,调度器将工作的优先级降低1,因此任务是在Q1。在运行了一个时间片的Q1之后,工作最终被降低到系统(Q0)的最低优先级,在这个系统中它仍然存在。很简单,没有?
例2:随之而来的是一份很短的工作。
现在让我们来看一个更复杂的例子,并希望看到MLFQ如何尝试接近SJF。在本例中,有两个作业:A,这是一个长期运行的cpu密集型作业,而B是一个短期运行的交互式作业。假设A已经运行了一段时间,然后B到达。会发生什么呢?B的MLFQ近似SJF吗?图8.3绘制了该场景的结果。A(以黑色表示)在最低优先级队列中运行(就像任何长时间运行的cpu密集型作业一样)。B(以灰色显示)在T = 100时到达,因此是插入到最高队列中;由于它的运行时间很短(只有20毫秒),B在到达底部队列之前完成,在两个时间段内完成;然后继续运行(低优先级A)。
从这个示例中,您可以理解该算法的主要目标之一。因为它不知道一份工作是一份短的工作还是一份长时间的工作,它首先假定它可能是一份短期的工作,因此给予工作高度优先。如果它实际上是一份短的工作,它将运行得很快并且完成。如果它不是一个短的工作,它将缓慢地向下移动队列,从而很快证明自己是一个长时间运行的类似批处理的过程。这样,MLFQ近似于SJF。
例3:I/O呢?
现在让我们来看一个带有一些I/O的例子。根据上面的规则4b状态,如果一个进程在使用它的时间片之前放弃了处理器,我们将它保持在相同的优先级。这个规则的意图很简单:例如,如果一个交互式作业正在做很多I/O(比如等待键盘或鼠标的用户输入),它将在它的时间片完成之前放弃CPU;在这种情况下,我们不希望对工作进行惩罚,从而使其保持在同一水平。图8.4显示了这个工作的一个示例,其中有一个交互式作业B(以灰色显示),在执行一个长时间运行的批处理作业a(以黑色表示)之前,需要CPU的CPU仅为1毫秒。
我们当前的MLFQ问题。因此,我们有一个基本的MLFQ。它看起来做的很好,在长时间运行的工作之间共享CPU,并且允许短时间或I/ o密集型的交互式作业快速运行。
8.3尝试#2:优先级提升。
让我们试着改变规则,看看我们是否能避免饥饿的问题。我们能做些什么来保证cpu密集型的工作能够取得一些进展(即使这并不多?)这里的简单想法是周期性地提高系统中所有工作的优先级。有很多方法可以实现这一点,但是我们只做一些简单的事情:将它们全部放入最顶层的队列中;因此,一个新规则:Rule 5,在一段时间之后,将系统中的所有作业移到最顶层队列。
我们的新规则立刻解决了两个问题:首先,保证进程不会饿死。坐在最上面的队列中。工作:与其他高优先级的工作以循环式的方式共享CPU,从而最终得到服务。第二,如果一个cpu绑定的工作已经变成了交互式的,那么当它得到优先级提升后,调度器就会正确地处理它。让我们来看一个例子。在这个场景中,我们只展示了一个长时间运行的作业的行为,它与两个短期运行的交互式作业竞争CPU。图8.5(第6页)中显示了两个图。在左边,没有优先级提升,因此长时间运行的作业在两个短作业到达时就会被饿死;在右边,有一个优先级提高每50毫秒(这是可能的值太小,但这里使用的例子),因此我们至少保证长时间运行的工作取得了一些进展。每50毫秒被提升到最高优先级,从而定期运行。当然,时间周期的增加导致了一个明显的问题:应该设置什么。John Ousterhout,一个备受尊敬的系统研究人员(O11),过去常把这样的值称为voo-doo常量,因为它们似乎需要某种形式的黑魔法来正确地设置它们。不幸的是,S有这种味道。如果设定的太高,长时间的工作可能会饿死;太低,交互作业可能得不到适当的CPU份额。
:# 3:尝试更好的会计
我们现在还有一个问题要解决:如何防止我们的调度程序的游戏?正如您可能已经猜到的,这里真正的罪魁祸首是规则4a和4b,它允许工作在时间片到期之前放弃CPU,从而保持优先级。我们应该怎么做。这里的解决方案是在MLFQ的每个级别上更好地计算CPU时间。调度程序应该保持跟踪,而不是忘记在给定的级别上使用多少时间片。一旦进程使用了它的分配,它将被降级到下一个优先队列。不论,它在一个长时间内使用时间片或许多小的片段并不重要。
因此,我们将规则4a和4b重写为以下单一规则:规则4:一旦工作占用了给定级别的时间分配(不管它放弃了多少次CPU),它的优先级就降低了(即:它向下移动一个队列。让我们来看一个例子。图8.6(第7页)显示了当一个工作负载试图用旧的规则4a和4b(在左边)和新的反游戏规则4进行游戏时发生了什么。没有任何游戏的保护,一个进程可以在一个时间片结束前发出I/O,从而控制CPU时间。有了这样的保护,无论进程的I/O行为如何,它都会缓慢地向下移动队列,从而不能获得CPU的不公平份额。
8.5优化MLFQ等问题
其他一些问题也出现在MLFQ调度中。一个大问题是如何参数化这样的调度器。例如,应该有多少个队列。每个队列的时间片应该有多大?为了避免饥饿和解释行为的变化,应该优先提高优先级吗?对于这些问题没有简单的答案,因此只有对工作负载和调度器的后续调优的一些经验将导致一个令人满意的平衡。例如,大多数MLFQ变体允许在不同的队列中使用不同的时间片长度。高优先级队列通常是短时间片;毕竟,它们是由交互式作业组成的,因此它们之间的快速交互是有意义的(例如,10毫秒或更少的毫秒)。与此相反,低优先级队列包含的长时间运行的作业是cpu绑定的;因此,较长的时间片工作得很好(例如,100s)。在可能的情况下,避免使用voo-doo常量是一个好主意。不幸的是,就像上面的例子一样,这通常是困难的。人们可以尝试让系统学习一个好的价值,但这也不是简单的。常见的结果是:配置文件中充满了默认的参数值,经验丰富的管理员可以在不正确工作的情况下进行调整。正如您可以想象的那样,这些通常都是未修改的,因此我们希望这些缺省值在字段中运行良好。这个技巧是我们的老教授John Ousterhout带给你的,因此我们称之为Ousterhout s定律。Solaris MLFQ实现时间共享调度类,或者TS特别容易配置;它提供了一组表,确定过程的优先级如何在其整个生命周期中被改变,每个时间片是多长,以及如何经常提高工作的优先级[AD00]。管理员可以对这个表进行处理,以使调度程序以不同的方式运行。表的默认值是60个队列,从20毫秒(最高优先级)到几百毫秒(最低级)的时间片长度逐渐增加,每1秒左右就会增加优先级。其他MLFQ调度器不使用表或本章描述的精确规则;相反,他们使用数学公式来调整优先级。例如,FreeBSD调度器(版本4.3)使用一个公式来计算工作的当前优先级,基于该进程使用了多少CPU (LM+89);此外,随着时间的推移,使用会逐渐衰退,以不同的方式提供所需的优先级提升。请参阅Epema的论文,以获得对这种decay使用算法及其属性的优秀概述[E95]。
最后,许多调度器还有一些您可能会遇到的其他特性。例如,一些调度器保留了操作系统工作的最高优先级;因此,典型的用户作业永远无法获得系统中最高的优先级。一些系统还允许一些用户建议来帮助设置优先级;例如,通过使用命令行实用工具,您可以增加或减少工作的优先级(某种程度上),从而增加或减少在任何给定时间运行的机会。请参阅手册页以获取更多信息。由于操作系统很少知道系统的每个进程的最佳状态,因此提供接口以允许用户或管理员向操作系统提供一些提示是非常有用的。我们经常把这样的建议称为建议,因为操作系统不一定要注意它,而是要考虑到建议,以便做出更好的决定。这些提示在操作系统的许多部分都很有用,包括调度器(例如,有nice)、内存管理器(如madvise)和文件系统(例如,通知预取和缓存[P+95])。MLFQ的有趣之处在于:它不是要求对工作性质的先验知识,而是观察工作的执行,并相应地优先考虑它。通过这种方式,它成功地实现了两个方面的优点:它可以提供出色的整体性能(类似于SJF/STCF),用于短期的交互式作业,并且是公平的,并为长期cpu密集型工作负载提供了进展。由于这个原因,许多系统,包括BSD UNIX衍生工具[LM+89, B86], Solaris [M06],以及Windows NT和后续的Windows操作系统[CS97]都采用了MLFQ的形式。