LabHub
学习 学习路径 课程

操作系统

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

在 LabHub 中继续学习

一句话总结

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

流程图: 17 · 3 · 护航效应(convoy effect) · 无法预知下一次 CPU burst 长度。

为什么需要了解这些

可运行进程多于核心时,必然有人等待。仅改变运行顺序,同一组任务的平均等待时间就可能相差数倍。假设三个进程的 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 表达优先级。