TT Lab
开始
学习 学习路径 课程

操作系统

CPU 调度 — 公平与响应之间的交易

在 TT Lab 中继续学习

一句话总结

调度器是一项政策:它要在吞吐量、等待时间、响应时间和公平性这些相互冲突的目标之间决定牺牲什么。

为什么需要了解这些

可运行进程多于核心时,必然有人等待。仅改变运行顺序,同一组任务的平均等待时间就可能相差数倍。假设三个进程的 CPU burst 分别为 24、3、3,并同时到达:

完成相同工作所需总时间不变,平均等待时间却相差五倍以上。短任务排在长任务后的现象称为护航效应(convoy effect),是先来先服务 FCFS 的典型弱点。

工作原理

主要算法可概括如下。

算法 抢占 饥饿 优点 弱点
FCFS 否 否 简单 护航效应
SJF 否 是 平均等待时间最优 无法知道下一次 burst
SRTF 是 是 响应优于 SJF 需要预测,切换增加
优先级 两者皆可 是 可反映政策 饥饿,需要老化
轮转 是 否 响应时间均衡 时间片难以选择
多级反馈队列 是 可能 最灵活 参数很多

SJF 可证明在平均等待时间上最优,但有致命问题:无法预知下一次 CPU burst 长度。 实现中通常用过去 burst 的指数平均预测:新预测值 = α×最近实测值 + (1−α)×上次预测值,α 常取 0.5。

轮转调度的关键是时间片。过大就退化为 FCFS,过小则上下文切换成本会吞噬真正的工作时间。经验上会让 80% 的 CPU burst 能在一个时间片内结束。

Linux CFS 从另一角度处理公平性。它追踪每个任务的虚拟运行时间(vruntime),选择最小者运行。vruntime 等于实际运行时间除以权重,权重来自 nice 值。nice 越低(优先级越高),权重越大,vruntime 增长越慢,因此更常被选择。任务以红黑树管理,插入删除为对数时间,最左节点始终是下一个运行对象。

实际工作中的表现

容器 CPU 限制直接使用这些概念。cgroup CPU 份额(shares/weight)相当于 CFS 权重,只在竞争时决定比例;CPU 配额则限制每个周期可用时间。份额降低并不会阻止容器使用空闲 CPU。

配额还有另一陷阱:多线程应用可能在周期开始时耗尽配额,随后整个应用停到下一周期,发生节流。若平均 CPU 利用率不高但尾延迟突增,应先看节流计数器。

不使用 CPU 的等待更常见

以上讨论的是就绪进程争夺 CPU,但真实服务器中,大部分等待时间来自等待 I/O 完成。混淆两者会使性能诊断完全走偏。

负载平均值(load average)是典型误区。Linux 不仅统计可运行进程,也统计等待磁盘 I/O 的进程。 因而 CPU 使用率 20%、负载平均值 30 完全可能。此时增加 CPU 没有效果,应检查磁盘或网络。

进程状态能直接显示区别:R 表示正在运行或等待运行,D 表示不可中断等待。 D 状态长期持续时,进程连信号也无法响应;存储停顿时,大量进程甚至无法退出,正是这个原因。

等待本身也影响调度。等待很久后唤醒的进程此前没有使用 CPU,所以 vruntime 较小,会优先运行。交互程序响应迅速正得益于此:等待键盘输入期间 vruntime 不增长,输入到达后便可超越一直计算的进程先运行。

因此诊断响应缓慢时,不能只看 CPU 使用率。 应先区分运行队列过长、I/O 等待过长,还是发生了配额节流。三者的原因和应对完全不同,一张 CPU 图无法区分它们。

后续测验将确认什么

确认你能计算为何同一任务集合仅改变顺序就会改变平均等待时间,并解释 CFS 如何用 vruntime 表达优先级。