0/6 已展开

LLM 分析

sched/fair:把 __cfs_bandwidth_used 等迁到新 static key API

系列概况

  • 标题:[PATCH 0/3] Remove deprecated static key API in scheduler code
  • 作者:Hongyan Xia hongyan.xia@transsion.com(Transsion)
  • 版本:v1,3 个补丁(cover letter 是 [PATCH 0/3])
  • 规模:13 个文件改动,44 行新增、35 行删除(-wc -l 统计见 cover)
  • 修改文件kernel/sched/{fair.c,debug.c,sched.h,core.c,cputime.c}include/linux/sched/cputime.h,以及多个架构:arch/{arm64,loongarch,powerpc/pseries,riscv}/kernel/paravirt.carch/x86/kernel/{cpu/vmware.c,kvm.c}drivers/xen/time.c
  • 代码统计:1/3 改 4 行(kernel/sched/fair.c),2/3 改 21 行(10 文件,对称替换),3/3 改 19 行(sched.h + debug.c)
  • Message-IDcover.1784538478.git.hongyan.xia@transsion.com(编号顺序在 lore 里是 1/3 → 3/3 → 0/3 → 2/3 → 两封回复)
  • 完整性:3 个 patch 都有 diff,cover letter 列了完整 stat,两位 reviewer 已给 tag(Juergen Gross: Acked-by;Ben Segall: Reviewed-by

补丁目的

内核最近重构了 static key 基础设施,把旧的 struct static_keystatic_key_true/false() 标记为 deprecated,并引入 struct static_key_true / struct static_key_falseDEFINE_STATIC_KEY_* / static_branch_* API。新 API 在编译期区分 true/false key,类型更安全,间接调用也收敛得更清晰。

这套 patch 系列做一次扫荡式迁移:把 scheduler 路径上还残留的旧 API 全部替换成新 API,没有功能改动

旧流程的问题

  • struct static_key 直接作为全局变量,每次 static_branch_*() 都要包 static_key_*() wrapper,类型和"启用/未启用"语义都耦合在同一个结构里。
  • static_key_true() / static_key_false() 命名本身就是带偏见的,调用方和阅读者都要靠前缀揣测语义。
  • SCHED_FEAT 还把整个 sched_feat_keys[] 声明成 struct static_key 数组,但每个元素语义可能是 true 也可能是 false——一个数组元素没法统一表达两种类型。
  • 多个架构各自有启用 paravirt steal clock 的代码分支,共用同一对旧 API,难看、易忘。

新流程

  • 1/3 把 __cfs_bandwidth_used 换成 DEFINE_STATIC_KEY_FALSE,访问改 static_branch_unlikely,inc/dec 改 static_branch_*_cpuslocked
  • 2/3 把 paravirt_steal_{rq,}enabled 在所有架构上的定义/声明/操作全部换成 static_key_false 家族,再加 DECLARE_STATIC_KEY_FALSE 暴露到头文件。
  • 3/3 在 sched_feat_keys[] 上用 union sched_feat_key 包装 static_key_true / static_key_false 两种 variant,给 SCHED_FEAT 生成对应版本的 static_branch_*() 入口。
  • 调用点 sched_feat(x) 不变;但 sched_feat_enable/disable() 这两行暂时仍用旧 API,作者明确承认"sadly they remain on the old API"。

Patch 概览

1/3  kernel/sched/fair.c         (cfs_bandwidth_used path)
       |__cfs_bandwidth_used
2/3  arch/*/paravirt.c            paravirt_steal_{rq,}enabled
     kernel/sched/{core,cputime}.c
     include/linux/sched/cputime.h
     drivers/xen/time.c
3/3  kernel/sched/{sched.h,debug.c}
       sched_feat_keys[] via union sched_feat_key

关键实现

1/3 kernel/sched/fair.c

-static struct static_key __cfs_bandwidth_used;
+static DEFINE_STATIC_KEY_FALSE(__cfs_bandwidth_used);

 static inline bool cfs_bandwidth_used(void)
 {
-       return static_key_false(&__cfs_bandwidth_used);
+       return static_branch_unlikely(&__cfs_bandwidth_used);
 }

作用:未启用 cgroup bandwidth control 时,跳过 unthrottle_cfs_rq 和配额统计;inc/dec 路径改成新版 static_branch_*_cpuslocked,对外接口保持不变。

2/3 paravirt_steal_*

/* kernel/sched/{core,cputime}.c */
-struct static_key paravirt_steal_rq_enabled;
+DEFINE_STATIC_KEY_FALSE(paravirt_steal_rq_enabled);

-if (static_key_false(&paravirt_steal_rq_enabled)) {
+if (static_branch_unlikely(&paravirt_steal_rq_enabled)) {

各架构的 pv_time_init / vmware_cpu_down_prepare / xen_time_setup_guest / KVM x86_hyper_kvm 注册处把 static_key_slow_inc 改成 static_branch_incinclude/linux/sched/cputime.hDECLARE_STATIC_KEY_FALSE 暴露给 arch/xen。

3/3 sched_feat_keys[]

最棘手的一个。features.hSCHED_FEAT(name, enabled) 可以展开成 enabled ∈ {true,false},数组元素类型必须统一:

union sched_feat_key {
    struct static_key_true  key_true;
    struct static_key_false key_false;
};

#define sched_feat_branch_true(key)  static_branch_likely(&(key)->key_true)
#define sched_feat_branch_false(key) static_branch_unlikely(&(key)->key_false)

#define SCHED_FEAT(name, enabled)                                       \
static __always_inline bool                                             \
static_branch_##name(union sched_feat_key *key)                         \
{                                                                       \
        return sched_feat_branch_##enabled(key);                        \
}

extern union sched_feat_key sched_feat_keys[__SCHED_FEAT_NR];

debug.c 的初始化宏相应改为:

#define jump_label_key__true  { .key_true  = STATIC_KEY_TRUE_INIT  }
#define jump_label_key__false { .key_false = STATIC_KEY_FALSE_INIT }

union sched_feat_key sched_feat_keys[__SCHED_FEAT_NR] = { ... };

sched_feat_disable/enable 处由于暂时还没法干净替换,只能 (struct static_key *)&sched_feat_keys[i] 强转,绕过类型差异——这正是本系列"还剩两条线"的原因。

类比

  • 把旧 static_key 想象成一面没有挂旗的旗杆static_key_true() 是手写的红色飘带,static_key_false() 是手写的白色飘带,每次写代码都要在脑子里查颜色。新 API 把旗杆拆成"红杆"和"白杆"两种,你想用哪个颜色就直接拿对应的杆,编译器帮你查。
  • sched_feat_keys[] 像一排双面门牌:每家门牌可能是"营业"也可能是"休业",可是门牌号只能有一种字形。union sched_feat_key 就相当于把门牌做成翻板,正面写"营业",翻过去是"休业",一排门牌共用一个格子。
  • Juergen Gross 给 Acked-by、Ben Segall 给 Reviewed-by,就像两位主厨在 tasting 一盘摆盘只是换了盘子、菜没变的菜,确认味道还是原来的。

Highlight:风险与注意点

  • sched_feat_enable/disable(struct static_key *)&sched_feat_keys[i] 强转是已知技术债:作者在 commit message 里直接承认。这种 reinterpret 在新版 struct static_key_* 内部分布局变了之后,可能导致 inc/dec 操作点到错误的内存。后续需要真正实现 sched_feat_key_enable/disable helper 或者重载 static_key_enable_cpuslocked 接受 union。
  • DECLARE_STATIC_KEY_FALSE 要求头文件已经被 <linux/jump_label.h> 包含。2/3 在 include/linux/sched/cputime.h 里手动加了一行 #include <linux/jump_label.h>——本系列还好,如果将来调度头文件对外露出更多 key,可能需要在 linux/sched.h 集中 include。
  • 这套替换是 API 同步迁移,技术上一对一等价;任何架构的 paravirt 路径如果走 custom CPU hotplug 锁路径,仍要核对 *_cpuslocked 后缀是否真的对应正确的锁——目前 inc 改成 static_branch_inc(无 _cpuslocked),要确保调用方已经持锁。Cover letter 没逐一审计这个前提,需要 maintainer 复核。
  • 没有对每个编译配置(CONFIG_JUMP_LABEL 关闭、CONFIG_CFS_BANDWIDTH=nCONFIG_PARAVIRT=n 等)单独做 CI;理论上 0-day 会跑,但 mailing list 里只有两位 reviewer 的 tag,没有维护者合入信号。
  • 这一系列没有真实"bug",但若 deprecated API 在未来内核版本被彻底移除,本系列相当于预防性补丁;mention 这种"为未来的编译警告/构建失败做准备"的语义在 cover letter 里几乎没提,建议补一段说明。

版本变化

目前只有 v1。后续可能的 v2 改动方向:

  • 真正把 sched_feat_enable/disable 改造成 union 友好的 helper,去掉强转 struct static_key *
  • 给 cover letter 加一段"为何做这次清理"(deprecation timeline、treewide static-key cleanup 的关联)。
  • DECLARE_STATIC_KEY_FALSE 在头文件里的暴露做得更对称,可能把 include/linux/jump_label.h 集中挪到 linux/sched.h

一句话总结

这套 3-patch 系列把 scheduler 路径上残留的 deprecated struct static_key 调用一次性迁到 static_key_true/false 新 API;多数改法机械,唯一卡点在 sched_feat_keys[] 用 union 包装两种 variant,剩余两行强转留给后续跟进的二阶段。