sched discussion
[PATCH v2 0/8] sched: introduce for_each_process_rculock and for_each_thread_rculock
LLM 分析
sched:引入带 RCU 锁范围的进程/线程迭代宏并大规模转换
系列概况
- 标题:[PATCH v2 0/8] sched: introduce for_each_process_rculock and for_each_thread_rculock
- 作者:Ye Liu liuye@kylinos.cn
- 版本:v2(前作 lore8 月 13 日 PATCH v1 与 9 月 4 日前身)
- 规模:8 个 patch,共 17 个文件,+54/-81
- 修改文件:
include/linux/sched/signal.h,fs/proc/base.c,fs/resctrl/rdtgroup.c,kernel/cpu.c,kernel/freezer.c,kernel/hung_task.c,kernel/locking/lockdep.c,kernel/rcu/update.c,kernel/sched/core.c,kernel/sched/debug.c,kernel/trace/fgraph.c,kernel/unwind/deferred.c,lib/is_single_threaded.c,mm/ksm.c,mm/memory-failure.c,mm/oom_kill.c,security/landlock/tsync.c - 代码统计:17 files changed, 54 insertions(+), 81 deletions(-)
- Message-ID:20260907081334.1152889-1-ye.liu@linux.dev
- 完整性:完整 8/8 patch + 7 封回复(sashiko-bot 自动审阅 2 封、Lorenzo Stoakes 4 封、Oleg Nesterov 1 封);Lorenzo 对 patch 3/8 给 Reviewed-by,Oleg 对 patch 1/8 给 Reviewed-by,Michal Hocko 全程 Acked-by,patch 2/8 由 SJ Park Review,patch 4/8 由 Miaohe Lin Ack,patch 8/8 由 Justin Suess 与 Günther Noack Review。
补丁目的
内核中现存的 for_each_process()、for_each_thread()、for_each_process_thread() 周边通常要手写 rcu_read_lock() / rcu_read_unlock() 或 guard(rcu)()。这套系列:
- 在
include/linux/sched/signal.h新增三个_rculock宏,把加解锁放进scoped_guard(rcu)作用域; - 把全内核 17 个文件 21 处手工锁配对一一改写为新宏调用;
- 解决"return/goto/多重 break 时漏写解锁"的脆弱性问题,让锁范围与循环体作用域一一对应。
旧流程的问题
每个站点都重复 rcu_read_lock() ... for_each_* ... rcu_read_unlock() 模板:
rcu_read_lock();
for_each_process(p) {
do_something(p);
if (cond)
return -EAGAIN; /* 漏解锁 */
}
rcu_read_unlock();
多重 return/goto/复杂 break 易漏写 rcu_read_unlock(),留下 RCU 临界区泄漏;反之锁范围比预期大、阻塞 writer。
新流程
for_each_process_rculock(p) {
do_something(p);
if (cond)
return -EAGAIN; /* scoped_guard 自动解锁 */
}
锁范围 = 循环体作用域,离开循环(break/goto/return)自动释放。
Patch 概览
- 1/8 sched:在
signal.h中定义三个宏本体。 - 2/8 mm/oom_kill:5 处(
oom_cpuset_eligible、select_bad_process、dump_tasks、task_will_free_mem、__oom_kill_process)。 - 3/8 mm/ksm:1 处(
collect_procs_ksm)。 - 4/8 mm/memory-failure:4 处(anon/file/fsdax/pfn),并解释
page_pgoff()在锁外仍安全。 - 5/8 kernel:9 文件(cpu/freezer/hung_task/lockdep/rcu/sched.core/sched.debug/fgraph/unwind)。
- 6/8 fs:
proc/base.c+resctrl/rdtgroup.c。 - 7/8 lib:
is_single_threaded.c。 - 8/8 security/landlock:
tsync.c两处。
关键实现
核心定义(patch 1/8):
#define for_each_process_rculock(p) \
scoped_guard(rcu) \
for (p = &init_task ; (p = next_task(p)) != &init_task ; )
#define for_each_thread_rculock(p, t) \
scoped_guard(rcu) \
__for_each_thread((p)->signal, t)
#define for_each_process_thread_rculock(p, t) \
scoped_guard(rcu) \
for_each_process(p) for_each_thread(p, t)
scoped_guard(rcu) 来自 <linux/cleanup.h>,离开作用域自动 rcu_read_unlock()。双重循环里 break 只退内层 for_each_thread(),要同时跳出两层必须 goto,这一约束被加进注释。patch 5/8 把 hung_task.c 过时的 unlock: 标签改名 out:,fgraph.c 中 goto unlock 改为 goto free,因为解锁已不再需要手动写在标签处。
典型调用(patch 4/8,collect_procs_anon):
for_each_process_rculock(tsk) {
struct task_struct *t = task_early_kill(tsk, force_early);
if (t)
add_to_kill_anon(t, page, vma, to_kill, addr);
}
类比
把 RCU 读锁想象成图书馆借阅证:
- 旧流程:进门先在柜台拿证(
rcu_read_lock),一本本翻书(for_each_process),最后必须自己记得把证交回柜台。中途换书、走开、跑去看别的房间,柜台就一直等你回来——但你可能忘了还。 - 新流程:借阅证塞进"智能卡套"(
scoped_guard(rcu)),只要你离开那个阅览区(循环作用域),卡套就会自动归还柜台。换书、跑开、临时出去都不用操心。 - 双重循环 = "先按姓氏翻目录(外层 process)再按名字翻抽屉(内层 thread)"。只
break只能跳出抽屉,要离开整区必须goto——让卡套感知到你要结束整个借阅。
Highlight:风险与注意点
- goto + scoped_guard 混用:patch 5/8 在
fgraph.c把goto unlock改为goto free,hung_task.c同步改名out:。sashiko-bot 把这条标为 Low,所有手动跳转都要确认目标仍在 guard 作用域内。 - break 仅退内层:
for_each_process_thread_rculock双重循环里break不会触发 RCU 解锁(外层 for 还在跑),需goto才能完整退出,已写进注释(Thomas Gleixner 提示)。 page_pgoff()在锁外仍安全:patch 4/8 把page_pgoff()挪到 RCU 临界区外,只读folio->index,不依赖 RCU 保护结构(SJ Park 验证)。- checkpatch 误报:patch 1 会触发"Macros with complex values should be enclosed in parentheses"警告,作者说明是
scoped_guard控制流伪语句造成的误报。 - 覆盖维护者:每封 patch 都 CC 对应子系统 maintainer(Lorenzo 建议)。
- 机械转换回归风险:21 处站点需逐个确认没有遗漏
rcu_read_lock()之外的额外语义(如preempt_disable)被错误合并。
版本变化
v1 → v2 主要改动(cover letter 列出):
- 宏名
*_rcu→*_rculock,避免与现有*_rcu()列表迭代器混淆(Steven Rostedt,Thomas Gleixner 同意)。 for_each_process_thread_rculock注释强化,指出break只退内层(Thomas Gleixner)。hung_task.c中过时的unlock:标签改名out:(Günther Noack)。- patch 4 commit message 明确
page_pgoff()在 RCU 临界区外安全(SJ Park)。 - 每封 patch 都 CC 相关 maintainer(Lorenzo Stoakes)。
- patch 1 前缀由
mm:改为sched:(Michal Hocko)。 - 校对所有 patch 的
Acked-by/Reviewed-by列表,避免漏维护者。
一句话总结
通过 scoped_guard(rcu) 把 RCU 读锁与 for_each_process/thread 系列迭代宏绑定到同一作用域,把内核 17 个文件 21 处手写锁配对统一收敛为一行宏调用,消除漏锁漏解锁的脆弱性。
+------------------+ +----------------------+
| Old: hand pair | | New: _rculock macro |
+------------------+ +----------------------+
| rcu_read_lock() | | for_each_*_rculock(p)|
| for_each_*(p) { | ==> | scoped_guard(rcu) |
| ... | | { |
| return X | | ... |
| --> leak! | | return X |
| } | | --> auto unlock |
| rcu_read_unlock()| | } |
| | | } // auto unlock |
+------------------+ +----------------------+
Double loop (for_each_process_thread_rculock):
+---------------------------------------------+
| break -> exits only inner for_each_thread |
| goto -> exits both loops + auto unlock |
+---------------------------------------------+