首页 > 代码库 > 现代操作系统的调度

现代操作系统的调度

一. 操作系统调度的原则


1. 什么是调度
当计算机系统死多道程序设计系统时,通常就会有多个进程或者线程竞争CPU,只要有两个或者更多的进程处于就绪状态,这种情况就会发生,如果只有一个CPU可以用,那么必须选择下一个要运行的进程,在操作系统中,完成选择工作的这一部分被称为调度程序(scheduler)。该程序使用的算法称为调度算法(scheduler algorithm)。
 
几乎所有的进程的I/O请求或者计算都是交替突发的,注意,某些I/O活动可以看做是计算,例如,当CPU向视频RAM复制数据以等待更新屏幕时,因为使用了CPU,所以是计算活动,而不是I/O活动,按照这种观点,当一个进程等待外部设备完成工作而被阻塞时,才是I/O活动。
技术分享
Fig 1 : CPU调度的突发使用和等待I/O的交替出现:a) CPU密集型进程;b)I/O密集型进程
 
2. 何时调度 
  • 在创建一个新进程后,需要决定是运行父进程还是子进程,需要决定是运行父进程还是子进程
  • 一个进程退出后必须进行调度
  • 在一个进程阻塞在I/O和信号量上或者由于其他原因阻塞的时候,必须选择另一个进程开始运行。
  • 在一个I/O发生中断的时候,必须做出调度决策。
    
  • 非抢占式调度:挑选一个进程,然后让该进程运行到被阻塞。
  • 抢占式调度:挑选一个进程让他运行到某个固定时段的最大值。如果在改时间段结束时,该进程仍然在裕兴,它就会被挂起,而调度程序挑选另一个进程运行,如果存在一个就绪进程。进行抢占式调度处理,需要在事件间隔的末端发生时钟中断,以便把CPU控制返回给调度程序,如果没有可用的时钟,那么非抢占式调度就是唯一的选择。
 
3. 调度算法的三个目标
  • 吞吐量 (throughout) : 系统每个小时完成的作业数量。
  • 周转时间 (turnaround time) : 一个批处理作业提交时刻到完成该作业完成时刻为止的统计平均时间。
  • CPU利用率 (CPU Utilization) : 多用于度量批处理系统,反应CPU的使用情况
 
当然我们也可以不完全依靠操作系统的调度进程来对我们的进程进行调度,我们可以手动调整,即调整调度程序的参数。这种方法被称为把调度机制与调度策略分离。
 
二. 批处理系统中的调度

对于批处理系统的调度,需要满足以下几个要求:
  • 吞吐量——每小时最大作业量
  • 周转时间——保持从提交到终止间最小忙碌
  • CPU利用率——始终保持CPU忙碌
 
1. 先来先去服务 (first-come first-served, FCFS)
这是一个非抢占式的调度算法。实现也很简单,就是进程按照它们请求的CPU顺序使用CPU,谁先申请谁就会在队列的前面,后面申请的就不断加入到队尾即可。当一个正在运行的进程发生了阻塞,那么队列中的第一个进程就会继续运行,在阻塞的进程编程就绪时,就像一个新来到的作业一样,排到队列的末尾。
 
 
2. 最短作业优先 (shortest job first)
同样也是一个非抢占式调度算法。实现起来也非常简单,就是把作业的时间从小到大排列,并且按这个顺序进行进程的运行。但要注意,这个算法要当所有的进程都可以同时运行的时候才是最优的。比如有4个作业ABCD(假设度可以同时运行),他们的作业时间分别是1,2,4,8,我们按照ABCD的顺序进行进程的调度即可得最优的平均周转时间。
 
 
注意平均周转时间的计算方法 (k a0 + (k - 1) a1 + (k - 2) a2 ... +)/k,其中a0,a1,a2...分别是第k个作业运行的时间,比如上述例子的最优平均周转时间为 (4*1+3*2 + 2*4 +8)/4 = 6.5 
 
当作业不能同时运行时,最短作业优先算法不一定是最优的,比如现在有5个作业,从A到E,运行时间分别是2,4,1,1和1,他们的到达时间是0,0,3,3和3,开始的时候我们只能选择A或者B运行,因为其他三个作业还没有到达,使用最短作业优先。将按照ABCDE的顺序开始运行,其平均等待时间是6.4,但是按照BCDEA的顺序运行作业,其平均的等待时间则是6。
 
 
3. 最短剩余时间优先 (shortest remaining time next)
这个算法是最短作业优先算法的抢占式的版本,使用这个算法的时候,调度程序总是选择则剩余运行时间最短的那个进程运行。再次提醒,有关的运行时间必须提前掌握。当一个新的作业到达打的时候,其整个时间同当前进程的剩余时间进行比较。其进程的运行时间必须被提前知道,当一个新的作业到达的时候,其整个时间同当前进程的剩余时间作比较。如果较新的进程比当前运行的进程需要更少的时间,当前进程就会被挂起,而运行新的进程。这种方式可以使新的短作业获得良好的服务。
 
 
 
三. 交互式系统中的调度

1. 轮转调度
最古老,最简单,最公平而且使用最广的算法是轮转调度。每个进程被分配一个时间片(quantum),即允许该进程在该时间段中运行,如果时间片结束时该进程还在运行,则剥夺CPU并分配给另外一个进程。如果该进程在时间片结束前阻塞或者结束,则CPU立刻进行切换。如下图所示:
技术分享
 Fig 2:轮转调度:a) 可运行进程列表;b) 进程B用完时间片后的可运行进程列表
时间片长度的设置在轮转调度里面非常重要,因为从一个进程切换到另一个进程是需要一定的时间的——包括保存和装入寄存器的值以及内存映像。更新各种表格和列表,清除和重新调入内存高速缓存等。进程切换有时候也被称为上下文切换。时间片设置得太短会导致过多的进程切换。降低了CPU的效率;而设置得太长又会可能对短的交互请求的响应时间变长。
 
2. 优先级调度
轮转调度隐含的假设是所有的进程都是相同的优先级的,通过动态地或者静态地对进程设立优先级,我们可以实现优先级调度,优先级高的进程优先运行。
 
为了防止高优先级的进程无休止地运行下去,调度程序可以在每个时钟周期结束时降低当前进程的优先级。如果这个动作导致该进程的优先级低于次高优先级的进程。则进行进程切换。一个可采用的方法是,每个进程可以赋予一个允许运行的最大时间片。当这个时间片用完时,下一个次高优先级的优先级进程可以获得机会运行。
 
静态优先级可以通过预先评估进程的重要性来确定。而动态优先级的确定可以按照以下的方法进行处理:
技术分享  
Fig 3:有4个优先级类的调度算法
上图的算法一目了然,就是把所有的进程分为四个优先级组,而在各类进程的内部采用轮转调度。如图3,只要存在优先级为4的进程,则不会理会优先级3,2,1的进程。当优先级4的队列为空后,才开始调度优先级3的进程。以此类推。
 
动态优先级调度要每隔一段时间调整进程的优先级,否则会发生饥饿。同时也要注意锁封护和优先级反转的问题。
 
 
3. 多级队列
多级队列实现的思想和优先级调度的思想类似,但是增加给不同优先级的分配不同的时间片。越低优先级分配的时间片越大,并且当一个进程用完被分配的时间后,会被降低一个优先级。这样可以有效地让短的交互进程让出CPU。而对于那些刚开始运行一段时间,然而后来又需要交互的进程,为了避免被永远地惩罚。某些操作系统提供手动提高优先级的策略(比如按下F键可以提升优先级)。
 
 
4. 最短进程有限
如果我们把每一条执行的指令都看做是一个独立的 “作业”,那么我们就可以利用批处理系统的一个思想 “最短作业常常伴随着最短响应时间”,对进程的过去进行推测,并执行估计运行时间最短的那一个。假设某个终端上每条命令的估计运行时间为T0,现在假设测量到其下一次运行时间为T1。可以用这两个值的加权和来改进估计时间。即aT0 +  (1 - a)T1。通过选择a的值,可以决定是尽快忘记老的运行时间,还是在一段唱的时间内记住他们。当a = 1/2时,可以得到如下序列:
T0T0/2 + T1/2T0/4 + T1/2 + T2/2T0/8 + T1/8 + T2/4 + T3/2
可以看到三轮过后,T0在新的估计值中所占的比重下降到1/8,这样的算法被称为是老化 (aging) 。
 
 
5. 保证调度
保证调度是一个理想的调度,很难被实现。其基本实现思想是系统跟踪每个进程自创建以来已使用了多少CPU时间。然后它计算各个进程应该获得的CPU时间。即自创建以来的时间除以n,由于各个进程实际获得的CPU时间是已知的。所以很容易计算出真正获得的CPU时间和应该获得的CPU的时间之比。比率为0.5说明一个进程只获得了应得时间的一半,而比率为2.0则说明它已经获得了应得时间的两倍。(理想情况是,在一个有n个进程的单用户系统中,若所有的进程都等价。则每个进程将获得1/n的CPU时间)。
 
 
6. 彩票调度
彩票调度的基本思想是向进程提供各种系统资源(如CPU时间)的彩票,一旦需要作出一项调度决策的时候,就随机抽出一张彩票,拥有该彩票的进程获得该资源。在应用到CPU调度时候,系统可以掌握每秒钟50次的一种彩票,作为奖励每个获奖者可以得到20ms的CPU时间。
 
彩票调度可以很简单地实现优先级调度——只要让每个进程的彩票配额不一样就可以了,有更多彩票的进程获得CPU的几率要更大一点。同时进程间也可以进行彩票交换,比如当一个进程请求IO而被阻塞时,它可以把彩票让给另一个进程,让另一个进程获得更大的得到CPU的几率。
 
 
7. 公平分享调度
我们上面所考虑到的调度算法都是针对单用户的。比如用户一有9个进程,而用户2有1个进程,假设我们进行轮转调度,那么用户1可以获得90%的CPU的时间,但是用户2只能获得10%的CPU时间。在多用户的系统必须考虑不同用户的CPU的时间分配。
 
 
 
四. 实时系统中的调度

实时系统是一种时间起着主导作用的系统。最典型的就是多媒体实时系统,当我们在播放CD时,必须在非常短的时间内将流转换成音乐,如果转换时间过长,那么音乐就会产生问题。实时系统通常可以分为硬实时和软实时,前者的含义是必须满足绝对的截止时间,后者的含义是虽然不希望偶尔错失截止时间,但是还可以容忍。
 
实时系统中的时间可以按照响应方式进一步分类为周期性(以规则的时间间隔发生)事件或者非周期(发生时间不可预知)事件。一个系统可能要响应多个周期性的事件流,系统可能无法响应所有事件。实时系统可调度的条件:技术分享(有m个周期事件,事件i以周期Pi发生,并需要Ci秒CPU时间来处理事件),满足这个条件的实时系统被称为可调度的。
 
在实时系统中我们可以尝试设置一个主控时钟,该时钟每秒滴答适当的次数,例如针对NTSC制式的视频,每秒滴答30次,在时钟的每一个触发下,所有的进程都以相同的次序相继运行,当一个进程完成其工作时,它将发出suspend系统调用释放CPU直到主控时钟再次触发。当主控时钟再次响应,所有的进程再次以相同的次序运行。只要进程数较少,所有的工作都可以在一帧的时间内完成。采用轮转调度就可以了。但是模型相当不靠谱,因为不同的进程可能以不同的频率运行,具有不用的工作量,并且具有不同的最终时限。
 
技术分享 
Fig 4:三个周期性的进程,每个进程播放一部电影,每一部电影的帧率以及每帧的处理需求有所不同
 一般实时调度模型:多个进程竞争CPU,每个进程有自己的工作量和最终时限。假设系统知道每个进程必须以什么样的频率运行,有多少工作要做以及下一个最终时限是什么。多个相互竞争的进程,其中若干进程或者全部进程都必须满足的最终时限的调度称为是实时调度。
 
1. 速率单调调度(实时静态算法)
速率单调调度算法(Rate Monotonic Scheduling,RMS)可以用于满足以下条件的进程:
  • 每个周期性进程必须在其周期内完成。
  • 没有进程依赖于任何其他进程。
  • 每一个进程在一次突发中需要相同的CPU时间量。
  • 任何非周期进程都没有最终时限。
  • 进程抢占时刻发生而没有系统开销。(理想模型)
单调速率算法按照以下规则给进程设立优先级:比如A进程每30ms运行一次,则每秒运行33次,则获得优先级33;B进程每秒运行20次,则获得优先级20,所以优先级与速率成线性关系,这就是这个算法的名字的来历。RMS算法是最优的实时静态算法中。
 
但是要注意的是,RMS算法并不是在什么情况下都能正常运行的,下面会有一个RMS算法不能调度的情形。
Liu和Layland证明了在任何周期性进程系统,如果:技术分享则RMS可以正常工作,随着m->无穷,那么最大利用率就会逼近ln2。比如当m=3时,最大允许利用率为0.780,所以在三个周期性进程的系统中,当CPU利用率小于0.780,那么RMS总可以工作的,但是反过来说,如果CPU利用率大于0.780,并不能说明RMS不能工作(对于特定的周期和运行时间可以的调度成功)。    
 
2. 最早最终时限调度(实时动态算法)
最早最终时限优先算法(Earliest Deadline First,EDF)是一个动态算法,它不要求进程是周期性的,也不要求每个CPU突发具有相同的运行时间。只要有一个进程需要CPU时间,它就宣布它的到来和最终时限。调度程序维持一个可运行的进程列表,该列表按最终时限排序,EDF算法运行列表中的第一个进程,也是具有最近最终时限的进程。当一个新的进程就绪时,系统进行检查以了解其最终时限是否发生在当前运行的进程结束之前。如果是这样,那么新的进程就抢占当前正在运行的进程。
 
EDF算法对于任何一组可调度的进程总是可以工作的。
 
技术分享
Fig 5:RMS和EDF调度实例1
在上述例子中,RMS和EDF算法都可以正常调度,对于RMS算法,前80ms的都是很好理解的,因为优先级A>B>C(A的优先级是33,B是25,C是20),所以我们只用把ABC三个进程轮转调度即可。注意A可以抢占B,B可以抢占C,所以在90ms的时候,A和B都处于就绪态,但是A的优先级要比B要高,所以A会抢占B,当A运行完以后,继续运行C。
 
而对于EDF算法,在前90ms中,与RMS算法的处理结果都是一样的,然而在90ms时,A和B选择的最终时限都是一样的,但是EDF算法规定,当当前运行的进程和其他进程的最终时限一样时,不抢占当前运行的进程,防止而外的开销,所以B继续运行,然后B运行完以后调度A。
 技术分享
Fig 6:RMS调度失败的例子
现在我们换一个例子,假设A进程每个周期运行15ms,而不是10ms,这个时候RMS算法就会调度失败了,首先在RMS算法中,ABC的优先级不变,因为RMS算法中的优先级只与进程的运行频率有关,而与进程的运行时间是无关的。在t = 45ms的时候,调度B,而由于C的优先级低于B,所以C无法抢占B而错过其最终时限。RMS算法调度失败。
 
现在我们来看EDF算法,当t = 30时,A,B和C发生竞争,而这个时候A的最终时限是60,B的最终时限是80,而C是50,所以调度算法决定调度C进程,当C进程运行完后其最终时限变为100,所以调度算法调度A,以此类推。当t = 90时,也发生了刚才A和B发生竞争的情况,EDF选择不切换进程,继续运行B。
 
 
五. 线程调度

线程调度主要是分为用户级线程调度和内核级线程调度,他们的区别就是在于内核是否认识到有不同的线程,如下图所示:
技术分享
Fig 7: a)用户级线程的可能调度,有50ms时间片的进程以及每次运行5ms CPU的线程 b) 拥有相同特征的内核级线程的调度
对于用户级线程,CPU给一个进程分配CPU后,由进程在用户空间上控制线程的调度,这种调度不会影响其他进程,当这个进程的时间片用完后,内核会调度其他进程。而对于内核级线程,它不用考虑被调度的线程是属于哪个进程的。这个时候时间片是线程拥有的。
 
用户级线程和内核级线程的性能是有差别的。一般来说用户级线程的切换非常轻松。但是对于内核级线程,线程切换需要保存完整的上下文,修改内存映像,使高速缓存失效等操作。这些操作是费时的。
 
 
 
 
 

现代操作系统的调度