在之前的系列文章中,我们反复提到 CFS(Completely Fair Scheduler)的核心目标就是“公平”——通过 vruntime、权重(weight)和 PELT 等机制,确保每个进程按照其 nice 值比例公平分享 CPU 时间。
这套设计在多任务、通用服务器场景下表现优秀,但随着硬件和应用场景的演进,CFS 暴露出了一个越来越明显的短板:对延迟(latency)的支持不足。
而现在随着Linux嵌入式的高性能开发越来越普及,内核也想设计一个对latency支持更好的调度器,也就是EEVDF
本文就来聊聊 CFS 在公平性之外,为什么对延迟敏感任务的处理显得力不从心,以及为什么简单靠 nice 值或 rt class 都无法完美解决这个问题。
还记得我之前举了一个分蛋糕的例子,现在基于之前的再举一个相似的例子
想象一个幼儿园分蛋糕的场景:CFS 就像一个严格按“体重比例”分蛋糕的阿姨——你 nice 值高(权重高),就能多分一块;nice 值低,就少分一点。整体上大家都没饿着,公平得很。 但如果有个小朋友只是偶尔想吃一口(低吞吐需求),却希望“现在就要吃到”,而另一个小朋友能吃一整块但可以等会儿再吃——阿姨的规则却只知道“谁体重大谁多吃”,完全不考虑“谁更着急”。 结果就是:着急的小朋友哭了半天还没吃到(延迟高),而能等的那个反而先吃饱了(响应差)。这正是 CFS 当前在延迟敏感任务上的痛点。
CFS 的核心公式大家都很熟:
vruntime += delta_exec * NICE_0_LOAD / weight
也就是说
这套机制对“长期公平分享 CPU”非常有效,但在“短期响应延迟”上却力不从心。
假设是桌面 UI、游戏输入、终端敲命令,它们对 CPU 吞吐需求很低(可能每秒只跑几 ms),但一旦有输入事件,就希望在 1–5 ms 内得到响应。 如果当前有几个 CPU 密集型任务(nice 0 或正 nice),它们 vruntime 增长慢,会持续霸占 CPU,交互任务即使 vruntime 很小,也可能被延迟十几甚至几十 ms。
假设是音频处理、视频编码预处理、实时监控、飞行控制、相机链路,这些任务对吞吐要求中等,但对延迟有硬性要求(比如音频缓冲区 underrun 就会爆音)。 CFS 无法表达“我不需要很多 CPU,但需要低延迟”的诉求——nice 值只能给你更多时间片,却不能保证“尽快给我一次”。
在linux桌面上,例如大家使用的ubuntu,centos,或者麒麟系统,如果一边使用,后台一边编译代码,,CFS 会让编译任务占满大部分时间,使用的浏览器,QT程序会卡顿非常明显
cfs的 nice 值本质上是吞吐量权重,不是延迟优先级。调低 nice(-10)→ 权重变大 → vruntime 增长慢 → 获得更多 CPU 时间(吞吐提升),但不保证“下次调度更快”。 即使 nice 很低,如果当前 vruntime 已经被其他任务拉开差距,仍然要等别人“还债”完。
CFS 的 vruntime 机制是“累计欠债还债”,不是“谁着急谁先上”。 这就好比银行贷款:nice 值高的人利息低、额度高,但排队顺序还是看谁先借的,而不是谁更急用钱。
当然,Linux 也提供了实时调度类(rt_sched_class),优先级高于 CFS,可以实现低延迟。但是有如下缺点
总而言之,言而总之,社区仍选择发展eevdf,其原因是 rt class 太重了。除非你是一个对调度非常熟悉的开发者在使用rt,否则一定会存在大量的管理,错误使用等问题。
CFS 的公平性设计非常成功,它让多任务系统在吞吐量上达到了前所未有的均衡。 但公平不等于响应快——nice 值只能告诉你“谁该多吃”,却无法回答“谁该先吃”。 在桌面、游戏、移动、软实时等场景下,延迟敏感任务越来越重要,单纯靠 rt class 或调 nice 都已力不从心。这在开发高性能交互系统或实时应用时至关重要。理解 CFS 的延迟短板,我们就能更好地选择调度策略、调优参数