处理器调度

CPU调度(Scheduling): 其任务是控制、协调进程对CPU的竞争, 即按一定的调度算法从就绪队列中选择一个进程, 把CPU的使用权交给被选中的进程.

(例如: N个进程就绪, 等待上CPU运行, 然后有M个CPU, CPU调度来决定哪一个进程分配哪一个CPU)

如果没有就绪进程, 系统会安排一个系统空闲进程或idle进程.

 

CPU调度要解决的三个问题:

WHAT:按什么原则选择下一个要执行的进程 — 调度算法

WHEN:何时选择 — 调度时机

HOW: 如何让被选中的进程上CPU运行 — 调度过程(进程的上下文切换)

 

WHEN(CPU调度时机):

事件发生->当前运行的进程暂停运行->硬件机制响应后->进入操作系统,处理相应的时间->结束处理后:

某些进程的状态会发生变化, 也可能又创建了一些新的进程

->就绪队列有调整->需要进程调度根据预设的调度算法从就绪队列选一个进程.

 

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级

总结 – 内核对中断/异常/系统调用处理后返回到用户态时就为CPU调度的时机:

  • 进程正常终止或由于某种错误而终止
  • 新进程创建或一个等待进程变成就绪
  • 当一个进程从运行态进入阻塞态
  • 当一个进程从运行态变为就绪态

 

HOW(调度过程 – 进程切换)

进程调度程序从就绪队列选择了要运行的进程(可以是刚被暂停执行的进程, 也可以是另一个新的进程, 如果是新的进程, 就会发生一个进程切换)

进程切换: 指一个进程让出处理器, 由另一个进程占用处理器的过程

进程切换主要包括两部分工作:

切换全局页目录以加载一个新的地址空间(因为新的进程上CPU,它要有自己的地址空间)

切换内核栈和硬件上下文,其中硬件上下文 包括了内核执行新进程需要的全部信息,如 CPU相关寄存器

(切换过程包括了对原来运行进程各种状态的保存和对新的进程各种状态的恢复)

上下文切换的步骤:

进程A下CPU, 进程B上CPU

  1. 保存进程A的上下文环境(程序计数器、程序状态字、其他寄存器......)
  2. 用新状态和其他相关信息更新进程A的PCB
  3. 把进程A移至合适的队列(就绪、阻塞......)
  4. 将进程B的状态设置为运行态
  5. 从进程B的PCB中恢复上下文(程序计数器、程 序状态字、其他寄存器......)

做完以上步骤, 进程B就上CPU运行, 而进程A的所有信息保存好之后, 之后还可以继续上CPU接着执行

上下文切换的开销(cost):

直接开销:内核完成切换所用的CPU时间. 这些时间用于保存和恢复寄存器......, 用于切换地址空间(相关指令比较昂贵)

间接开销: 高速缓存(Cache)、缓冲区缓存(BufferCache)和 TLB(Translation Look-aside Buffer)失效 (新的进程上CPU后, 原来的这些内容都会失效, 还要把新的进程所需内容送入)

 

WHAT(CPU调度算法)

调度算法的设计

用户角度和系统角度, 对调度算法有不同的要求:

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_时间片_02

 

调度算法的衡量指标

吞吐量(Throughput): 每单位时间完成的进程数目

周转时间TT(Turnaround Time): 每个进程从提出请求到运行完成的时间

响应时间RT(Response Time): 从提出请求到第一次回应的时间

CPU 利用率(CPU Utilization): CPU做有效工作的时间比例 (忙碌与空闲的时间比例)

等待时间(Waitingtime): 每个进程在就绪队列(ready queue)中等待的时间


设计调度算法要考虑的点

1.进程控制块PCB中, 需要记录哪些与CPU调度有关的信息

2.进程优先级及就绪队列的组织

  • 优先级: 进程的重要性,紧迫性
  • 优先数: 一个数值, 反映了某个优先级
  • 静态优先级: 进程创建时指定, 运行过程中不再改变
  • 动态优先级: 进程创建时指定了一个优先级, 运行过程中可以动态变化, 如等待时间较长的进程可提升其优先级
  • 就绪队列可以按优先级来组织: 不同优先级进入不同队列, 就绪队列1 的优先级最高, 当调度程序选择进程时, 首先从最高优先级队列选择进程, 依次往下.

           

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_时间片_03

  另一种就绪队列(也是按优先级): 所有进程第一次创建后都进入第一级就绪队列, 随着进程的运行可能会降低某些进程的优先级, 如时间片用完就降级.

   

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级_04

 

3.抢占式调度与非抢占式调度

占用CPU的方式:

  • 可抢占式Preemptive(可剥夺式): 当有比正在运行的进程优先级更高的进程就绪时,系统可强行剥夺正在运行进程的CPU,提供给 具有更高优先级的进程使用
  • 不可抢占式Non-preemptive(不可剥夺式): 某一进程被调度运行后,除非由于它自身的原因不能运行,否则一直运行下去

4.I/O密集型与CPU密集型进程

按进程执行过程中的行为划分:

  • I/O密集型或I/O型(I/O-bound): 频繁的进行I/O,通常会花费很多时间等待I/O操作的完成
  • CPU密集型或CPU型或计算密集型(CPU-bound): 需要大量的CPU时间进行计算

 

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_时间片_05

5.时间片

  时间片(Time slice / quantum): 指的是一个时间段, 分配给调度上CPU的进程, 确定了运行该进程运行的时间长度.

  设计时间片大小的考虑因素:

  • 进程切换的开销
  • 对响应时间的要求
  • 就绪进程的个数
  • CPU能力
  • 进程的行为

 

批处理系统的调度算法

批处理系统考虑因素:吞吐量, 周转时间, CPU利用率, 平衡/公平问题

先来先服务(FCFS-First Come First Serve)

最短作业优先(SJF-Shortest Job First)

最短剩余时间优先(SRTN-Shortest Remaining Time Next)

最高相应比优先(HRRN-Highest Response Ratio Next)

 

先来先服务(First Come First Serve): 它是先进先出 First In First Out (FIFO): 按照进程就绪的先后顺序使用CPU. 它是非抢占式的调度算法.

优缺点:1.公平(谁先就绪谁先上) 2.实现简单(仅维护一个队列) 3.长进程后面的短进程需要等很长时间,不利于用户体验

例子: 三个进程按顺序就绪:P1,P2,P3, 进程P1执行需要24s, P2和P3各需3s.

 

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级_06

吞吐量: 3jobs/30s = 0.1 jobs/s

周转时间TT: P1:24, P2:27, P3:30 => 平均周转时间: 27s

 

短作业优先(Shortest Job First): 具有最短完成时间的进程优先执行, 它是非抢占式的调度算法.

最短剩余时间优先(Shortest Remaining Time Next): 它是最短作业优先SJF的抢占式版本, 即当一个新就绪的进程比当前运行进程具有更短的完成时间时, 系统抢占当前进程, 选择新就绪的进程执行.

例子:

 

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_调度算法_07

优缺点:1. 在所有进程同时可运行时, 可以得到最短的平均周转时间 2. 不公平, 因为源源不断的短任务到来, 可能使长的任务长时间得不到运行, 而产生”饥饿”现象(starvation)

 

最高相应比优先(Highest Response Ratio Next): 折衷权衡(tradeoff). 它是一个综合的算法(同时具有FCFS和SJF的优点). 调度时, 首先计算每个进程的响应比R;之后, 总是选择 R 最高的进程执行.

响应比R = 周转时间 / 处理时间

=(处理时间 + 等待时间) / 处理时间

= 1 +(等待时间 / 处理时间)

 

交互式系统的调度算法

交互式系统考虑因素: 响应时间, 公平/平衡问题

轮转调度(RR-Round Robin)

最高优先级调度(HPF—Highest Priority First)

多级反馈队列(Multiple feedback queue)

最短进程优先(Shortest Process Next)

 

时间片轮转调度算法

 

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级_08

进程B用完自己的时间片后就回到队列的末位, 然后运行进程F, F时间片到后又回到队列的末位, 以此类推.

目标: 为段人物改善平均响应时间

做法: 周期性切换, 每个进程分配一个时间片, 通过时钟中断引发轮换.

时间片大小的选择:

  • 太长(大于典型的交互时间): 会降级为FCFS算法, 且延长短进程的响应时间

   

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_调度算法_09

  • 太短(小于典型的交互时间): 进程切换良妃CPU时间. (典型的时间片大小在10ms – 100ms之间, 通常为50/60ms)

  

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_时间片_10

 优缺点: 1. 公平(轮流上CPU, 相同时间片) 2. 有利于交互式计算(响应时间快) 3. 由于进程切换, 时间片轮转要话费较高的开销

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级_11

 (若时间片为10ms, 如果进程切换话费0.1ms, CPU开销约占1%, 这才是一个比较合理的开销)

4. 对不同大小的进程是有利的, 但是不利于相同的大小的进程

例子: 两个进程A,B, 运行时间均为100ms, 假设时间片大小为1ms, 上下文切换不耗时.

使用RR的平均完成时间: ABABAB…A(199)B(200) => (199+200)/2 = 199.5ms

使用FCFS的平均完成时间: A(100)B等待(100)B(100) => 100*3/2 = 150ms

 

普通的时间片RR, 给I/O 型进程带来一定的不公平.

CPU型进程: CPU型进程上CPU, 用完时间片, 重新排队… CPU型的进程总是用完给它的时间片.

I/O型进程: I/O型进程上CPU, 还未运行完它的时间片就去等待I/O, 放弃了CPU, 进入等待队列, 等待结果来了, 才再变到就绪队列… I/O 型进程总是用不完它的时间片.

所以引入新的调度算法 – 虚拟轮转法(Virtual RR). 当一个I/O型进程让出CPU进到等待队列, 从等待队列又重新回到就绪状态的时候, 不去进入原来的就绪队列, 单独为它设置一个队列, 叫做辅助队列. 即所有I/O型的进程从等待变成就绪的时候, 进到辅助队列. VRR选择进程的时候, 首先去这个辅助队列里去选择进程, 直到辅助队列为空, 才去从就绪队列里头去选进程.

 

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级_12

 

最高优先级调度算法: 选择优先级最高的进程投入运行

通常: 系统进程优先级 高于 用户进程

            前台进程优先级 高于 后台进程

            操作系统更偏好I/O型进程

优先级可以是静态不变的, 也可以是动态调整的

就绪队列可以按照优先级组织

优缺点: 1.实现简单 2.不公平(导致优先级低的进程产生饥饿现象)

优先级反转问题(优先级反置/翻转/倒挂 – Priority Inversion), 问题的主要是由抢占式优先级调度算法产生的.

现象: 一个低优先级进程持有一个高优先级进程所需要的资源, 使得高优先级进程等待低优先级进程运行.

例子: 假设H是高优先级进程, L是低优先级进程, M是中优先级进程(CPU型). L进入临界区执行, 没出临界区就被抢占; H也要进入临界区, 失败(要等L出临界区, 取得它手上的资源), 被阻塞; M上CPU执行, L无法执行所以H也无法执行.

影响: 1.系统错误 2. 高优先级进程停滞不前, 导致系统性能降低

解决方案: 1. 设置优先级上限(进入临界区的都是优先级最高的) 2.优先级继承 3.使用中断禁止(进入临界区就不响应中断)

 

多级反馈队列算法: 是一个综合调度算法, 是考虑各种因素之后进行折衷权衡(tradeoff)的一个结果.

基本思想(非抢占式):

  • 设置多个就绪队列, 第一级队列优先级最高
  • 给不同就绪队列中的进程分配长度不同的时间片, 第一级队列时间片最小; 随着队列优先级别的降低, 时间片增大.(级别越低, 时间片越大)
  • 首先从第一级队列调度, 当第一级队列为空时, 在第二级队列调度, 以此类推
  • 各级队列按照时间片轮转方式进行调度
  • 当一个新创建进程就绪后, 进入第一级队列
  • 进程用完时间片而放弃CPU, 进入下一级就绪队列
  • 由于中断/阻塞而放弃CPU的进程进入相应的等待队列, 一旦等待的事件发生,该进程回到原来一级就绪队列. (但需考虑是回到队首还是队尾, 且时间片是用完剩下的还是重新分配)

但是若允许抢占, 当有一个优先级更高的进程就绪时, 可以抢占CPU. 被抢占的进程回到原来一级就绪队列. (但需考虑是回到队首还是队尾, 且时间片是用完剩下的还是重新分配)

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_优先级_13

它更偏好I/O型进程.

 

小结: 各种调度算法的比较

Android cfs调度和cpu 空闲 cpu调度schedutil好吗_调度算法_14

 

设计多处理调度算法考虑因素:

  • 不仅要决定选择哪一个进程执行, 还需要决定在哪一个CPU上执行.
  • 要考虑进程在多个CPU之间迁移时的开销
  • 高速缓存失效、TLB失效
  • 尽可能使进程总是在同一个CPU上执行(如果每个进程可以调度到所有CPU上, 假如进程上次在CPU1上执行, 本次被调度到CPU2, 则会增加高速缓存失效、TLB失效;如果每个进程尽量调度到指定的CPU上, 各种失效就会减少)
  • 考虑负载均衡问题(工作和空闲)

   

操作系统采用的调度算法:

UNIX – 动态优先数法

Linux – 抢占式调度策略 (CFS: 完全公平调度算法)

Windows – 基于优先级的抢占式多任务调度策略