CPU调度
引入了线程,对于支持它们的操作系统,是内核级的线程被操作系统调度,而不是进程。不过,术语线程调度或进程调度常常被交替使用。在讨论普通调度概念时使用进程调度,特别指定为线程概念时使用线程调度。
基本概念
CPU-I/O区间周期
CPU的成功调度依赖于进程的如下属性:进程执行由CPU执行和I/O等待周期组成。进程在这两个状态之间切换。进程执行从CPU区间(CPU burst)开始,在这之后是I/O区间(I/O burst),接着是另一个CPU区间,然后是另一个I/O区间,如此进行下去。
CPU调度程序
每当CPU空闲时,操作就必须从就绪队列中选择一个进程来执行。进程选择由短期调度程序(short-term scheduler)或CPU调度程序执行。调度程序从内核中选择一个能够执行的进程,并为之分配CPU。就绪队列不必是先进先出(FIFO)队列。就绪队列可实现为FIFO队列,优先队列,树或简单的无序链表。不过从概念上来说,就绪队列内的所有进程都要排队以等待在CPU上运行。队列中的记录通常为进程控制块(PCB)。
抢占调度
CPU调度决策可在如下4种环境下发生:
- 当一个进程从运行状态切换到等待状态(例如,I/O请求,或调用wait等待一个子进程的终止)。
-
当一个进程从运行状态切换到就绪状态(例如,当出现中断时)
-
当一个进程从等待状态切换到就绪状态(例如,I/O完成)
-
当一个进程终止
对于第1和第4两种情况,没有选择而只有调度。一个新进程(如果就绪队列中已有一个进程存在)必须被选择执行。不过,对于第2和第3两种情况,可以进行选择。
当调度只能发生在第1和第4两种情况下时,称调度方案是非抢占的(nonpreemptive)的或协作的(cooperative);否则,称调度方案是抢占的(preemptive)。采用非抢占调度,一旦CPU分配给一个进程,那么该进程会一直使用CPU知道进程终止或切换到等待状态。
中断能随时发生,而且不能总是被内核所忽视,所以受中断影响的代码段必须加以保护以避免同时访问。为了这些代码段不被多个进程同时访问,在进入时要禁止中断,而在退出时要重新允许中断。
分派程序
与CPU调度功能有关的另一个部分是分派程序(dispatcher)。分派程序是一个模块,用来将CPU的控制交给由短期调度程序选择的进程。其功能包括:
-
切换上下文
-
切换到用户模式
-
跳转到用户程序的合适位置,以重新启动程序
分派程序应尽可能快,因为在每次进程切换时都要使用。分派程序停止一个进程而启动另一个所要花的时间称为分派延迟(dispatch latency)。
调度准则
不同的CPU调度算法具有不同的属性,且可能对某些进程更为有利。为了比较CPU调度算法,分析员提出了许多准则,这些准则包括如下:
CPU使用率:需要使CPU尽可能忙。从概念上讲,CPU使用率0%-100%。对于真实系统,它应从40%(轻负荷系统)~90%(重负荷系统)
吞吐量:如果CPU忙于执行进程,那么就有工作在完成。一种测量工作量的方法称为吞吐量,它指一个时间单元内所完成进程的数量。对于长进程,吞吐量可能为每小时一个进程;对于短进程,吞吐量可能为每秒10个进程。
周转时间:从一个特定进程的角度来看,一个重要准则是运行该进程需要多长时间。从进程提交到进程完成的时间称为周转时间。周转时间为所有时间段之和,包括等待进入内存,在就绪队列中等待,在CPU上执行和I/O执行。
等待时间:CPU调度算法并不影响进程运行和执行I/O的时间;它只影响进程在就绪队列中等待所花的时间。等待时间为在就绪队列中等待所花费时间之和。
响应时间:对于交互系统,周转时间并不是最佳准则。通常,进程能相当早就产生输出,并继续计算新结果同时输出以前的结果给用户。因此,另一时间是从提交请求道产生第一响应的时间。这种时间称为响应时间,是从开始响应所需的时间,而不是输出响应所需要的时间。周转时间通常受输出设备速度的限制。
需要使CPU使用率和吞吐量最大化,而使周转时间、等待时间和响应时间最小化。在绝大多数情况下,需要优化平均值。不过在有的情况下,需要优化最小值或最大值,而不是平均值。例如,为了保证所有用户都得到好的服务,可能需要使最大响应时间最小。
调度算法
CPU调度处理是从就绪队列中选择进程并为之分配CPU的问题。
先到先服务调度
显然,最简单的CPU调度算法是先到先服务调度算法(first-come,first-served (FCFS) Scheduling algorithm)。采用FCFS策略的平均等待时间通常不是最小的,且如果进程CPU区间时间变化很大,平均等待时间也会变化很大。
FCFS调度算法是非抢占的。一旦CPU被分配给了一个进程,该进程会保持CPU直到释放CPU为止,即程序终止或是请求I/O。FCFS算法对于分时系统(每个用户需要定时地得到一定的CPU时间)是特别麻烦的。允许一个进程保持CPU时间过长将是个严重错误。
最短作业优先调度
另一种CPU调度方法是最短作业优先调度算法(shortest-job-first(SJF)schedulingalgorithm)。这一算法将每个进程与其下一个CPU区间段相关联。当CPU为空闲时,它会赋给具有最短CPU区间的进程。如果两个进程具有相同长度,那么可以使用FCFS调度来处理。一个更为适当的表示是最短下一个CPU区间的算法,这是因为调度检查进程的下一个CPU区间的长度,而不是其总长度。
SJF调度算法可证明为最佳的,这是因为对于给定的一组进程,SJF算法的平均等待时间最小。通过将最短进程移到长进程之前,短进程等待时间的减少大于长进程等待时间的增加。因而,平均等待时间减少了。
SJF算法的真正困难是如何知道下一个CPU区间的长度。对于批处理系统的长期(作业)调度,可以将用户提交作业时所指定的进程时间极限作为长度。SJF调度经常用于长期调度。
虽然SJF算法最佳,但是它不能在短期CPU调度层次上加以实现。因为没有办法知道下一个CPU区间的长度。一种方法是近似SJF调度。虽然不知道下一个CPU区间的长度,但是可以预测它。认为下一个CPU区间的长度与以前的相似。因此,通过计算下一个CPU区间长度近似值,能选择具有最短预测CPU区间的进程来进行。
下一个CPU区间通常可以预测为以前CPU区间的测量长度的指数平均。设为第n个CPU区间的长度,设为下一个CPU区间的预测值。因此,对于a, 0≤a≤1,定义
公式定义了一个指数平均。存储了过去历史,值包括最近信息。参数a控制了最近和过去历史在预测中的相对加权。如果a=0,那么近来历史没有影响;如果a=1,只有最近的CPU区间才重要。下图说明了一个指数平均值,其中a=1/2, =10
由于a和(1-a)小于或等于1,所以后面项的权比前面项的权要小。
SJF算法可能是抢占或非抢占的。当一个新进程到达就绪队列而以前进程正在执行时,就需要选择。与当前运行的进程相对,新进程可能有一个更短的CPU区间。抢占SJF算法可抢占当前运行的进程,而非抢占SJF算法会允许当前运行的进程先完成其CPU区间。抢占SJF调度有时称为最短剩余时间优先调度(shortest-remaining-time-first scheduling)。
优先级调度
SJF算法可作为通用优先级调度算法(priority scheduling algorithm)的一个特例。每个进程都有一个优先级与其关联,具有最高优先级的进程会分配到CPU。具有相同优先级的进程按FCFS顺序调度。SJF算法属于简单优先级算法,其优先级(p)为下一个(预测的)CPU区间的倒数。CPU区间越大,则优先级越小,反之亦然。
优先级调度可通过内部或外部方式来定义。内部定义优先级使用一些测量数据以计算进程优先级。例如,时间极限,内存要求,打开文件的数量和平均I/O区间与平均CPU区间之比都可以用于计算优先级。外部优先级是通过操作系统之外的准则来定义的,如进程重要性,用于支付使用计算机的费用类型和数量,赞助工作的单位,其他因素。
优先级调度可以是抢占或者非抢占的。当一个进程到达就绪队列时,其有优先级与当前运行进程的优先级相比较。如果新到达进程的优先级高于当前运行进程的优先级,那么抢占优先级调度算法会抢占CPU。而非抢占优先级调度算法只是将新进程加到就绪队列的头部。
优先级调度算法的一个主要问题是无穷阻塞(indefinite blocking)或饥饿(starvation)。
可以运行但缺乏CPU的进程可认为是阻塞的,它在等待CPU。优先级调度算法会使某个低优先级进程无穷等待CPU。通常,会发生两种情况,要么进程最终能运行,在系统最后为轻负荷时,要么系统最终崩溃并失去所有未完成的低优先级进程。
低优先级进程无穷等待问题的解决之一是老化(aging)。老化是一种技术,以逐渐增加在系统中等待很长世间的进程的优先级。
轮转法调度
轮转法(round-robin,RR)调度算法是专门为分时系统设计的。它类似于FCFS调度,但是增加了抢占以切换进程。定义了一个较小时间单元,称为时间片(time quantum,or time slice)。时间片通常为10-100ms。将就绪队列作为循环队列。CPU调度程序循环就绪队列,为每个进程分配一个时间片的CPU。
为了实现RR调度,将就绪队列保存为进程的FIFO队列。新进程增加到就绪队列的尾部。CPU调度程序从就绪队列中选择第一个进程,设置定时器在一个时间片之后中断,再分派该进程。
接下来将可能发生两种情况。进程可能只需要小于时间片的CPU区间。对于这种情况,进程本身自动释放CPU。调度程序接着处理就绪队列的下一个进程。否则,如果当前运行进程的CPU区间比时间片长,定时器会中断并产生操作系统中断,然后进程上下文切换,将进程加入到就绪队列的尾部,接着CPU调度程序会选择就绪队列中的下一个进程。
对于RR调度算法,队列中没有进程被分配超过一个时间片的CPU时间(除非它是唯一可运行的进程)。如果进程的CPU区间超过一个时间片,那么该进程会被抢占,而被放回到就绪队列。RR调度算法是可抢占的。
RR算法的性能很大程序上依赖于时间片的大小。在极端的情况下,如果时间片非常大,那么RR算法与FCFS算法一样。如果时间片很小,那么RR算法称为处理器共享,n个进程对于用户都有它自己的处理器,速度为真正处理器的1/n。
周转时间随着时间片的大小而改变:
多级队列调度
多级队列调度算法(multilevel queue scheduling algorithm)将就绪队列分成多个独立队列。根据进程的属性,如内存大小、进程优先级、进程类型,一个进程被永久地分配到一个队列中。每个队列都有自己的调度算法。例如,前台进程和后台进程可处于不同队列。前台队列可能采用RR算法调度,而后台队列可能采用FCFS算法调度。
另外,队列之间必须有调度,通常采用固定优先级抢占调度。每个队列与更低层队列相对有绝对优先级。例如,只有系统进程、交互进程和交互编辑进程队列都为空,批处理队列内的进程才可运行。如果在一个批处理进程运行时有一个交互进程进入就绪队列,那么该批处理进程会被抢占。
另一种可能是在队列之间划分时间片。每个队列都有一定的CPU时间,这可用于调度队列内的进程。例如,对于前台-后台队列的例子,前台队列可以有80%的CPU时间用于在进程之间进行RR调度,而后台队列可以有20%的CPU时间采用FCFS算法调度进程。
多级反馈队列调度
通常在使用多级队列调度算法时,进程进入系统时被永久地分配到一个队列。例如,如果前台进程和后台进程分别有独立队列,进程并不从队列转移到另一个队列,这是因为进程并不改变前台或后台性质。这种设置的优点是低调度开销,缺点是不够灵活。
与之相反多级反馈队列调度算法(multilevel feedback queue scheduling algorithm)允许进程在队列之间移动。主要思想是根据不同CPU区间的特点以区分进程。如果进程使用过多的CPU时间,那么它会被转移到更低优先级队列。这种方案将I/O约束和交互进程留在更高优先级队列。此外,在较低优先级队列中等待时间过长的进程会被转移到更高优先级队列。这种形式的老化阻止了饥饿的发生。
通常,多级反馈队列调度程序可由下列参数来定义:
- 队列数量
- 每个队列的调度算法
- 用以确定何时升级到更高优先级队列的方法
- 用以确定何时降级到更低优先级队列的方法
- 用以确定进程在需要服务时应进入哪个队列的方法
多级反馈队列调度程序的定义使它成为最普通的CPU调度算法。它可被配置以适应特定系统设计。不幸的是,由于需要一些方法来选择参数以定义最佳的调度程序,它也是最复杂的算法。
多处理器调度
如果有多个CPU,则负载分配(load sharing)成为可能,但调度问题也相应地变得更为复杂。
多处理器调度的方法
在一个多处理器中,CPU调度的一种方法是让一个处理器(主服务器)处理所有的调度决定、I/O处理以及其他系统活动,其他的处理器只执行用户代码。这种非对称多处理器(asymmetric multiprocessing)方法更为简单,因为只有一个处理器访问系统数据结构,减轻了数据共享的需要。
另一种方法是使用对称多处理器(symmetric multiprocessing,SMP)方法,即每个处理器自我调度。所有进程可能处于一个共同的就绪队列中,或每个处理器都有它自己的私有就绪进程队列。
处理器亲和性
由于使缓存无效或重新构建的代价高,绝大多数SMP系统避免将进程从一个处理器移至另一个处理器,而是努力使一个进程在同一处理器上运行,这被称为处理器亲和性。
处理器亲和性有几种形式。当一个操作系统具有设法让一个进程保持在同一处理器上运行的策略,但不做任何保证时,则会出现软亲和性(soft affinity)。此时,进程进程可能在处理器之间移动。有些系统,如Linux,还提供一个支持硬亲和性(hard affinity)的系统调用,从而允许进程指定它不允许移至其他处理器上。
负载平衡
负载平衡(load balancing)设法将工作负载平均地分配到SMP系统中的所有处理器上。负载平衡通常只是对那些拥有自己私有的可执行进程的处理器而言是必要的。在具有共同队列的系统中,通常不需要负载平衡,因为一旦处理器空闲,它立刻从共同队列中取走一个可执行进程。但同样值得注意的是,在绝大多数支持SMP的当代操作系统中,每个处理器都有一个可执行进程的私有队列。
负载平衡通常有两种方法:push migration和pull migration。对于push migration,一个特定的任务周期性地检查每个处理器上的负载,如果发现不平衡,即通过将进程从超载处理器移到(或推送到)空闲或不太忙的处理器,从而平均地分配负载。当空闲处理器从一个忙的处理器上pull一个等待任务时,发送pull migration。push migration 和 pull migration不能相互排斥,
对称多线程
通过提供多个物理其,SMP系统允许同时运行几个线程。另一种方法是提供多个逻辑(而不是物理)处理器来实现。这种方法被称为对称多线程(SMT)。在Intel处理器中,它被称为超线程(hyperthreading)技术。
SMT的思想是在同一个物理处理器上生成多个逻辑处理器,向操作系统呈现一个多逻辑处理器的视图,即使系统仅有单处理器。每个逻辑处理器都有它自己的架构状态,包括通用目的和机器状态寄存器。进一步讲,每个逻辑处理器负责自己的中断处理,这意味着中断被送到并被逻辑处理器所处理,而不是物理处理器。
SMT是硬件而不是软件提供的。硬件应该提供每个逻辑处理器的架构状态的表示以及中断处理方法。
线程调度
对于支持线程的操作系统而言,体统调度的是内核线程,而不是用户线程。用户线程由线程库管理,内核并不了解它们。为了能在CPU上运行,用户线程最终必须映射到相应的内核线程,尽管这种映射可能是间接的,可能使用轻量级进程(LWP)。
竞争范围
用户线程与内核线程的区别之一在于它们是如何被调度的。在执行多对一模型多对多模型的系统上,线程库调度用户级线程到一个有效的LWP上运行,这被称为进程竞争范围(process-contention scope,PCS)方法,因为CPU竞争发生在属于相同进程的线程之间。当提及线程库调度用户线程到有效的LWP时,并不意味着线程实际上就在CPU上运行,这需要操作系统将内核线程调度到物理CPU上。为了决定调度哪个内核线程到CPU,内核采用系统竞争范围(system-contention scope,SCS)方法来进行。采用SCS调度方法,竞争CPU发生在系统的所有线程中,采用一对一的模型的系统,调度仅使用SCS方法。
PCS是根据优先级完成的----调度程序选择具有最高优先级的可运行的线程来运行。用户线程优先级由程序员给定,并且不被线程库调节,尽管有些线程库允许程序员改变线程的优先级。
小结
CPU调度的任务是从就绪队列中选择一个等待进程,并为其分配CPU。CPU由调度程序分配给所选中的进程。
先到先服务(FCFS)调度室最简单的调度算法,但是它会让短进程等待非常长的进程。最短作业优先(SJF)调度可证明是最佳的,它提供了最短平均等待时间。实现SJF调度比较困难,因为预测下一个CPU区间的长度有难度。SJF算法是通过优先级调度算法(将CPU简单地分配给具有最高优先级的进程)的特例。优先级和SJF调度会产生饥饿。老化技术可以阻止饥饿。
轮转法(RR)调度对于分时(交互)系统更为合适。RR调度让就绪队列的第一个进程使用CPU的q个时间单元,这里q是时间片。在q时间单元之后,如果该进程还没有释放CPU,那么它被抢占并放到就绪队列的尾部。该算法的主要问题是选择时间片。如果时间片太大,那么RR调度就成了FCFS调度;如果时间片太小,那么因为上下文切换而引起的调度开销就过大。
FCFS算法是非抢占的,而RR算法是抢占的。SJF和优先级算法可以是抢占的,也可以是非抢占的。
多级队列调度算法允许多个不同算法用于各种类型的进程。最为常用的模型包括使用RR调度的前后台交互队列,以及使用FCFS调度的后台批处理队列。多级反馈队列调度算法允许进程在队列之间迁移。
许多当前的计算机系统支持多处理器,并允许多个处理器独立地调度它自己。通常,每个处理器维护自己的私有进程(或线程)队列,它们都可以运行。与多处理器调度相关的问题包括多处理器亲和性和负载平衡。
如果操作系统在内核级支持线程,那么必须调度线程而不是进程来执行。Linux进程调度也使用基于优先级算法,并提供实时支持。