CPU调度 (CPU scheduling):多个进程同时处于内存,当一个进程必须等待时,OS从该进程拿走CPU使用权交给其他进程。
进程执行从一个IO区间(I/O burst)开始,随后进入一个CPU区间(CPU burst)并反复,进程循环地在CPU执行和I/O等待两个状态间切换,直到通过系统请求终止最后一个CPU burst。
CPU burst的长度随进程和计算机的不同而变化,通常具有大量短CPU burst和少量长CPU burst。I/O约束程序通常具有很多短CPU burst,CPU约束程序可能有少量长CPU burst。
每当CPU空闲时,OS就使用短期调度程序(short-term scheduler) (或CPU scheduler)从就绪队列中选择一个能够执行的进程并为它分配CPU。
就绪队列不一定是FIFO队列。就绪队列中的记录通常为PCB。
抢占&非抢占
CPU调度决策发生在4种情况下:
1) 进程从运行(running)状态切换到等待(waiting)状态;
2) 进程从运行(running)状态切换到就绪(ready)状态;
3) 进程从等待(waiting)状态切换到就绪(ready)状态;
4) 进程终止
非抢占(nonpreemptive)调度方案:a.k.a. 协作(cooperative)调度方案,一旦CPU分配给一个进程,该进程会一直使用CPU直到进程终止或切换到等待状态,该方案中调度只发生在1、4两种情况下。
否则称为抢占(preemptive)调度方案。
分派程序
分派程序(dispatcher):每次进程切换时都要使用的一个模块,用于将CPU控制交给由short-term scheduler 所选的进程。功能包括1)切换上下文;2)切换到用户模式;3)跳转用户程序的合适位置来重启程序。
分派延迟(dispatch latency):dispatcher停止一个进程并启动另一个进程所用的时间。
调度准则
用于分析比较CPU调度算法的准则可包括
·CPU使用率(CPU utilization):理论上为0%~100%,真实系统一般为40%~90%。
·吞吐量(Throughput):一个时间单位内所完成进程的数量。
·周转时间(Turnaround time):一个进程从提交到完成的所用时间。
·等待时间(Waiting time):进程在就绪队列中等待所用时间之和。
·响应时间(response time):从提交请求到产生第一响应的所用时间。
需要使CPU utilization和throughput最大化,turnaround time、waiting time和response time最小化。绝大多数情况下需要优化平均值,有些情况下需要优化最大值、最小值或response time方差等。
调度算法
FCFS (最简单,但会让短进程等待时间非常长)
先到先服务调度算法(first-come, first-served (FCFS) scheduling algorithm):先请求CPU的进程先分配到CPU,通常用FIFO队列实现。 [nonpreemptive]
FCFS策略平均等待时间通常较长,不适用于time-sharing系统。
护航效果(convoy effect):所有其他进程都等待一个大进程释放CPU,相比让较短进程最先执行的情况,CPU和设备的使用率更低。
SJF (提供最短平均等待时间)
最短作业有限调度算法(shortest-job-first (SJF) scheduling algorithm):每个进程与其下一个CPU burst关联。当CPU空闲时,将它分配给具有最短CPU burst的进程。[preemptive or nonpreemptive]
更适当的术语表示应该是 “the shortest-next-CPU-burst algorithm”,命名为SJF主要是因为惯例。
难点:如何知道下一CPU burst的长度。
1) 对于(batch system中的)long-term job scheduling,可将用户提交job时所指定的process time limit作为长度。
2) 对于short-term CPU scheduling,下一CPU burst的长度无法知道。可对下一CPU burst长度进行预测,选择具有最短预测CPU burst的进程。
下一CPU burst通常预测为以前CPU burst区间测量长度的指数平均(exponential average)。
τn+1 = αtn + (1-α) τn
tn – 第n个CPU burst的长度(记录最近信息);
τn – 第n个CPU burst的预测值(记录过去历史);
τn+1 – 下一CPU burst的预测值;
α – 控制最近和过去历史在预测中的相对加权,0≤α≤1。
preemptive的SJF调度 – 最短剩余时间优先调度(shortest-remaining-time-first scheduling),如果到达就绪队列的新进程有比当前运行进程更短的CPU burst,可抢占CPU。
Nonpreemptive的SJF调度 – 允许当前运行的进程先完成。
优先级调度
优先级调度算法(priority scheduling algorithm):每个进程都与一个优先级(priority)关联,CPU被分配给具有最高priority的进程,相同priority的进程按FCFS顺序调度。[preemptive or nonpreemptive]
SJF是优先级调度的一个特例,其priority为下一CPU burst的倒数。
Priority通常为固定区间的数字,有的系统用小数字表示低priority,有的系统则用小数字表示高priority。
Priority可通过内部或外部定义。
内部priority通过对进程的测量数据(e.g. 时间极限、内存要求、打开文件数量、平均I/O burst和平均CPU burst之比)计算来定义;
外部priority通过OS之外的准则(e.g. 进程重要性、使用计算机的费用类型和数量、赞助单位等因素)。
Preemptive的priority调度 – 如果到达就绪队列的新进程有比当前运行进程更高的priority,可抢占CPU。
Nonpreemptive的priority调度 – 将新进程加到就绪队列的头部。
优先级调度和SJF调度会产生饥饿,可用老化技术解决。
Problem – 无穷阻塞(indefinite blocking)/饥饿(starvation):某个低priority进程可能会无穷等待CPU。
Solution — 老化(aging):逐渐增加在系统中等待很长时间的进程的priority,使得最终该进程拥有最高priority并能执行。
RR (适合分时/交互系统)
时间片(time quantum, a.k.a. time slice):一个较小的时间单元,通常为10~100ms。
轮转法调度算法(round-robin (RR) scheduling algorithm):专门为time-sharing系统设计,CPU调度程序循环就绪队列,为每个进程分配不超过一个time quantum的CPU。[preemptive]
实现
CPU调度程序每次从FIFO就绪队列中选择第一个进程,设置定时器在一个time quantum后终端,再dispatch该进程。
1) 如果当前运行进程的CPU burst短于time quantum,则CPU由进程自动释放,调度程序再处理下一个进程;
2) 如果当前运行进程的CPU burst长于time quantum,则到点后定时器向OS发送中断,执行上下文切换,进程被加入到就绪队列尾部,CPU调度程序再处理下一个进程。(被抢占)
采用RR的平均waiting time通常较长。
主要问题:选择时间片。RR算法的性能非常依赖于time quantum的大小。
如果time quantum太大,则与FCFS一样;
如果time quantum很小,则因上下文切换而引起的调度开销过大。
Time quantum应该比context switch时间长。
处理器共享(processor sharing):如果time quantum很小,则RR可产生“n个进程都有它自己的处理器,各自速度都为真正处理器速度的1/n”的效果。
多级队列调度(允许多个不同算法用于各种类型的进程)
多级队列调度算法(multilevel queue scheduling algorithm):将就绪队列划分成多个独立队列,每个队列有自己的调度算法。进程根据自身属性被永久分配到对应的一个队列。
常用模型:前台交互队列使用RR,和后台批处理队列使用FCFS。
队列之间必须有调度。可采用
1) 固定优先级抢占调度(fixed-priority preemptive scheduling) – 每个队列相比更低层队列有绝对的priority。对于一个队列,只有高层队列都为空时该队列内进程才可运行;如果有新进程进入高层队列则CPU会被抢占。[通常采用]
2) 在队列间划分time-slice – 每个队列有固定的CPU时间,在自己的CPU时间内可调度队列内进程。
多级反馈队列调度
对比多级队列调度 –允许进程在队列之间移动,相比开销更大,更灵活。
多级反馈队列调度算法(multilevel feedback queue scheduling algorithm):根据CPU burst的特点区分进程。如果进程使用过多CPU时间则转移到更低队列,在低priority队列中等待时间过长的进程可被转移到高priority队列(aging的一种形式)。
可由以下参数定义:
1) 队列数量;
2) 每个队列的调度算法;
3) 用于确定何时升级到更高priority队列的方法;
4) 用于确定何时降级到更低priority队列的方法;
5) 用于确定进程需要服务时应进入哪个队列的方法。
e.g.
队列0中,不能在8ms内完成的进程会被抢占,移到队列1尾部;
队列1中,不能在16ms内完成的进程会被抢占,移到队列2尾部;
只有队列0和1为空时,队列2内进程才可根据FCFS运行。
多处理器调度(multuiple-processor scheduling)
同构(homogeneous):processor功能相同。同构系统中,可将任何processor用于运行队列中的任何进程。
当前许多计算机都支持multiprocessing,并允许每个processor独立调度自己。通常每个processor维护自己私有的进程或线程队列。
多处理器调度的方法
1) 非对称多处理(asymmetric multiprocessing)方法:让单独的一个processor (master server) 处理所有调度决策、I/O处理和其他系统活动,其它processor只执行用户代码。(简单,只有一个processor访问系统数据结构,减轻数据共享的需要)
2) 对称多处理(symmetric multiprocessing, SMP)方法:每个processor是self-scheduling的,每个processor检查就绪队列并选择一个进程来执行。(所用进程可能处于一个共同的就绪队列中,或每个processor都有私有的就绪队列。)
在绝大多数支持SMP的当代操作系统中,每个processor都有私有的就绪队列。
几乎现代操作系统都支持SMP。
处理器亲和性
处理器亲和性(Processor affinity):进程对其运行所在的processor的亲和性,尽量使一个进程在同一个processor上运行,避免将进程在processors之间迁移。
由于在processors之间迁移进程的代价(1. 使原processor的cache无效 2. 重新构建新processor的cache)高,大多数SMP系统都具有processor affinity。
优点:进程可以利用它在原processor的cache中的数据。
Processor affinity通常有两种形式:
软亲和性(soft affinity):OS具有让一个进程保持在同一个processor上的运行策略,但不能做任何保证,进程有可能移动。
硬亲和性(hard affinity):允许进程指定它不能迁移至其他processor上。E.g. Linux提供
负载平衡
负载平衡(Load balancing):设法将工作负载平均分配到SMP系统的所有processor上。
对于“拥有自己私有的就绪队列”的processor来说是必需的,“具有共同就绪队列”的系统通常不需要。
Load balancing通常有两种方法:
Push migration:一个特定的task周期性地检查每个processor上的负载,如果发现不平衡就把进程从过载的processor push到空闲或不太忙的processor上。
Pull migration:空闲的processor从忙碌的processor上pull走一个waiting task。
Push migration和pull migration可以并行实现,不需要互斥。
e.g. Linux scheduler, ULE scheduler for FreeBSD.
Load balancing通常会抵消掉processor affinity的好处,关于何种方式更好并没有绝对的规则。有些系统中只有当不平衡达到一定额度后才会移动进程。
SMT
对称多线程(Symmetric multithreading, SMT):提供多个logical processors来实现多线程同时运行的策略。在intel processors中也称为超线程(hyperthreading)技术。
SMT在同一physical processor上生成多个logical processor,向OS呈现一个“多个logical processors”的视图。
每个logical processor都有自己的架构状态(architecture state),包括general-purpose和machine-state registers。
每个logical processor负责自己的中断处理。
SMT是由硬件(而不是软件)提供的。硬件应提供每个logical processor的架构状态的表示以及中断处理方法,OS不需要特殊设计。
线程调度(Thread Scheduling)
对于支持多线程的OS,OS调度的是内核线程,而不是进程。
竞争范围
用户线程和内核线程所用调度方法的不同
进程竞争范围(Process-contention scope, PCS):在实现多对一或多对多模型的系统中,线程库调度用户级线程到可用的LWP上。i.e. CPU竞争发生在“属于相同进程的线程”之间。
系统竞争范围(System-contention scope, SCS):OS将内核线程调度到物理CPU上,该竞争发生在OS的所有线程中。采用一对一模型的OS的线程调度只采用SCS。
PCS通常根据priority来完成调度 [preemptive] ,但在具有相同priority的线程间并不保证有time slicing。
Pthread线程调度
POSIX Pthread API允许在线程生成中指定是PCS或SCS。竞争范围值PTHREAD_SCOPE_PROCESS表示“采用PCS调度”,PTHREAD_SCOPE_SYSTEM表示“采用SCS调度”。
函数pthread_attr_setscope(pthread attr_t *attr, int scope)、pthread_attr_getscope(pthread_attr_t *attr, int *scope) 用于获取及设置竞争范围。
某些OS只允许特定的竞争范围值。E.g. Linux, Mac OS X只允许PTHREAD_SCOPE_SYSTEM。
OS实例
对于支持内核级线程的OS,必须调度线程(而不是进程)来执行。
以下3种OS通常偏爱交互进程而不是批处理进程或CPU-bound进程。
Solaris的内核线程调度(抢占、基于优先级、支持实时线程)
传统Solaris使用多对多模型,Solaris 9使用一对一模型。
Solaris按照优先级排序有4种调度类型: 实时(real time)、系统(System)、分时(Time sharing)和交互(Interactive),每种类型有不同的priority和调度算法。
Scheduler将特定类的priority转换为全局priority,再选择全局priority最高的线程来执行,直到该线程阻塞、用完time quantum或被更高priority的线程抢占。如果多个线程的priority相同,则采用循环队列。
Solaris 9引入2种新的调度类型:
1) 固定优先级(fixed priority) – 线程的priority与time sharing类型范围相同,但不能动态调节。
2) 公平共享(fair share) – 用CPU shares代替priority来做调度决策。
CPU shares:表明可用CPU资源的权利,并被分配到一个project(进程集)。
Time sharing类(默认调度类型)和Interactive类采用同样的调度策略(多级反馈队列),Priority和time quantum默认成反比。
通常Interactive进程的priority更高,CPU-bound进程的priority更低。
Interactive和time sharing类包括60个优先级,在其调度中:
时间片到期(Time quantum expired)(i.e. 用完其time-quantum而未堵塞)的线程将被认为是CPU-intensive的,并被降低优先级。
从睡眠中返回(Return from sleep)(e.g. 从等待I/O中返回)的线程优先级将被提高。
System类专门保留给内核使用,用于运行内核进程。System进程一旦创建,其priority就不再改变。(在内核模式下运行的用户进程并不属于system类。)
Real time类的进程具有最高priority,能在其他类型进程之前运行。通常只有少数进程属于real time类。
Windows XP的内核线程调度(基于优先级、抢占、支持实时线程)
Dispatcher:Windows XP内核中用于处理调度的部分。(应该与前面所介绍的分派程序dispatcher不同)
Dispatcher选择priority最高的线程来执行,直到该进程被更高priority的进程抢占、终止、用完time quantum或调用了blocking system call。
Dispatcher使用32级priority方案,priority分为两大类型:
Priority 0的线程用于内存管理
Priority 1~15的线程属于可变类型(variable class),此类线程的priority可以改变。
Priority 16~31的线程属于实时类型(real-time class)
Dispatcher为每个priority使用一个队列,并从高到低检查队列集,直到发现一个可以执行的线程。如果没有找到,则执行一个称为空闲线程(idle thread)的特别线程。
Win32 API定义了进程可能属于的priority类型:
1) REALTIME_PRIORITY CLASS
2) HIGH_PRIORITY_CLASS
3) ABOVE_NORMAL_PRIORITY_CLASS
4) NORMAL_PRIORITY_CLASS
5) BELOW_NORMAL_PRIORITY_CLASS
6) IDLE_PRIORITY_CLASS
其中,REALTIME_PRIORITY_CLASS属于real-time class,其它类型属于variable class。
进程通常属于NORMAL_PRIORITY_CLASS,除非其父进程为IDLE_PRIORITY_CLASS或在创建该进程时指定其他类型。
每个线程的priority都基于其所属priority类型和它在该类型中的relative priority。Relative priority的值包括:
1) TIME_CRITICAL
2) HIGHEST
3) ABOVE_NORMAL
4) NORMAL
5) BELOW_NORMAL
6) LOWEST
7) IDLE
基础优先级(base priority):每个线程都有的一个(在其所属类型范围内的)priority值,默认为该类型中relative priority为NORMAL的值。
线程的priority初始值通常为所属进程的base priority。
线程的time quantum用尽时将被中断,如果该线程属于variable class则其priority将降低,但绝不会低于其base priority。
当属于variable class的线程被从等待操作中释放时,其priority将提升(提升多少取决于该线程等待的是什么)。
e.g. 等待I/O比等待磁盘操作得到的提升更大,缩短交互线程的响应时间。
为了给交互程序的进程提供优秀的性能,Windows XP对NORMAL_PRIORITY_CLASS的进程有一个特别调度规则 – 区分前台进程和后台进程,当进程进入前台时增加其time quantum的倍数(通常为3)。
Linux内核调度(基于优先级,抢占、提供实时支持)(Linux不区分进程和线程,使用task。)
2.5版本前运行传统UNIX算法,存在问题:1) 对SMP系统没有提供足够支持 2) tasks数量增加时无法按比例调整。2.5版本提供了O(1)的调度算法和对SMP的支持。
Linux两个独立的priority range:
Real-time (0~99):被分配静态priority。
Nice (100~140):具有动态priority,根据task的交互性决定+5还是-5。(交互性更强的任务通常-5,使priority更高)
这两个ranges映射到global priority,数值越低priority越高。
Linux中分配给task的time quantum与priority成反比。(与Solaris、Windows XP相反)
runque:内核所维护的一个数据结构,记录可运行tasks的列表。每个runque包括两个priority arrays,每个priority array都有一个根据priority索引的tasks列表。
Active array包括所有在time quantum中还有剩余的tasks;
Expired array包括所有已到期(耗尽time quantum)的tasks。
因为支持SMP,每个processor都需维护自己的runque并自行调度。调度时,从active array中选择priority最高的task来在CPU上执行。Task到期被移至expired array后将重新计算动态优先级。当所有tasks都到期(I.e. active array为空)时,两个array互换。
调度算法评估
分析评估法(analytic evaluation):评估算法的一种主要方法,使用给定算法和系统负荷产生一个公式或数字来评估算法在该负荷下的性能。(使用数学分析)
确定模型
确定模型法(deterministic modeling):analytic evaluation的一种。使用预先确定的特定负荷,计算每个算法在该负荷下的性能。主要用于描述调度算法和提供例子。
缺点:要求输入精确数字,并且答案只适用于给定情况。
排队模型
对于许多系统,进程集合并不是静态的但可以通过测量CPU burst分布和进程到达系统的时间分布来估计进程的到达率(arrival rates)和CPU的服务率(service rates)。
排队网络分析(queueing-network analysis):CPU可看做具有一个等待进程队列的服务器,从进程的到达率和服务率可计算CPU使用率、平均队列长度、平均等待时间等。主要用于比较调度算法。
缺点:队列模型只是现实系统的近似,难以处理复杂算法或分布。
Little formula: (可用于根据3个变量中的2个来计算另外1个)
n – 平均队列长度; W – 队列平均等待时间; ? – 进程平均到达率;
如果系统处于稳定状态,则 n = ? × W。
模拟(对代表性进程采用算法模拟并计算性能)
模拟(simulation):对计算机系统建模。随着模拟程序执行,时钟变量增加,模拟程序修改系统状态来反映设备、进程和scheduler的活动,收集打印反映算法性能的统计资料。
缺点:精确模拟需要较多时间、跟踪磁带需要大量存储空间、模拟程序编码调试的工作。
生成驱动数据的方法
最常见 – 使用随机数生成器,根据概率分布生成。分布可以数学定义或经验定义,经验定义需要对真实系统进行测量。
问题:由于频率分布不能表示时间的发生顺序,分布驱动的模拟可能不够精确。
解决方案:使用跟踪磁带(trace tapes)记录真实系统中时间的发生顺序。
实现
在真实系统上实现算法并在真是环境中进行跟踪。
缺点:代价高(编程、修改)、环境可能发生变化。