1. 调度器概述:Linux调度器发展历史、CFS调度器原理、BFS与MuQSS的诞生背景

1.1 Linux调度器发展历史

Linux内核调度器经历了多次重大重写,每一次演进都围绕吞吐量、公平性、交互响应和可扩展性之间的权衡展开。下表梳理了从2.4到5.x版本的关键里程碑:

内核版本调度器名称核心算法主要问题
2.4 (2001)O(1) 调度器前身循环时间片 + 优先级队列O(n) 扫描进程链表,多核扩展差
2.6.0 (2003)O(1) 调度器双优先级位图 + 活跃/过期数组交互性启发式复杂,桌面响应不稳定
2.6.23 (2007)CFS (完全公平调度器)红黑树 + 虚拟运行时间 (vruntime)服务器/桌面兼顾,但延迟敏感场景仍有抖动
3.14 (2014)CFS + EEVDF 改进负载跟踪 + 组调度 + 带宽控制NUMA 感知增强,但全局锁竞争仍存在
5.x (2020+)CFS + 核心调度 (Core Scheduling)细粒度负载均衡 + 核心隔离安全隔离需求上升,实时性依赖外部补丁
关键认知: 调度器演进本质是“公平”与“响应”的博弈。O(1) 用位图换取了常数时间选择,但交互性判断依赖启发式;CFS 用红黑树实现了数学上的完美公平,却在高竞争桌面场景下引入“平滑但迟钝”的调度延迟。

1.2 CFS调度器原理

CFS (Completely Fair Scheduler) 由 Ingo Molnár 于 2007 年引入,核心思想是:让每个可运行进程获得 CPU 时间的 1/n。它摒弃了固定时间片和优先级队列,转而使用红黑树维护进程的虚拟运行时间 (vruntime)。

CFS 关键数据结构与流程:

  1. 红黑树 (rbtree):以 vruntime 为键值,最左节点是 vruntime 最小的进程,即“最需要 CPU”的进程。
  2. 虚拟运行时间 (vruntime):vruntime += delta_exec * (NICE_0_LOAD / se.load.weight)。权重越高,vruntime 增长越慢,获得更多 CPU。
  3. 调度点 (schedule()):每次时钟中断或进程主动让出 CPU 时,调用 __schedule() 选择红黑树最左节点。
  4. 负载均衡:在多个调度域 (sched_domain) 之间迁移进程,保持各 CPU 运行队列负载均衡。
CFS 调度周期示例: 假设两个进程 A (权重 1024) 和 B (权重 2048),调度延迟目标 6ms。A 获得 2ms,B 获得 4ms。每次调度后,A 的 vruntime 增加 2ms,B 的 vruntime 增加 1ms(因为权重高,增长慢)。红黑树始终选择 vruntime 最小的进程,保证长期公平。
CFS 红黑树调度选择流程 进程 C (vr=8) 进程 B (vr=5) 进程 D (vr=12) ★ 进程 A (vr=2) 最左节点 → 被调度 调度选择: 每次 schedule() 从红黑树取最左节点(vruntime 最小),运行后更新 vruntime 并重新插入树中。

1.3 CFS 的局限性

尽管 CFS 在服务器和通用桌面场景表现出色,但在以下场景暴露出明显短板:

  • 桌面交互响应抖动:CFS 追求长期公平,短期调度延迟可能达到 10~20ms,导致鼠标/键盘操作出现可感知的卡顿。
  • 高负载下的饥饿风险:当大量 CPU 密集型进程竞争时,交互式进程的 vruntime 追赶缓慢,响应时间恶化。
  • NUMA 感知不足:默认负载均衡策略在跨 NUMA 节点迁移时产生额外缓存缺失和延迟。
  • 全局锁竞争:rq->lock 在大型系统上成为瓶颈,影响多核扩展性。
注意: CFS 的“完全公平”是数学意义上的长期公平,而非瞬时公平。对于毫秒级交互响应要求,CFS 的调度粒度(通常 4~6ms 调度周期)和 vruntime 累积机制会引入不可忽略的延迟。

1.4 BFS 与 MuQSS 的诞生背景

BFS (Brain Fuck Scheduler) 由 Con Kolivas 于 2009 年发布,MuQSS (Multiple Queue Skiplist Scheduler) 是 BFS 的后续多队列版本,于 2016 年推出。它们的诞生直接源于对 CFS 桌面响应不满的社区分歧。

BFS 核心设计理念:

  • 单运行队列 (Single Runqueue):所有 CPU 共享一个全局优先级链表,避免负载均衡复杂性。
  • O(n) 调度选择:每次调度遍历整个链表选择最佳进程,但通过虚拟 deadline 和优先级位图加速。
  • 虚拟 deadline 调度:为每个进程计算“最晚开始时间”,优先选择 deadline 最早的进程。
  • 抢占粒度更细:默认调度延迟 1~2ms,远低于 CFS 的 4~6ms。

MuQSS 改进点:

  • 多队列 (Per-CPU Runqueue):每个 CPU 独立维护跳表 (Skiplist),消除全局锁竞争。
  • 跳表替代链表:O(log n) 插入/删除,O(1) 选择最小 deadline 进程。
  • NUMA 感知:进程优先在本地 CPU 队列调度,减少跨节点迁移。
  • 可配置调度粒度:通过 sched_granularity_ns 调整,适应桌面/服务器不同需求。
BFS 与 MuQSS 架构对比 BFS (单队列架构) 全局优先级链表 (Global Runqueue) CPU 0 CPU 1 CPU 2 CPU 3 所有 CPU 从同一个链表取进程 全局锁保护,O(n) 遍历选择 NUMA 不感知,缓存亲和性差 MuQSS (多队列架构) CPU 0 跳表 CPU 1 跳表 CPU 2 跳表 CPU 3 跳表 进程按 NUMA 节点优先本地调度 每个 CPU 独立跳表,无全局锁 O(log n) 插入/删除,O(1) 选择 NUMA 感知,缓存亲和性好

1.5 BFS/MuQSS 与 CFS 的核心差异

特性CFSBFSMuQSS
数据结构红黑树 (O(log n))优先级链表 (O(n))跳表 (O(log n))
运行队列Per-CPU 队列 + 负载均衡全局单队列Per-CPU 跳表
调度选择依据vruntime 最小虚拟 deadline 最早虚拟 deadline 最早
默认调度延迟4~6ms1~2ms1~2ms (可配置)
NUMA 感知通过调度域实现无内置 NUMA 优先
锁竞争rq->lock 全局锁全局链表锁Per-CPU 锁,无竞争
桌面交互响应中等,偶发抖动优秀,低延迟优秀,低延迟
多核扩展性良好 (16~32核)差 (4~8核上限)良好 (64核+)
社区评价: Con Kolivas 称 BFS 为“桌面调度器的最后一块拼图”。尽管 BFS 未合入主线,但其设计思想深刻影响了后续调度器优化方向,尤其是对调度延迟的极致追求。MuQSS 在保留 BFS 低延迟特性的同时,通过多队列和跳表解决了扩展性问题,成为许多桌面 Linux 发行版(如 Liquorix、XanMod)的默认调度器。

1.6 为什么需要移植 BFS/MuQSS?

主线内核持续演进,BFS/MuQSS 作为外部补丁,需要跟随内核 API 变化进行移植。移植动机包括:

  • 桌面/游戏场景优化:降低输入延迟、帧率抖动,提升交互流畅度。
  • 嵌入式/实时场景:BFS 的细粒度抢占适合对调度延迟敏感的嵌入式系统。
  • 旧硬件优化:在低核数 CPU 上,BFS 的简单设计反而比 CFS 更高效。
  • 学术研究:对比不同调度策略在实际负载下的表现,验证调度理论。