0/2 已展开

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-ID20260814032623.3834952-1-fangqiurong@kylinos.cn
  • 完整性:patch + maintainer 回复齐全,已被 Tejun Heo 合入 sched_ext/for-7.3,回指 Fixes commit 4269c603cc26,并 Cc stable@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)要等到全局系统启动完毕才生效。中间任何「扫码后取消」,都会留下脏数据。
  • 关键点是:initexit 必须共用同一把闸刀,由同一份「业务开启」语义控制,不能分属两个开关。

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,恢复成对执行并堵住泄漏。