乐于分享
好东西不私藏

Linux 调度器迎来"插件化"时代:sched_ext 子调度器深度解读

Linux 调度器迎来"插件化"时代:sched_ext 子调度器深度解读

当你的数据库 VM、Web 服务器、AI 训练任务可以在同一台物理机上各自"请"一个专属调度器——会发生什么?


一、开篇:一个困扰了虚拟化领域十几年的老问题

想象一下这个场景:

你在一台物理服务器上跑了三个虚拟机——一个跑着延迟敏感的数据库、一个在做 HPC 计算、还有一个是桌面云。三个 VM 共享着同一批物理 CPU 核。

问题是:宿主机调度器不知道每个 VM 内部在干什么

数据库 VM 可能正在处理一个关键事务,它的 vCPU 急需被调度——但宿主机调度器看不到这个信息,它只是公平地轮转所有 vCPU 线程。于是数据库的延迟多出了几个毫秒,而这在金融交易场景里可能就是不可接受的。

反之亦然。HPC VM 的 vCPU 可能在空转,但宿主机不知道,仍然给它分配了时间片,导致资源浪费。

这就是虚拟化领域著名的 "双重调度(Double Scheduling)"问题。十几年来,人们提出了各种方案:

  • ❌ 早期方案:让 KVM(内核虚拟机)内嵌调度策略 → 被拒绝,KVM maintainer 说"调度策略不该是 KVM 的事"
  • ❌ 中期方案:KVM 做握手 + 内核模块做策略 → 又被拒绝,因为握手逻辑也不该进 KVM
  • ✅ 最终共识:VMM 做握手 + BPF 调度器做策略

而这个 BPF 调度器方案,正是本文的主角——sched_ext


二、sched_ext 是什么?一句话版

sched_ext = “插件化调度器框架”

它允许你像装一个 App 一样,动态加载一个用 BPF(Berkeley Packet Filter) 写的 CPU 调度器,而不需要编译内核、不需要重启机器

想试试一个新的调度算法?写几十行 BPF 代码,跑一个二进制文件,它就生效了。不满意?再跑一个命令就切回去了。就这么简单。

这个功能在 Linux 6.12(2024 年下半年)正式合入主线,背后站着 Meta、Google、Valve 等大厂。


三、它背后的设计哲学

为什么传统方式不行?

Linux 的默认调度器(EEVDF/CFS)是一个通用调度器——它试图在所有场景下都表现良好。但现实世界是:

场景
想要什么
数据库 VM
极低延迟,vCPU 醒了就要跑
HPC 计算
最大吞吐,少打断
桌面云
交互流畅,不卡顿
批处理任务
有饭吃就行,别抢 CPU

一个调度器搞定所有?不可能。

sched_ext 的设计哲学就是:不要试图用一个调度器取悦所有人,而是让大家自己写调度器。

核心技术:DSQ(Dispatch Queue,分发队列)

sched_ext 引入了一个聪明的抽象——DSQ,它是 BPF 调度器和内核核心调度器之间的缓冲层:

BPF 调度器(策略层)     ↕  通过 DSQ 通信内核核心调度器(执行层)     ↕物理 CPU

DSQ 有三种内置类型:

  • SCX_DSQ_GLOBAL
     — 全局 FIFO 队列
  • SCX_DSQ_LOCAL
     — 每个 CPU 独占的 FIFO 队列
  • 自定义 DSQ
     — BPF 自己创建管理的队列

无论 BPF 调度器做出什么复杂决策,最终每个 CPU 只是从自己的本地 DSQ 里取出任务执行——简单、确定、高效

调度四步曲

一个任务从醒来(wakeup)到最终在 CPU 上执行,经过四个阶段:

① select_cpu()  →  给"即将醒来"的任务挑个 CPU② enqueue()     →  把任务放入某个队列③ dispatch()    →  CPU 空闲了,从队列里取出任务④ CPU 执行

每个阶段都是一个 BPF 回调,你可以在任何一个阶段插入自己的调度逻辑。


四、子调度器(Sub-Scheduler):让调度器可以"套娃"

如果说 sched_ext 是"你可以自己写调度器",那子调度器就是"你可以写一堆调度器,让它们协作"

为什么需要子调度器?

回到开头的场景:一台物理机上有数据库 VM、HPC VM、桌面云 VM。

宿主机├── cgroup /vm/db-vm       → 我需要低延迟调度!├── cgroup /vm/hpc-vm      → 我需要最少噪声!└── cgroup /vm/desktop-vm  → 我需要交互流畅!

子调度器的方案:

根调度器 (scx_layered)  ← 控制总带宽    │    ├── /vm/db-vm        ← 子调度器 A: deadline-aware    ├── /vm/hpc-vm       ← 子调度器 B: scx_tickless    └── /vm/desktop-vm   ← 子调度器 C: scx_lavd

每个 VM 跑着自己的专属调度器,互不干扰

核心机制:scx_bpf_sub_dispatch()

父调度器通过调用这个函数,把 CPU 控制权交给子调度器:

c
// 父调度器的 dispatch 回调voidBPF_STRUCT_OPS(parent_dispatch, s32 cpu, ...){// 先调度自己的任务    ...// 把 CPU 交给数据库 VM 的子调度器    scx_bpf_sub_dispatch(cgroup_of_db_vm, ...);// 把 CPU 交给 HPC VM 的子调度器    scx_bpf_sub_dispatch(cgroup_of_hpc_vm, ...);}

关键点:父调度器决定"谁获得 CPU",子调度器决定"拿到 CPU 后跑谁"。这是层次化带宽控制的最优雅实现。

安全隔离

  • 最多嵌套 4 层(防止栈溢出)
  • 子调度器挂了 → bypass 级联向上传播,保证系统不卡死
  • 父调度器挂了 → 所有子调度器自动 fallback 到 EEVDF

Linux 7.2 的新基础设施

2026 年 6 月合入的 Linux 7.2 为子调度器铺好了路:

组件
作用
Topological CPU IDs (CIDs)
按拓扑排序的 CPU 编号,子调度器内部使用
cmask
CID 空间的 bitmap,管理亲和性和空闲 CPU
BPF Arena 集成
BPF 调度器和内核共享内存,避免数据搬运
scx_qmap 重写
示例调度器展示新接口用法

五、挑战与展望

当然,任何新技术都不是银弹:

挑战
说明
调试难度
嵌套 4 层调度器时出问题,定位谁的责任不容易
性能开销
子调度器嵌套调用有上下文切换成本
成熟度
很多调度器还标记为 experimental
ABI 不稳定
sched_ext API 不承诺向后兼容,需随内核更新

但方向是明确的——从 LKML 到 LPC,从 Meta 到 Google,社区已经在这条路上投入了大量资源。


六、写在最后

sched_ext 的子调度器代表了 Linux 内核调度领域的一次范式转变

从 “一个调度器管所有” 到 “可组合的调度器层级”

对虚拟化平台而言,这意味着:

  • ✅ 每个 VM 跑专属调度器
  • ✅ CPU 弹性共享,无需硬分区
  • ✅ Host 能感知 Guest 负载(pvSched)
  • ✅ 调度策略热切换,零重启

KVM 社区已经明确表态:调度策略是 sched_ext 的事,不是 KVM 的事


参考来源:

  • Phoronix: Linux 7.2 sched_ext
  • LWN: Sub-schedulers for sched_ext
  • sched-ext OVERVIEW.md
  • LPC 2024: Paravirtual Scheduling