0/4 已展开

LLM 分析

sched_ext:scx_cgroup_lock() 死锁修复与 6.18-stable 回迁

系列概况

  • 主题:sched_ext 锁顺序修复 + stable 回迁
  • 主线作者:Tejun Heo tj@kernel.org
  • Stable 作者:Sasha Levin sashal@kernel.org
  • Stable 通知:Greg KH 自动化机器人
  • 版本:6.18-stable (6.18.y)
  • 规模:1 封 apply-failed 通知 + 1 套 3 段 stable 补丁系列
  • 修改文件:kernel/sched/ext.c(主线原文件路径是 kernel/sched/ext/ext.c
  • Message-ID:2026081715-muscular-drizzly-bfad@gregkh20260819111420.3549656-1/2/3-sashal@kernel.org
  • 完整性:完整,含主线 commit 5f8b69642d18e1 与两个前置 stable-dep 0454a604b98a9dbd542a8fac7 的 commit message

补丁目的

修复 sched_ext 在 scx_cgroup_lock() 中错误锁顺序引发的可触发死锁,并把修复连同两个前置 stable-dep 一起回迁到 6.18-stable。

旧流程的问题

scx_cgroup_lock() 的旧顺序是:

percpu_down_write(&scx_cgroup_ops_rwsem);
cgroup_lock();

这会和 kernfs 形成 ABBA 闭环:

  • scx enable 路径持 cgroup_lock(),等待 rmdir 释放 cgroup_mutex
  • rmdir 在 kernfs_drain() 中等待 cpu.weight 写者的 active 引用
  • cpu.weight 写者在 scx_group_set_weight() 里等待 rwsem 上排在前面的 enable writer

三方互相等,整条链路僵死。

新流程

cgroup_lock();
percpu_down_write(&scx_cgroup_ops_rwsem);

理由:set_* 路径在 read 侧不取任何 cgroup 锁,所以一旦 write-lock 进入等待,它前面阻挡它的只是"必然短小且跑完就释放"的读段;不再有任何路径从 rwsem 反向依赖 cgroup_mutex

 +----------------------------+
 |  cgroup teardown (rmdir)   |
  | |
  |  holds cgroup_mutex |
  |  waits in kernfs_drain()   |
  +-------------+--------------+
                |
                v +----------------------------+
  |  writer (cpu.weight)       |
  |  blocks on scx_cgroup_ops_ |
  |  rwsem (writer queue)      |
  +-------------+--------------+
                ^
 |  wakes after read sections
                |  (no cgroup_lock dependency)
                |
  +-------------+--------------+
  |  scx_enable()              |
  |  takes cgroup_lock() FIRST |
  |  then rwsem write |<-- loop broken here
  +----------------------------+

Patch 概览

主线作者 Tejun Heo 的修复(5f8b69642d18)依赖两个前置 commit;Sasha Levin 把它们重新打成 [PATCH 6.18.y 1/3]2/33/3 一起投递到 stable 队列:

  • 1/3:把 scx_init_task()p->scx.disallow 的告警翻转 if/else
  • 2/3:为多 scheduler 重构 enable/disable 路径,把 scheduler 实例显式化
  • 3/3:原始的 cgroup_lock() 顺序修复,删除嵌套 #ifdef,并落到 6.18-stable 还在用的 kernel/sched/ext.c

关键实现

scx_cgroup_lock() 内部调用顺序对调:

static void scx_cgroup_lock(void)
{
    cgroup_lock();
    percpu_down_write(&scx_cgroup_ops_rwsem);
}

static void scx_cgroup_unlock(void)
{
    percpu_up_write(&scx_cgroup_ops_rwsem);
    cgroup_unlock();
}

2/3scx_disable() 改成接受 @sch 参数,全局 scx_root 的 RCU 查找集中到 sysrq 与 BPF 卸载路径;只有当 scx_claim_exit() 成功时才排队 disable_work,避免重复触发。sysrq 处理器在没有 scheduler 加载时打印 sched_ext: BPF schedulers not loaded

1/3 把原来的 if (!fork) ... else if (SCHED_EXT) warn 翻转为 if (unlikely(fork)) warn; else ...,fork 路径不再被 policy 屏蔽,只要 disallow 就报警。

类比

锁嵌套像进电影院:

  • 错误顺序:先在检票口排队(rwsem 写锁),等到门口才领号(cgroup_lock)。一旦检票口塞死,外面的人进不来,里面的人出不去,影院经理(rmdir)也动不了。
  • 修正顺序:先在门口领号(cgroup_lock),再排队进检票口。门口不堵之后,经理就能继续办事;检票口的队伍虽然长,但每一波放进去的人不会再回头去门口抢号。

set_* 路径"不取 cgroup 锁"就像"短排队的人不会跑去门口再领一次号"——这是修正顺序能真正打破闭环的关键前提。

Highlight:风险与注意点

  • 锁顺序对称性:新顺序下,set_* 读侧必须继续保持"不取 cgroup 锁",否则会把环路接回来;后续任何在 read 侧引入 cgroup 操作的改动都需要重做死锁分析。
  • stable 树路径差异:主线把 ext 目录拆到 kernel/sched/ext/ext.c,但 6.18-stable 还是老的 kernel/sched/ext.c3/3 显式删除了嵌套的 #ifdef CONFIG_EXT_GROUP_SCHED(函数本身已经在一个 #ifdef 里)。
  • 回迁顺序敏感2/3 改名了 scx_enable_workfn3/3 引用 scx_enable/scx_disable;若不按 1/3 → 2/3 → 3/3 顺序合并,stable-dep 链就会断。Sasha 在每封 patch 顶部都加了 Stable-dep-of: 5f8b69642d18
  • 可观察性:sysrq 路径从静默变成 pr_info,出问题时更容易区分"压根没加载 scx"与"reset 逻辑失败"。

版本变化

主线 v1 → Sasha 回迁过程:

  • 失败尝试:直接 git cherry-pick -x 5f8b69642d18,因 6.18-stable 仍是 kernel/sched/ext.c 而非 ext/ext.c 触发冲突,Greg KH 机器人发出 FAILED 通知。
  • 成功回迁:把 5f8b69642d18(顺序对调)+ 0454a604b98a9(warning flip)+ dbd542a8fac7(enable/disable 重构)三段依次 cherry-pick,并在 ext.c 里去掉嵌套 #ifdef
  • 函数/注释同步:scx_enable_workfn 改为 scx_root_enable_workfnscx_disable() 增加 @sch 参数,与主线 v2 行为对齐。

一句话总结

cgroup_lock() 提到 rwsem 写锁之前,断开 enable 与 rmdir 以及 cpu.weight 三方死锁闭环,并把这次修复连同两个 stable-dep 前置一起按 6.18-stable 的 kernel/sched/ext.c 目录结构重新回迁。