sched discussion
[PATCH v9 0/6] Introduce per-CPU debugfs files
LLM 分析
Linux scheduler:scheduler debugfs 内存安全与 per-CPU 调试文件
系列概况
- 标题:[PATCH v9 0/6] Introduce per-CPU debugfs files
- 作者:Aaron Tomlin atomlin@atomlin.com
- 版本:v9(共 9 个迭代版本;本帖是 v9)
- 规模:6 个补丁- 修改文件:
kernel/sched/core.c、kernel/sched/deadline.c、kernel/sched/debug.c、kernel/sched/fair.c、kernel/sched/rt.c、kernel/sched/sched.h、kernel/sched/topology.c - 代码统计:cover letter 末尾给出 +/−数字片段,按子系列大致几十到几百行;具体 diff stat 在 patch 3给出
1 file,4 insertions(+), 1 deletion(-),patch 2 给出2 files, 3 insertions(+), 2 deletions(-),patch 4 给出1 file, 2 insertions(+),patch 5 给出3 files, 83 insertions(+), 40 deletions(-),patch 6 给出1 file, 43 insertions(+) - Message-ID:
20260827221809.988394-1-atomlin@atomlin.com(6 个 patch 的子 ID 在同 thread_key 下递增 2..7) - 完整性:thread 只包含 cover letter + 6 个 patch,缺独立回复;本帖仅展示该系列 v9 的代码改动,无 maintainer 收尾讨论
补丁目的
/sys/kernel/debug/sched/debug 在打印每个 CPU runqueue 状态时,存在多处不加锁的指针解引用,可能在并发 hot-unplug、cgroup cpuset repartition、任务退出、cfs_rq 重新挂链等场景下触发 use-after-free、TOCTOU 或者 RCU 遍历违例。本系列同时修这些隐患并新增针对单个 CPU 的 debugfs 入口 /sys/kernel/debug/sched/cpu/cpu<N>/debug,离线时返回 -ENODEV,便于排查特定 CPU 的调度延迟异常。
旧流程的问题
+-------------------+ lockless read
| debugfs reader | rcu not held
+---------+---------+
|
v +-------------------+ race window
| print_dl_rq()/... |----> use-after-free / TOCTOU
+-------------------+ (rd / curr->pid / p->mm)
|
v
+-------------------+
| leaf_cfs_rq_list |----> backward jump, RCU stall
+-------------------+
新流程
+-----------------------+ RCU read lock (guard(rcu))
| debugfs reader | + rcu_dereference_root_domain()
+----------+------------+ + rcu_dereference(rq->curr)
| + task_lock(p)
v
+-----------------------+ task_group / cfs_rq safe via
| print_*() helpers |----> call_rcu() and READ_ONCE via
+----------+------------+ list_for_each_entry_rcu()
|
v
+-----------------------+
| circuit breaker4096 |----> truncation notice
+-----------------------+
/* Patch 2 节选(print_dl_rq) */
guard(rcu)();
dl_bw = &rcu_dereference_root_domain(cpu_rq(cpu)->rd)->dl_bw;
/* Patch 3 节选(print_cpu) */
rcu_read_lock();
SEQ_printf(m, " .%-30s: %ld\n", "curr->pid",
(long)(task_pid_nr(rcu_dereference(rq->curr))));
rcu_read_unlock();
/* Patch 5 节选(print_cfs_stats) */
#define for_each_leaf_cfs_rq_rcu(rq, cfs_rq) \
list_for_each_entry_rcu(cfs_rq, &(rq)->leaf_cfs_rq_list, \
leaf_cfs_rq_list)
Patch 概览
- 1/6:
sched: Annotate rq->rd with __rcu,新增rcu_dereference_root_domain()宏,更新 core/deadline/fair/topology 中的rq->rd解引用,sparse 与 lockdep-RCU 干净- 2/6:sched/debug: Protect lockless rq->rd access in print_dl_rq(),在 writer 侧rcu_assign_pointer(rq->rd, rd),reader 侧guard(rcu)()+rcu_dereference() - 3/6:
sched/debug: Protect lockless rq->curr access in print_cpu(),把task_pid_nr(rq->curr)包在 RCU 临界区 - 4/6:
sched/debug: Protect p->mm access in sched_show_numa(),用task_lock(p)/task_unlock(p)避免exit_mm()并发导致的 TOCTOU - 5/6:
sched/fair: Use list_for_each_entry_rcu() in print_cfs_stats(),引入for_each_leaf_cfs_rq_rcu()、guard(rcu)()、SCHED_DEBUG_MAX_ITER=4096熔断 + 截断提示 - 6/6:
sched/debug: Introduce per-CPU debugfs files,新增/sys/kernel/debug/sched/cpu/cpu<N>/debug,离线 CPU 返回-ENODEV
关键实现
/* Patch 6 per-CPU debugfs 创建 */
static int sched_debug_cpu_show(struct seq_file *m, void *v)
{
unsigned long cpu = (unsigned long)m->private;
if (!cpu_online(cpu))
return -ENODEV;
print_cpu(m, cpu);
return 0;
}
static __init void debugfs_cpu_init(void)
{
struct dentry *d_cpu_dir = debugfs_create_dir("cpu", debugfs_sched);
for_each_possible_cpu(cpu) {
char buf[16];
snprintf(buf, sizeof(buf), "cpu%lu", cpu);
debugfs_create_file("debug", 0444,
debugfs_create_dir(buf, d_cpu_dir),
(void *)cpu, &sched_debug_cpu_fops);
}
}
rcu_dereference_root_domain() 把 sched_domains_mutex 与 rcu_read_lock_sched() 两个语境统一校验,绕开之前 lockdep_is_held(&rq->__lock) 的误用(Peter指出)。SCHED_DEBUG_MAX_ITER 是“per-CPU 软上限”,意图是即使 cfs_rq churn 也能让 RCU 临界区终结。
类比
把 /sys/kernel/debug/sched/debug 想象成医院的总检大厅:所有病人都在同一张长椅上等候,每叫一个号就要顺手翻他的病历(rq->rd、rq->curr、p->mm),但病历随时可能被转科、被归档。本系列相当于给总检大厅装上叫号锁(RCU 读锁)和病历夹保险扣(task_lock/list_for_each_entry_rcu),让翻阅动作要么原子、要么熔断退出;最后的 per-CPU 文件则是新增挂号窗口,你说“只看7 号诊室”,就只把 7 号 CPU 的 runqueue 端上来,7 号不在岗就直接告诉你 ENODEV。
Highlight:风险与注意点
rcu_dereference_root_domain()在CONFIG_PROVE_RCU下若被 cpuset 路径(如dl_task_needs_bw_move)以非 RCU 上下文调用会误报,本系列在 v9 加了guard(rcu)()才消掉SCHED_DEBUG_MAX_ITER=4096是“兜底”,正常负载不应被截断;如果真实环境频繁看到 “stats truncated at 4096 iterations”,说明 leaf_cfs_rq churn 异常,应回头查 cgroup 配置- per-CPU 文件在 offline CPU 时返回
-ENODEV,不要在自动化脚本里把它当成成功读到空内容 sashiko-bot报的若干项是同一族漏洞的分支,全部都已Fixes:链回02968ccf7b80或b32e86b4301e,回头 cherry-pick 到 stable 时要按 patch 顺序合入
版本变化
- v1→v2:cover 围绕“大型 SMP 上定点调试”重写;
cpu_online()网关加入- v2→v3:print_cpu加rcu_read_lock+READ_ONCE,引入for_each_leaf_cfs_rq_rcu() - v3→v4:新增 patch 1 标
__rcu;print_dl_rq改用guard(rcu)+rcu_dereference();sched_show_numa改用task_lock/task_unlock - v4→v5:把
SEQ_printf*移到sched.h,strcpy→strscpy,SCHED_DEBUG_MAX_ITER1024→4096 - v5→v6:rebase 到
tip/sched/core (sched-core-2026-08-17) - v6→v7:删除误用
rcu_dereference_protected(... &rq->__lock),引入新宏;恢复check_update_overutilized_status()注释 - v7→v8:把剩余
rq->rd解引用(add_nr_running、__sched_setscheduler、dl_task_check_affinity)转用新宏 - v8→v9:给
dl_task_needs_bw_move()加guard(rcu)(),消除CONFIG_PROVE_RCU误报
一句话总结
本系列把 /sys/kernel/debug/sched/* 中裸读 rq->rd、rq->curr、p->mm、leaf_cfs_rq_list 的几个 race 全部用 RCU/task_lock/list_for_each_entry_rcu() 包起来,并新增 per-CPU debugfs 入口做定点诊断。