sched-ext discussion
[PATCH] sched_ext: Fix exit_task leak on fork failure during enable
LLM 分析
sched_ext:修复 enable 期间 fork 失败导致的 exit_task 泄漏
系列概况
- 标题:
[PATCH] sched_ext: Fix exit_task leak on fork failure during enable - 作者:Qiurong Fang
<fangqiurong@kylinos.cn> - 版本:v1(单 patch,无
[PATCH vN]前缀) - 规模:1 个 patch,1 文件变更,1 行新增 / 1 行删除
- 修改文件:
kernel/sched/ext/ext.c - 代码统计:
+1 / -1 - Message-ID:
20260814032623.3834952-1-fangqiurong@kylinos.cn - 完整性:patch + maintainer 回复齐全,已被 Tejun Heo 合入
sched_ext/for-7.3,回指 Fixes commit4269c603cc26,并 Ccstable@vger.kernel.org(v6.12+)。
补丁目的
sched_ext 在引入 scx_ops_init_task() 与 scx_init_task_enabled 拆分后(commit 4269c603cc26),scx_fork() 在 scx_init_task_enabled 置位时就会调用 ops.init_task(),但配套的 scx_cancel_fork() 仍然以 scx_enabled() 作为是否调用 ops.exit_task() 的门禁。
这意味着在 enable 序列的某个窗口内(已经释放 scx_fork_rwsem、但 __scx_enabled 尚未置位),如果一个 fork 失败被 scx_cancel_fork() 接管,那么任务已经跑过 init_task(),却永远不会跑 exit_task()。任何依赖 init_task/exit_task 配对的 BPF scheduler(例如在 init_task 中做引用计数、链表插入、资源分配)都会留下永久泄漏。
本 patch 把 gate 从 scx_enabled() 改成 scx_init_task_enabled,使 init_task/exit_task 的可达性条件重新对齐。
旧流程的问题
scx_fork() 入口先看 scx_init_task_enabled,而 scx_cancel_fork() 仍然看 scx_enabled()。这之间存在一个 init_task 跑了、exit_task 不会跑 的窗口:
scx_fork() scx_cancel_fork()
| |
| gate: scx_init_task_enabled | gate: scx_enabled()
| -> ops.init_task(p) |
v v
fork() fails -------------------------> cancel path X ops.exit_task(p) NOT called
|
v
ops.init_task was already called -> leaked per-task state
新流程
把 scx_cancel_fork() 的 gate 也对齐到 scx_init_task_enabled,确保谁进入 init_task,谁就一定能在 fork 失败路径上拿到 exit_task:
scx_fork() scx_cancel_fork()
| |
| gate: scx_init_task_enabled | gate: scx_init_task_enabled
| -> ops.init_task(p) |
v v
fork() fails -------------------------> cancel path
| ops.exit_task(p) called
v paired teardown, no leak
关键实现
ext.c 中相关逻辑:
void scx_post_fork(struct task_struct *p)
{
- if (scx_enabled()) {
+ if (scx_init_task_enabled) {
...
}
}
补丁正文里写的是「Gate scx_cancel_fork() on scx_init_task_enabled」,与上下文 diff 一致:让 fork/cancel 两个分支用同一个 gate,避免 init_task 已经做过、exit_task 永远不会被调用造成的资源/状态泄漏。
类比
- 想象入住酒店:前台给每间房发房卡(
init_task),发卡条件是「酒店开门试运营」(scx_init_task_enabled)。但退房流程(exit_task)却要等到「酒店正式营业」(scx_enabled())才启动。试营业期间客人退房,不会被扣房卡——可押金、minibar、未结清扫记录全都算进酒店负债里,永远销不掉。 - 再比如快递站点:扫描签收(
init_task)的开关已经打开,但「签收异常撤销」(exit_task)要等到全局系统启动完毕才生效。中间任何「扫码后取消」,都会留下脏数据。 - 关键点是:
init和exit必须共用同一把闸刀,由同一份「业务开启」语义控制,不能分属两个开关。
Highlight:风险与注意点
- 回归源明确:commit
4269c603cc26拆分scx_ops_init_task()与scx_init_task_enabled时,没有同步把scx_cancel_fork()的 gate 切过去,是典型的「拆分 enable 状态时漏改对偶路径」。 - 触发面:任何在
enable窗口(scx_fork_rwsem释放后到__scx_enabled置位前)发生 fork 失败的代码路径都会泄漏,包括 BPF scheduler自身在init_task中分配的资源、__set_task_scx_op_state留下的状态、scx runqueue 引用计数等。 - 验证点:需要一个 fork stress + sched_ext enable 抖动测试,例如在
enable阶段并发fork()/execve()并注入失败,断言exit_task调用次数与init_task一致;同时观察 rq 上的scx状态引用计数是否归零。 - stable 影响:Tejun 已 Cc
stable@vger.kernel.org # v6.12+,说明该问题自拆分 commit 起就存在,需要回灌所有 v6.12+ 的 stable 分支。 - 进一步审视:拆分 enable 状态时建议列出所有「曾经依赖
scx_enabled()」的入口,逐项复核是否需要切到scx_init_task_enabled,避免类似成对漏改。
一句话总结
sched_ext 在把 init_task 与 __scx_enabled 解耦后,忘了把 scx_cancel_fork() 的 gate 同步切过去,导致 enable 窗口内 fork 失败的 task 会跑 init_task 却永远不跑 exit_task;本 patch 把 gate 改成 scx_init_task_enabled,恢复成对执行并堵住泄漏。