想象你走进一家只有一位店员的奶茶店。店员一次只能做一杯奶茶,可顾客却有好几位:有人只点一杯原味珍珠,有人一次性点了十杯水果茶。这时候,店员下一杯该先做谁的?这个“下一杯做谁的”的决定,就是调度;而这位店员,就是操作系统里的 CPU。排队等待的顾客,就是一个个进程;那位负责安排做单顺序的店长,就是调度器。 店长可以有很多种做单策略:先来后到、短单优先、VIP 优先、轮流做单;每种策略都让一部分顾客满意,也会让另一部分顾客抱怨。操作系统的 CPU 调度算法,本质上也是同一道排队题:CPU 只有一个,任务却有多个,如何选择下一个任务、何时打断当前任务、如何让所有任务都“感觉被照顾到”。这篇文章,就跟着奶茶店的店长一起,把 CPU 调度算法的原理与实践完整走一遍。

岗位关联

对 AI 从业者而言,理解 CPU 调度是读懂训练集群性能、推理服务延迟和云资源成本的前提;它帮你从“会调 API”走向“会调系统”。

目录
  1. 可视化演示
  2. 调度核心概念
  3. 评价指标
  4. 先来先服务
  5. 短作业优先
  6. 优先级调度
  7. 时间片轮转
  8. 多级反馈队列
  9. 实践与调优
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

交互对比FCFS、SJF、Round Robin和Priority四种CPU调度算法的执行过程,通过甘特图展示进程调度顺序并计算平均等待时间与周转时间。

本节配有交互动效(进入视口后自动加载,约 3KB)。

CPU调度算法对比 P1: 到达0 突发5 优先级2 P2: 到达1 突发3 优先级1 P3: 到达2 突发8 优先级3 P4: 到达3 突发2 优先级2 等待时间 = 周转时间 - 执行时间 SJF平均等待最短,RR公平但切换开销大

调度核心概念

奶茶店为什么需要店长?因为店员只有一双手,顾客却不止一位。店长不负责做奶茶,只负责回答一个问题:下一个做单的人是谁。在操作系统里,这个“回答下一个问题”的角色就是调度器。它的专业说法是:从就绪队列中挑选一个进程,把 CPU 分配给它。

调度器不是随时都在做决定,只有在特定“调度时机”才会出手。你可以理解为店长通常不会每秒钟都盯着店员,而是等某个时刻发生了才重新排单:新顾客进店、店员做完一杯、某个顾客临时改单走了、或者计时器响了。对应到操作系统,就是进程创建、进程退出、进程阻塞、中断或时间片耗尽。这些时刻决定了系统何时需要重新“排班”。

有了调度时机,还要回答“能不能打断当前这杯奶茶”。如果店员正在做一杯大果茶,而新来了一位只点柠檬水的顾客,店长能不能让他把柠檬水插队先做?如果可以,就是抢占式调度;如果必须等大果茶做完,就是非抢占式调度。抢占需要硬件定时器支持,就像需要一个“闹钟”到点提醒店长该重新排单。

调度器很像奶茶店的店长:平时不显眼,但一忙起来,店长的排单策略直接决定顾客会不会等得发火。理解“谁来决定、何时决定、能不能打断”这三件事,后面所有算法都只是店长排单的不同套路而已。

R={P1,P2,…,Pn},s=select⁡(R),s∈R\displaystyle R = \{ P_1, P_2, \dots, P_n \}, \quad s = \operatorname{select}(R), \quad s \in R
这条公式说明调度器的工作对象是就绪队列 R 里的进程,它按某种规则 select 出一个进程 s,并把 CPU 交给它。
  1. R 表示就绪队列所有“已经准备好、只等 CPU”的进程组成一个集合,就像奶茶店正在排队等做单的顾客。
    R={P1,P2,…,Pn}R = \{ P_1, P_2, \dots, P_n \}
  2. select 表示选择规则由具体的调度算法决定,比如先来先服务、短作业优先、时间片轮转。
    s=select⁡(R)s = \operatorname{select}(R)
  3. s 表示最终选中的进程一次调度只会把 CPU 给一个进程,所以 s 必须来自就绪队列 R。
    s∈Rs \in R

评价指标

怎么判断一个店长排单排得好不好?不能只看店员是否一刻不停,还要看顾客等多久、店里一小时能出多少杯奶茶。操作系统调度也有四个常用指标:CPU 利用率,即 CPU 真正干正事的时间占比;吞吐量,即单位时间完成的进程数;周转时间,即从进程进入系统到完成所花的总时间;响应时间,即从提交请求到用户第一次感觉“有反应”的时间。

我们用一个简单公式衡量平均等待时间:让每个顾客记录自己从进店排队到开始做单等了多久,再求平均。一个优秀的调度算法往往能降低这个平均值,但也可能带来其他代价。比如只顾着让 CPU 一直忙,可能会让长任务霸占资源,短任务体验极差。

这四个指标就像奶茶店门口的顾客满意度问卷:有的顾客在意“多久拿到奶茶”,有的在意“店员是不是先理我一下”。不存在一个算法能让四项指标同时最优,店长必须先想清楚这家店最重视什么,再选择对应的排单策略。

Wˉ=1n∑i=1nWi=1n∑i=1n(Ci−Ai−Ti)\displaystyle \bar{W} = \frac{1}{n} \sum_{i=1}^{n} W_i = \frac{1}{n} \sum_{i=1}^{n} (C_i - A_i - T_i)
平均等待时间等于每个任务等待时间的总和除以任务数量;等待时间可以用“完成时间减到达时间再减实际执行时间”来算。
  1. W_i 表示第 i 个进程的等待时间从到达到开始真正执行之间,进程在就绪队列里空等的时间,就像顾客排队等点单的时长。
    Wi=Ci−Ai−TiW_i = C_i - A_i - T_i
  2. C_i 是完成时间,A_i 是到达时间,T_i 是服务时间完成时间减到达时间是“总在店时间”,再减去实际做单时间,剩下的就是排队时间。
    Ci−Ai−TiC_i - A_i - T_i
  3. 1/n 求和表示求平均把所有进程的等待时间加起来除以 n,得到调度策略的整体等待水平。
    1n∑i=1nWi\frac{1}{n} \sum_{i=1}^{n} W_i

先来先服务

如果店长采用最简单的“先来后到”,那就是先来先服务(FCFS)。顾客进店后按顺序排队,店员从队头开始一杯一杯做,不插队也不暂停。这个策略很公平,写起来也简单:只需要维护一个先进先出队列。但它有个致命缺点——如果排在最前面的顾客点了二十杯大果茶,后面每位顾客都得瞪着他做完。

这种“一个长任务堵住后面所有人”的现象,叫作护航效应,就像一辆超慢的车走在单行道上,后面所有车只能慢慢跟着。FCFS 的平均等待时间可能非常长,尤其当长任务比短任务先到时。但它对批处理任务友好:因为没有频繁切换,店员不用反复“洗杯子换手”,系统开销很低。

FCFS 就像奶茶店的“傻瓜排单法”:规则简单、不会打起来,但一遇到大单就把整条队伍拖垮。它告诉我们,公平不等于高效,有时候“先来的大单”恰恰是让后面所有人痛苦的源头。

Wi=∑k=1i−1tk\displaystyle W_i = \sum_{k=1}^{i-1} t_k
在 FCFS 下,第 i 个进程的等待时间等于它前面所有进程的服务时间之和。
  1. W_i 表示第 i 个进程的等待时间进程在队列中的位置越靠后,等得越久。
    WiW_i
  2. t_k 表示第 k 个进程的服务时间每个“顾客”做单需要的时间,对应进程占用 CPU 的时长。
    tkt_k
  3. 求和符号从 k=1 到 i-1把第 i 个进程之前的所有服务时间都加起来,就是它要等的总时长。
    ∑k=1i−1tk\sum_{k=1}^{i-1} t_k

短作业优先

假如店长学聪明了:他决定先做耗时短的订单,让那些只点一杯柠檬水的顾客先拿到奶茶,长单最后再做。这个策略就是短作业优先(SJF)。从数学上看,它可以显著降低平均等待时间,因为短任务不必等长任务做完,整体队伍流动更快。

SJF 还有一种抢占版本,叫作最短剩余时间优先(SRTF):如果新来的任务剩余时间比当前正在执行的任务短,店长会立刻“抢单”先做新任务。这在理论上能进一步降低平均等待时间,但代价是系统需要知道每个任务还要多久才能完成。现实中最难的不是算法本身,而是准确预估“这杯奶茶到底要做多久”。

短作业优先像“聪明店长”的排单法,但它有个大问题:如果不断有更短的订单插进来,那个超大果茶订单可能会从开门等到打烊都做不上。这就是饥饿。算法再“最优”,也不能饿死顾客;所以在真实系统里,SJF 往往要和优先级、老化等机制配合使用。

s=arg⁡min⁡Pi∈Rti\displaystyle s = \arg\min_{P_i \in R} t_i
短作业优先从就绪队列 R 中选择服务时间 t_i 最小的进程 s。
  1. R 是就绪队列所有等待 CPU 的进程组成一个候选集合,就像店员面前等待做单的顾客。
    R={P1,P2,…,Pn}R = \{ P_1, P_2, \dots, P_n \}
  2. t_i 是进程服务时间进程需要的 CPU 时间,相当于做一杯奶茶需要的分钟数。
    tit_i
  3. argmin 表示取最小值的那个进程在 R 中比较所有 t_i,返回最短任务作为 s。
    s=arg⁡min⁡Pi∈Rtis = \arg\min_{P_i \in R} t_i

优先级调度

有些奶茶店会有会员,会员订单可以优先做。这种“按身份插队”的策略,就是优先级调度。每个进程有一个优先级数值,调度器每次从就绪队列中选择优先级最高的进程执行。优先级可以静态设定,比如系统进程高于用户进程;也可以动态调整,比如响应延迟变高就把优先级提高。

优先级调度有两种形态:如果当前正在执行的进程允许被更高优先级的进程打断,就是抢占式优先级调度;否则就是非抢占式。高优先级进程获得更快响应,但低优先级进程可能长期得不到 CPU,就像普通顾客看着 VIP 一个接一个插队,自己永远轮不上。

优先级调度的精髓不是“谁身份高谁先做”,而是“身份高的人也要记得让低身份的人有机会上桌”。老化就是店长给普通顾客偷偷加码:等太久的人会慢慢变成“临时 VIP”,这样既有优先级,又不至于饿死任何人。

πi=πi0+α⋅wi,s=arg⁡max⁡Pi∈Rπi\displaystyle \pi_i = \pi_i^0 + \alpha \cdot w_i, \quad s = \arg\max_{P_i \in R} \pi_i
优先级调度选择优先级数值最大的进程;为了让等待者不被饿死,可以让优先级随等待时间增加而上升,这就是老化。
  1. π_i 表示进程 i 的当前优先级优先级越高,越容易被调度器选中,相当于顾客的会员等级。
    πi\pi_i
  2. π_i^0 + α * w_i 表示老化基础优先级加上等待时间乘以一个系数,等得越久,优先级缓慢升高。
    πi0+α⋅wi\pi_i^0 + \alpha \cdot w_i
  3. argmax 表示选择优先级最高者遍历就绪队列 R,把 CPU 交给 π_i 最大的进程。
    s=arg⁡max⁡Pi∈Rπis = \arg\max_{P_i \in R} \pi_i

时间片轮转

如果店长不想让任何一个顾客等太久,可以改成“每人做 30 秒”的轮流制:店员按队列顺序做单,每杯奶茶最多做固定的一段时间,时间一到就换下一位,做了一半的订单重新排到队尾。这就是时间片轮转(RR)。它让每个进程都能定期获得 CPU,响应时间变得均匀,特别适合交互式系统,比如你边敲键盘边等程序响应。

时间片 q 的选择非常关键。q 太大,轮转就退化成 FCFS,短任务依然要等很久;q 太小,店员频繁“换手”,光顾着洗杯子做交接,真正做奶茶的时间变少。于是系统需要在“响应够快”和“切换开销够低”之间找平衡。

时间片轮转就像奶茶店里“一人做三十秒”的规则:很公平,但换手太频繁会让店员累得做不出几杯。好的店长会把时间片设得恰到好处——短到让顾客感觉人人有份,长到店员能把一杯奶茶比较完整地做出来。

Rmax⁡=n⋅q,O=⌈tq⌉⋅Ccs\displaystyle R_{\max} = n \cdot q, \quad O = \left\lceil \frac{t}{q} \right\rceil \cdot C_{cs}
最坏情况下,一个进程要等完其他 n-1 个进程的时间片才能再次得到 CPU;时间片越短,切换开销越大。
  1. n 是就绪进程数,q 是时间片长度一圈轮下来需要 n 个时间片,所以最坏响应时间是 n*q。
    Rmax⁡=n⋅qR_{\max} = n \cdot q
  2. t 是进程总服务时间一个进程总共需要 t 的 CPU 时间,可能被切成多段。
    tt
  3. C_cs 是一次上下文切换的代价切换次数大致是 t/q 向上取整,总切换开销等于切换次数乘以单次代价。
    O=⌈tq⌉⋅CcsO = \left\lceil \frac{t}{q} \right\rceil \cdot C_{cs}

多级反馈队列

真正高级的奶茶店不会只有一条队伍,而是把顾客分成好几条队列:只点一杯的“快单”走快速通道,点了很多杯的“大单”走慢速通道。多级反馈队列(MLFQ)就是这个思路的调度版本。系统有多个优先级队列,进程先进入最高层队列,如果它在给定时间内没用完时间片(说明它是交互型短任务),就留在高层;如果它用完时间片还没结束(说明它是计算型长任务),就被降到下一层。

MLFQ 的“反馈”体现在它观察进程的历史行为:主动让出 CPU 的进程说明需要频繁交互,继续保持高优先级;总是吃满时间片的进程说明是 CPU 密集型,降到低层让它少打扰别人。这样系统不需要预先知道任务长度,也能做到“短任务快速响应,长任务在后台慢慢跑”。

MLFQ 是店长们的“集大成智慧”:先用优先级让短单快跑,又用反馈识别哪些订单其实很耗时间,把它们请到后面的队伍慢慢做。现代操作系统里许多调度器都借鉴了它的思想,因为它既不需要神预言,又能适应动态变化的负载。

ql=2lq0,Pi→Ll+1 if used ql\displaystyle q_l = 2^{l} q_0, \quad P_i \to L_{l+1} \ \text{if used} \ q_l
每一层的时间片可以逐级加倍:进程降到更低层后,每次能执行的时间更长,从而减少长任务的切换开销。
  1. l 表示队列层级0 是最上层,数字越大优先级越低,相当于奶茶店的快单通道到慢单通道。
    l=0,1,2,…l = 0, 1, 2, \dots
  2. q_l = 2^l q_0 表示时间片逐级翻倍低层进程每次分到更长时间,不必频繁被打断。
    ql=2lq0q_l = 2^{l} q_0
  3. 进程用完本层时间片就降级如果 P_i 在当前层的一次时间片内没做完,它被转入 L_{l+1} 等待下一轮。
    Pi→Ll+1P_i \to L_{l+1}

实践与调优

调度算法的最终归宿是真实系统。在实时系统中,调度器不仅要选最快的任务,还要保证任务在截止时间前完成,比如无人驾驶必须定时处理传感器数据;在多核系统里,调度器要把进程分布到多个 CPU 上,避免一个核忙死、其他核闲着的“负载不均”。

Linux 的 CFS 调度器是很好的实践案例。它没有把进程抽象成“谁能插队”,而是给每个进程维护一个虚拟运行时间 vruntime。每次调度时,CFS 选择 vruntime 最小的进程执行,让所有进程在虚拟时间里尽量公平;nice 值小的进程权重高,运行同样时间 vruntime 涨得慢,自然获得更多 CPU。

实践中的调优,就像店长根据高峰期数据决定要不要加人、要不要把部分订单分流到小程序自取:先用指标观察 CPU 利用率、平均等待时间、响应延迟,再调整时间片、优先级、nice 值或队列层级。调度没有银弹,只有不断度量、对照场景、选择策略的工程循环。

vi←vi+tiwi,s=arg⁡min⁡Pi∈Rvi\displaystyle v_i \leftarrow v_i + \frac{t_i}{w_i}, \quad s = \arg\min_{P_i \in R} v_i
CFS 用“谁欠的 CPU 时间多谁先跑”的思路实现公平调度:实际运行时间除以权重得到虚拟运行时间,每次选最小者。
  1. v_i 是进程 i 的虚拟运行时间记录这个进程在公平世界里的“累计做单时间”。
    viv_i
  2. t_i 是实际运行时间,w_i 是权重实际跑得越多,虚拟时间涨得越多;权重越大,虚拟时间涨得越慢。
    tiwi\frac{t_i}{w_i}
  3. 选择 min 的进程系统从就绪队列 R 中挑出虚拟运行时间最小的进程,也就是“得到 CPU 机会最少”的那个。
    s=arg⁡min⁡Pi∈Rvis = \arg\min_{P_i \in R} v_i

AI 应用场景

训练集群里的“任务排班”

阶段:模型训练与资源调度

解决的问题:在共享 GPU 训练集群里,如果只按“先提交先运行”的 FCFS 策略,一个大型预训练任务会连续占用几十张卡好几天,后面大量小规模消融实验只能无限排队。AI 团队需要把调度思想搬进集群资源管理器,用优先级加老化让重要任务快跑,同时保证低优先级小任务不被饿死。

具体案例:某团队在 8 卡 A100 集群上同时运行大模型微调和 20 个算法工程师的消融实验。他们给微调任务设置高优先级,给实验任务设置低优先级并启用“排队时间每增加 30 分钟优先级提升一级”的老化策略。最终实验平均排队时间从 3 小时降到 15 分钟,而微调任务总耗时只增加了约 4%。

推理服务的负载均衡

阶段:在线推理部署与流量治理

解决的问题:大模型 API 服务的每个请求都像一个“奶茶订单”,有的简单有的复杂。如果负载均衡只按轮询分发,一个慢请求会堵住某个 GPU 实例,导致用户首 token 延迟忽高忽低。参考时间片轮转和多级反馈队列的思路,网关可以根据实例负载动态调整分发权重。

具体案例:某 LLM 服务网关把不同租户的请求放入多级队列:普通对话请求走快队列,长文档生成请求走慢队列;再用带权轮询把请求分发到 GPU 实例,并定期根据每个实例的队列长度调整权重。改造后,P95 首 token 延迟稳定在 800 毫秒以内,GPU 利用率提高了约 20%。

Python 演示

python68 行
class Process:    def __init__(self, pid, arrive, burst):        self.pid = pid        self.arrive = arrive        self.burst = burst        self.wait = 0  def make_procs():    return [        Process('P1', 0, 5),        Process('P2', 1, 3),        Process('P3', 2, 2),        Process('P4', 3, 4)    ]  def fcfs(procs):    procs.sort(key=lambda p: p.arrive)    t = 0    for p in procs:        if t < p.arrive:            t = p.arrive        p.wait = t - p.arrive        t += p.burst    return sum(p.wait for p in procs) / len(procs)  def sjf(procs):    left = procs[:]    t = 0    done = []    while left:        ready = [p for p in left if p.arrive <= t]        if not ready:            t = min(p.arrive for p in left)            continue        nxt = min(ready, key=lambda p: p.burst)        left.remove(nxt)        nxt.wait = t - nxt.arrive        t += nxt.burst        done.append(nxt)    return sum(p.wait for p in done) / len(done)  def rr(procs, quantum):    q = [(p, p.burst) for p in procs]    t = 0    wait_total = 0    while q:        p, remain = q.pop(0)        if t < p.arrive:            t = p.arrive        if remain <= quantum:            wait_total += t - p.arrive            t += remain        else:            wait_total += t - p.arrive            t += quantum            p.arrive = t            q.append((p, remain - quantum))    return wait_total / len(procs)  if __name__ == '__main__':    print('FCFS avg wait:', round(fcfs(make_procs()), 2))    print('SJF  avg wait:', round(sjf(make_procs()), 2))    print('RR q=1 avg wait:', round(rr(make_procs(), 1), 2))
目的:用一个小型 Python 演示程序计算 FCFS、SJF、时间片轮转三种调度策略的平均等待时间,直观对比不同排单策略的差异。
  1. procs.sort(key=lambda p: p.arrive)按到达时间排序,模拟顾客进店的先后顺序。
  2. p.wait = t - p.arrive记录进程从进店到真正开始执行的等待时长。
  3. left = procs[:]复制一份进程列表,避免修改原始数据。
  4. ready = [p for p in left if p.arrive <= t]筛选出当前时刻已经排到店里等待中的进程。
  5. nxt = min(ready, key=lambda p: p.burst)在已到达进程中选择服务时间最短的那个,体现 SJF 核心逻辑。
  6. if remain <= quantum:判断当前进程剩余时间能否在一个时间片内完成。
  7. p.arrive = t进程被抢占后重新记录它下次进入队列的时间,用于累计等待时间。
  8. q.append((p, remain - quantum))未完成的进程带着剩余时间重新回到队尾,等待下一轮执行。

常见误区

误区时间片轮转里,把时间片调到无限小,每个进程都能立刻获得 CPU,响应时间不就越好了吗?那干脆设成 1 微秒。

正解时间片过小会让上下文切换开销急剧上升。店员每做一个动作就要洗一次手,洗手的成本远大于做奶茶本身,结果吞吐量暴跌,大家的等待时间反而更长。时间片的设定必须在响应及时性和切换开销之间取一个平衡点,不是越小越好。

为何常见:初学者只看到“小时间片 = 更多进程轮流跑 = 更公平”,忽略了每次切换 CPU 都要保存现场、恢复现场,这个“洗杯子换手”的动作本身也要花时间。

误区既然 SJF 能让平均等待时间最短,那操作系统就应该永远用 SJF,所有任务都先做短的,效率最高。

正解SJF 的最优性建立在两个前提上:一是系统能准确知道每个进程的运行时间,这在现实中几乎不可能;二是它会导致长任务长时间得不到 CPU,出现饥饿。真实系统往往用 MLFQ 或优先级加老化来模拟“短任务优先”的效果,而不是直接使用 SJF。

为何常见:教材里常强调 SJF 在理论上的平均等待时间最优,读者容易忽略它的前提条件和现实约束,把“理论最优”误解成“工程最优”。

误区抢占式调度能随时打断当前进程,看起来更灵活、更公平,所以应该全面取代非抢占式调度。

正解抢占式调度虽然响应更快,但每次抢占都会带来上下文切换开销,还可能导致共享数据的一致性更难保证。非抢占式调度在批处理系统里反而更高效,因为任务能连续执行,省去了大量切换成本。调度方式没有绝对优劣,只有适合场景与否。

为何常见:交互式系统的体验强烈依赖抢占,初学者从个人电脑的流畅体验出发,容易忽略服务器批处理场景中“连续执行更划算”的事实。

误区看到“选择优先级最高的进程”这句话,就默认数字大的一定先执行,所以调参时拼命把任务优先级调大。

正解不同系统对优先级的定义可能相反:有的数字越大优先级越高,有的数字越小优先级越高,比如 Linux 的 nice 值越低优先级越高。在写程序时如果不确认调度器的语义,调高“优先级”反而可能让任务被降级或得不到调度。

为何常见:不同教材和系统约定不一,读者只记住了“高优先级”这个概念,忽略了数值方向的差异是系统设计约定的一部分。

误区监控面板上 CPU 已经跑满,说明资源一点没浪费,调度器一定非常完美。

正解CPU 利用率高可能意味着一个长任务把 CPU 霸占了,其他任务都在旁边干等;也可能是因为系统在忙等或空转。真正好的调度要看一组指标:利用率、吞吐量、等待时间、响应时间,还要结合业务目标。店长让店员忙到冒烟,但如果所有顾客都在门口暴走,这不算成功。

为何常见:运维看板默认展示 CPU 利用率,让人容易只盯着一个数字;但调度质量从来都是多目标权衡,单一指标无法说明问题。

面试题

Q1. 在操作系统中,负责从就绪队列中选出下一个进程并进行 CPU 分配的是?

单选题 · easy

  • A.中断控制器
  • B.调度器← 正确答案
  • C.内存管理单元
  • D.编译器

答案:B。调度器的职责就是从就绪队列里按某种算法挑选进程,并把 CPU 交出去。中断控制器负责处理硬件中断,内存管理单元负责地址转换,编译器负责把代码翻译成机器码,都与“选哪个进程”无关。

Q2. 先来先服务(FCFS)调度算法最典型的问题是什么?

单选题 · easy

  • A.需要频繁抢占
  • B.长任务会让后续任务的平均等待时间变长← 正确答案
  • C.必须预测每个任务的运行时间
  • D.时间片设置过小

答案:B。FCFS 按到达顺序执行,一旦队头是长任务,后面所有短任务都要等很长时间,形成护航效应。FCFS 是非抢占的,不需要频繁抢占,也不需要预测运行时间,时间片轮转才涉及时间片设置。

Q3. 在时间片轮转调度中,如果把时间片设置得过小,最可能带来什么后果?

单选题 · mid

  • A.吞吐量无限上升
  • B.上下文切换开销显著增大← 正确答案
  • C.所有进程都会饥饿
  • D.系统不再需要时钟中断

答案:B。时间片越小,进程切换越频繁,每次切换都要保存和恢复现场,这些上下文切换的成本会挤占真正的计算时间。吞吐量会下降而不是上升,轮转本身不会让所有进程饥饿,而且时间片机制恰恰依赖时钟中断。

Q4. 短作业优先(SJF)算法的主要缺点是?

单选题 · mid

  • A.实现起来太简单
  • B.需要预知任务运行时间,且长任务可能饥饿← 正确答案
  • C.无法用于批处理系统
  • D.一定会导致频繁抢占

答案:B。SJF 的核心是选择服务时间最短的任务,但系统一般很难准确预知运行时间;而且如果短任务不断到达,长任务可能长期得不到执行。实现简单不是缺点,SJF 也适合批处理,是否抢占取决于采用 SJF 还是 SRTF。

Q5. 在优先级调度中,解决低优先级进程长期得不到 CPU 的常用技术是?

单选题 · mid

  • A.老化← 正确答案
  • B.轮转
  • C.零拷贝
  • D.内存分页

答案:A。老化是指随着进程等待时间增加,逐步提高它的优先级,让低优先级进程最终也能被调度到。轮转是另一类调度算法,零拷贝和内存分页与优先级调度没有直接关系。

Q6. 在多级反馈队列(MLFQ)中,一个进程在当前队列用完整个时间片仍未结束,通常会被如何处理?

单选题 · mid

  • A.提升到更高优先级队列
  • B.降低到更低优先级队列← 正确答案
  • C.立即从系统中删除
  • D.保持原队列不变

答案:B。MLFQ 根据历史行为调整队列。用完时间片说明该进程是 CPU 密集型,系统会把它降到更低优先级队列,避免它频繁抢占交互型短任务。提升队列通常用于短任务让出 CPU 时,而不是用满时间片之后。

Q7. 下列哪些属于 CPU 调度的评价指标?

多选题 · mid

  • A.CPU 利用率← 正确答案
  • B.吞吐量← 正确答案
  • C.磁盘容量
  • D.平均周转时间← 正确答案

答案:ABD。CPU 利用率衡量 CPU 忙闲程度,吞吐量衡量单位时间完成的任务数,平均周转时间衡量任务从到达到完成的总耗时,这三个都是调度核心指标。磁盘容量属于存储资源,不是 CPU 调度的评价指标。

Q8. 关于抢占式调度与非抢占式调度,下列说法正确的有?

多选题 · hard

  • A.非抢占式调度中,进程只有阻塞或结束后才会让出 CPU← 正确答案
  • B.抢占式调度通常需要硬件定时器支持← 正确答案
  • C.抢占式调度一定比非抢占式公平
  • D.非抢占式调度更适合交互式系统

答案:AB。非抢占式调度下,进程一旦获得 CPU 就会一直运行到自愿让出,因此选项 A 正确。抢占式调度要靠时钟中断等机制强制切换,所以 B 正确。抢占不一定公平,也可能带来更高开销;交互式系统通常更需要抢占,所以 C 和 D 错误。

Q9. 关于时间片轮转(RR)和多级反馈队列(MLFQ),说法正确的有?

多选题 · hard

  • A.RR 比 FCFS 更容易保证各进程都有机会获得 CPU← 正确答案
  • B.MLFQ 可以结合优先级与进程历史行为进行调度← 正确答案
  • C.MLFQ 一定不会出现饥饿
  • D.时间片越大越有利于交互式系统

答案:AB。RR 让每个进程轮流执行固定时间片,比 FCFS 更能避免长任务霸占,所以 A 正确。MLFQ 通过队列层级和反馈机制动态调整进程优先级,B 正确。MLFQ 设计不好时低层进程仍可能饥饿,C 错误;时间片太大会让响应时间变差,D 错误。

Q10. Linux CFS 调度器选择下一个进程执行的主要依据是?

单选题 · extreme

  • A.优先级最高的进程
  • B.虚拟运行时间最小的进程← 正确答案
  • C.到达时间最早的进程
  • D.物理内存占用最大的进程

答案:B。CFS 为每个进程维护虚拟运行时间 vruntime,它会选择 vruntime 最小的进程,让所有进程在虚拟时间视角下尽量公平。CFS 也会考虑权重,但最终排序依据是虚拟运行时间,而不是单纯优先级、到达时间或内存占用。