1 概念
CPU调度是控制、协调进程对CPU的竞争,即按照一定的调度算法从就绪队列中选择一个进程把CPU的使用权交给该进程。如果没有就绪进程,则操作系统会安排一个系统空闲进程或idle进程
。
表1-1 CPU调度需要解决的三个问题
问题 | 本质 | 具体工作 |
---|---|---|
调度时机 | 内核对中断/异常/系统调用处理后返回到用户态 | 进程正常终止;新进程创建;进程从运行态进入阻塞态;进程从运行态进入就绪态 |
调度过程 | 进程的上下文切换:一个进程把CPU让给另一个进程 | 加载一个新的地址空间;切换内核栈和硬件上下文 |
调度算法 | 操作系统选择下一个要执行的进程所依赖的原则 | 衡量指标:1.吞吐量(Throughput):单位时间完成的进程数。2.周转时间TT(Turnaround Time):每个进程从发出请求到运行完成的时间。3.响应时间RT(Response Time):从发出请求到第一次响应的时间。4.CPU利用率(CPU Utilization):CPU执行有效工作的时间比例。5.等待时间(Waiting Time):每个进程在就绪队列(Ready queue)中等待的时间 |
1. 上下文切换的具体步骤:
场景:进程A下CPU,进程B上CPU
- 保存进程A的上下文环境(程序计数器、程序状态字等)
- 用新进程和其它相关信息更新进程A和进程B
- 把进程A 移入合适的队列(就绪队列、阻塞队列)
- 把进程B的状态设置为运行态
- 从进程B的PCB中恢复上下文(程序计数器、程序状态字等)
上下文切换不可避免的需要一些开销(Cost),这包含两部分:直接开销和间接开销。
表1-2 上下文切换的开销
开销来源 | 具体内容 |
---|---|
直接开销 | 内核完成切换所用的CPU时间,包括保存和恢复寄存器、切换地址空间(相关指令比较昂贵) |
间接开销 | 高速缓存(Cache)、缓冲区缓存(Buffer Cache)和TLB(Translation Lookup Buffer)失效 |
2 设计调度算法应考虑的因素
2.1 进程优先级
静态优先级:进程创建之后优先级就确定了,不再改变
动态优先级:进程优先级随着进程运行时可发生改变
2.2 进程就绪队列组织方式
方案一:进程创建的时候就按照优先级分配到不同的就绪队列中,CPU调度根据优先级高低依次调用
方案二:进程创建的时候放置统一的队列中,每次CPU调度运行时间片用完之后就把优先级降一级。
2.3 抢占式与非抢占式
表1-3 抢占式与非抢占式进程的比较
分类 | 特征 |
---|---|
可抢占式Preemptive | 当有比正在运行的进程优先级更高的进程就绪时,系统可强行剥夺正在运行进程的CPU,提高给更高优先级的进程 |
不可抢占式Non-Preemptive | 某一进程调度运行后,除非由于它自身的原因放弃CPU,否则将会一直运行下去 |
2.4 I/O密集型与CPU密集型进程
表1-4 I/O密集型与CPU密集型进程的比较
分类 | 特征 |
---|---|
I/O密集型(I/O-bound) | 频繁的进行I/O,通常会花费很多时间等待I/O操作 |
CPU密集型(CPU-bound) | 需要大量的CPU时间进行计算 |
2.5 时间片Time slice(或quantum)
分配给调度上CPU运行的进程,规定了允许该进程执行的时间长度。
介绍了CPU调度算法应该考虑的这五个问题之后,接下来就开始对现有的CPU调度算法进行学习。
3 批处理系统的调度算法
3.1 先来先服务(FCFS)
1 基本思想
First Come First Serve,也叫First In First Out(FIFO),是指按照进程就绪的先后顺序使用CPU。
2 算法举例
假设有这么三个就绪进程,P1、P2、P3,执行时间分别需要24s、3s、3s。
采用FCFS调度算法时关键指标如下所示:
调度顺序 | 关键指标 |
---|---|
P1->P2->P3 | 吞吐量:3 jobs/30s=0.1jobs/s。周转时间TT:P1;24s;P2:27s;P3:30s。平均周转时间:27s |
P2->P3->P1 | 吞吐量:3 jobs/30s=0.1jobs/s。周转时间TT:P1;30s;P2:3s;P3:6s。平均周转时间:13s |
通过上表的观察可以发现,具有最短完成时间的进程越靠前,算法的指标性能越好。由此引入另一种调度算法:短作业优先。
3.2 短作业优先(SJF)
1 基本思想
Shortest Job First, 是具有最短完成时间的进程优先执行。
2 演化
最短剩余时间优先(Shortest Remaining Time Next,SRTN),是SJF的抢占式版本,表示当一个新就绪的进程比当前进程具有更短的完成时间时,系统抢占当前进程,选择新就绪的进程执行。无论是SJF还是SRTN,核心思想都是优先完成作业时间较短的进程。
3 算法举例
进程 | 到达时刻 | 运行时间 |
---|---|---|
P1 | 0 | 7 |
P2 | 2 | 4 |
P3 | 4 | 1 |
P4 | 5 | 4 |
3.3 最高响应比优先HRRN
Highest Response Ratio Next,调度时首先计算每个进程的响应比R,之后总是选择R最高的进程执行。
响应比的定义可以有多种形式,其中的一种方案是:
响应比R=周转时间/处理时间
=(处理时间+等待时间)/处理时间
=1+(等待时间/处理时间)
3.4 批处理系统调度算法小结
表3-1 批处理系统的调度算法比较
调度算法 | 特征 | 优点 | 缺点 |
---|---|---|---|
FCFS | 非抢占式 | 公平,实现简单 | 在长进程之后的短进程需要等待很长时间,用户体验不够友好 |
SJF | 非抢占式 | 最短平均周转时间 | 不公平,当有源源不断的任务到来时,可能使耗时较长的任务长时间得不到运行,产生饥饿(starvation)现象。 |
HRRN | 综合性 | 比较好的综合了FCFS和SJF调度算法的优点 | - |
3 交互式系统中采用的调度算法
3.1 时间片轮转调度算法(Round Robin)
1 概念
通过周期性的切换,为每个进程分配一个时间片,通过时钟中断引发轮换实现。改善了短作业的平均响应时间。
2 如何选择合适的时间片
首先要明确,时间片太长和太短都不好,太长的话(大于典型的交互时间)则时间片轮转调度算法降级为先来先服务算法,并且对于某些短进程来说响应时间边长了。太短的话(小于典型的交互时间)则会由于进程切换造成CPU资源的浪费。
3.2 虚拟轮转算法(Virtual RR)
针对时间片轮转算法对I/O密集型进程不公平的现象,CPU密集型进程每次都能用完时间片,而I/O密集型进程每次都用不完时间片。为了解决这个问题,操作系统为I/O密集型进程单独设立了一辅助队列,每次I/O进程用完时间片之后不再回到就绪队列,而是进入辅助队列,CPU每次要选择进程执行的时候优先从辅助队列中获取,只有当辅助队列为空时才去就绪队列中选择。
3.3 最高优先级调度算法
总是选择优先级最高的进程投入运行。通常系统进程优先级高于用户进程,前台进程优先级高于后台进程,I/O进程高于CPU进程。
3.4 交互式系统调度算法小结
表3-2 交互式系统的调度算法比较
调度算法 | 特征 | 优点 | 缺点 |
---|---|---|---|
时间片轮转调度 | 每个进程交替执行,对不同大小的进程来说是有利的 | 公平,有利于交互计算,响应时间快 | 由于进程切换,该算法要花费较高的开销 |
虚拟轮转算法 | 为I/O密集型进程开辟了专门的辅助队列 | 对I/O密集型进程友好 | CPU密集型进程要在I/O进程执行完之后才能获取到CPU |
最高优先级算法 | 按照优先级调度,不公平 | 实现简单 | 对于优先级较低的进程很容易产生饥饿现象 |
3.5 优先级反转问题
1 概念
又称优先级倒挂,是指一个低优先级进程持有一个高优先级进程所需要的资源,使得高优先级进程需要等待低优先级进程运行。
假设H是高优先级进程,L是低优先级进程,M是中优先级进程(CPU型),场景:当L进入临界区执行,之后被抢占但尚未出临界区;H也要进入临界区,由于临界区被L占用导致失败被阻塞;M上CPU执行,此时L无法获取CPU运行,H也就无法运行。
2 解决方案
- 设置优先级上限,即让所有进入临界区的进程,设置它的优先级高于那些不在临界区的进程
- 优先级继承,如果一个低优先级阻碍了高优先级进程,那么它可以继承该高优先级进程的优先级,从而可以继续运行
- 使用中断禁止,凡是进入临界区的进程都不再响应中断直到它退出临界区
4 多级反馈队列调度算法(Multilevel Feedback)
1 基本思想
- 设置多个就绪队列,第一级队列优先级最高
- 不同的就绪队列时间片长度不同,第一级队列时间片最小,随着队列优先级的降低,时间片增大
- 首先从第一级队列调度,当低一级队列为空时在第二级队列调度,以此类推
- 各级队列按照时间片轮转方式进行调度
- 当一个新建进程就绪后进入第一级队列
- 进程用完时间片而放弃CPU后进入下一级就绪队列
- 由于阻塞而放弃CPU的进程(如I/O密集型进程)进入相应的等待队列,一旦等待事件发生,该进程回到原来一级就绪队列
5 各种调度算法小结
完成了各种调度算法的介绍说明之后,本节对以上各种调度算法做个小结,如表5-1所示。
表5-1 各种调度算法的比较
调度算法 | 占用CPU方式 | 吞吐量 | 响应时间 | 开销 | 对进程的影响 | 饥饿问题 |
---|---|---|---|---|---|---|
FCFS | 非抢占式 | 不强调 | 可能很慢,特别是当进程的执行时间差别很大并且长进程都排在前面时 | 最小 | 对短进程不利,对I/O型的进程不利 | 无 |
Round Robin | 抢占式(时间片用完时) | 若时间片小,吞吐量会很低 | 为短进程提供较好的响应时间 | 最小 | 公平对待 | 无 |
SJF | 非抢占 | 高 | 为短进程提供较好的响应时间 | 可能较大 | 对长进程不利 | 可能出现 |
SRTN | 抢占式 | 高 | 提供好的响应时间 | 可能较大 | 对长进程不利 | 可能出现 |
HRRN | 非抢占式 | 高 | 提供好的响应时间 | 可能较大 | 很好的平衡 | 无 |
Feedback | 抢占式(时间片用完时) | 不强调 | 不强调 | 可能较大 | 对I/O进程有利 | 可能出现 |
从上表可以看出,每种调度算法都是有利有弊的,没有一种绝对完美的调度算法。那么在多处理器系统中调度算法的设计应该考虑哪些因素呢?
- 调度算法不仅要实现选择哪一个进程执行,还需要决定在哪个CPU上执行
- 考虑进程在多个CPU之间迁移时的开销。当进程从一个CPU迁移到另一个CPU时,不可避免的会发生高速缓存失效、TLB失效等问题,所以尽可能使进程在同一个CPU上执行。
- 多个CPU时还需要考虑负载均衡的问题,不应该使某些CPU过忙或者过闲
6 Windows线程调度算法
首先需要明确Windows的调度单位是线程(因为Windows支持内核级线程),采用的是基于动态优先级、抢占式调度,同时结合了时间配额的调整。
6.1 基本思想
1)就绪线程按照优先级进入相应队列
2)系统总是选择优先级最高的就绪线程运行
3)统一优先级的各线程按时间片轮转算法进行调度
4)多CPU系统中允许多个线程并行运行
6.2 引发线程调度的时机
除了第1节的表1-1中调度时机中所介绍的四种条件外,Windows系统引发线程调度时机还有另外两种:
- 一个线程的优先级改变了
- 一个线程改变了它的亲和(Affinity)处理机集合
某个线程的亲和处理机集合可以简单理解为是可以执行该线程的所有处理机的集合。
6.3 线程优先级
Windows优先级一共有32个,范围是0~31。包括三类,如表6-1所示。
表6-1 Windows系统线程的优先级
分类 | 优先级 | 特征 |
---|---|---|
系统线程 | 0 | 零页线程:用于对系统中空闲物理页面清零 |
可变优先级 | 1~15 | 优先级可以在一定范围内升高或降低 |
实时优先级 | 16~31 | 优先级不可变 |
6.4 线程的时间配额
时间配额不是一个时间长度值,而是一个称为**配额单位(quantum unit)**的整数。一个线程用完了自己的时间配额时,如果没有其它相同优先级的线程,Windows将重新给该线程分配一个新的时间配额让它继续运行。
6.5 Windows下的线程调度策略
1 主动切换
线程在运行过程中由于需要等待输入输出结果,该线程会转到阻塞态,主动让出CPU。
2 抢占
线程在运行时被就绪队列中优先级更高的线程抢占了CPU。这个时候线程会被放回相应优先级的就绪队列的队首。但是要注意区别:
1)如果被抢占的线程是实时优先级,则时间配额会被重置为一个完整的时间配额
2)如果是可变优先级,时间配额不变,重新得到CPU时将运行剩余的时间配额
3 时间配额用完
假设线程A的时间配额用完,这又分为两种情况:
1)A的优先级没有降低
此时如果队列中有其它的就绪线程,选择下一个线程执行,A回到就绪队列末尾
如果队列中没有其它的就绪线程,系统会给线程A分配一个新的时间配额让它继续运行
2)A的优先级降低了(注意这种情况只有当线程优先级被提升过)
Windows将会选择一个更高优先级的线程执行。
6.6 线程优先级提升
线程优先级提升只针对可变优先级范围内(1~15)的线程。以下五种情况Windows会提升线程的当前优先级:
- I/O操作完成
- 信号量或事件等待结束还没
- 前台进程中的线程完成了一个等待操作
- 由于窗口活动而唤醒窗口线程
- 线程处于就绪态超过了一定的时间还没有运行(饥饿现象)