sched discussion
[PATCH RFC v2 0/24] Simple hazard-pointer implementation and torture tests
LLM 分析
我重新整理输出,控制在 200 行以内并补全全部 25 条翻译:
Hazard Pointers:内核里的"危险标记"安全回收机制
系列概况
- 标题:[PATCH RFC v2 0/24] Simple hazard-pointer implementation and torture tests
- 作者:Paul E. McKenney(paulmck@kernel.org),主要贡献者 Mathieu Desnoyers(EfficiOS),Co-developed-by Boqun Feng
- 版本:v2(RFC,2026-07-16)
- 规模:24 个 patch,1897 行新增,114 行删除
- 修改文件:
include/linux/hazptr.h、kernel/hazptr.c、kernel/rcu/hazptrtorture.c、kernel/rcu/refscale.c、kernel/torture.c、Documentation/admin-guide/kernel-parameters.txt、tools/testing/selftests/rcutorture/configs/hazptr/* - 代码统计:核心实现 197 行头文件 + 242 行 hazptr.c;torture 测试新增 682 行
- Message-ID 起始:
23e34c2e-67fd-45da-b130-e70a131a59ea@paulmck-laptop - 完整性:本帖仅含 cover letter + 24 个 patch,无评审回复邮件
补丁目的
在内核里引入轻量级 hazard pointer (HP) 实现,让读者侧无需等待完整 grace period 就能访问共享对象,回收者一旦确认"无人在读"即可立即释放内存。相比 RCU 的关键收益:HP 把回收延迟从 grace period 长度(毫秒~秒级)缩到保护者放下 hazard pointer 的瞬间。
配套提供 hazptrtorture 内核模块和 rcutorture 配置,覆盖普通进程/栈上 ctx/每 CPU 槽位/跨任务推迟释放/跨 IPI 释放/读者延迟与 CPU hotplug/stutter 抖动等场景。
旧流程的问题
- 延迟高:RCU 写者必须
synchronize_rcu()等所有读者经过 quiescent state,回收被 grace period 长度绑定。 - 写者阻塞:对延迟敏感的快速回收场景(无锁映射/对象池)不够用。
- 跨上下文恢复不直接:RCU 不提供 acquire/release 跨任务/IPI 的统一抽象。
新流程
per-CPU hazptr slot (4 slots / cacheline)
Reader (task/IRQ) | Writer
---------------------- | ---------------
guard(preempt)() |
slot = percpu.items[0] --> slot->addr = WILDCARD (Store B)
smp_mb()
addr = *addr_p (Load A)
slot->addr = addr (Store C)
ctx->slot = slot
... read *addr safely ...
smp_store_release(&slot->addr, NULL)
if (backup slot) unchain it
guard(preempt)()
跨上下文时(patch 19 之后)通过 hazptr_detach() 把 slot 搬入 backup overflow list,再由其他 task/IRQ 释放。
Patch 概览
整组 24 patch 分四条线:
- 核心 API(1, 19, 21, 22, 24):hazptr.h/hazptr.c 的 acquire/release/synchronize/detach/DEBUG
- 测试框架(3-18, 20, 23):hazptrtorture.c + selftests 配置
- 基准(2):refscale 增加 hazptr 测量
- 共享基础设施(7, 13):torture/stutter 工具与 kernel-doc
关键实现
快速路径:WILDCARD-then-load
static inline void *hazptr_acquire(struct hazptr_ctx *ctx, void * const *addr_p)
{
guard(preempt)();
slot = &percpu_slots->items[0].slot;
if (unlikely(slot->addr))
return __hazptr_acquire(ctx, addr_p);
WRITE_ONCE(slot->addr, HAZPTR_WILDCARD); /* Store B */
smp_mb();
addr = READ_ONCE(*addr_p); /* Load A */
WRITE_ONCE(slot->addr, addr); /* Store C */
ctx->slot = slot;
return addr;
}
Store B → Load A 顺序保证"我若看到 *addr_p = X,写者要么看到 WILDCARD 还未发布新对象,要么我能安全访问 X",与 Michael 2004 文献一致。
上下文切换搬运 slot
hazptr_note_context_switch() 把当前 task 还在用的 per-CPU slot 整体搬到 hazptr_overflow_list,CPU 让给别的 task 后写者扫描仍能从 backup slot 看到保护关系。overflow list 用 add_idx 在两个 hazptr_overflow_list 间翻转(flip list)防止扫描死锁。
跨上下文释放:detach + pending 链表
hazptr_detach() 把 slot 从 per-CPU 槽位挪到 backup overflow list,标记 detach_task/detach_cpu;hazptrtorture 用 struct hazptr_pending(含 hpp_hc/hpp_htp)把待释放 hazptr 通过 llist 投递给 do-pending kthread 或 IPI handler。
错误检测:CONFIG_HAZPTR_DEBUG
WARN_ONCE(warn_remote_cpu || warn_remote_task,
"Hazard Pointer (addr=%p) released on remote %s without %s. "
"Acquire: caller=%pS, pid=%d, cpu=%d. Release: pid=%d, cpu=%d.",
...);
记录 acquire 时的 pid/cpu/IP,release 时若未 detach 即跨 task/cpu 则告警——正是 patch 22 修的会触发 OOPS 的所有权问题。
整体测试拓扑
+----------------------+ +---------------------+ +----------------------+
| reader kthreads | | writer kthread | | do_pending kthread |
| (perCPU/IRQ/stack) | <--> | (pipe_count, N=10) | <--> | (llist drain) |
+----------+-----------+ +----------+----------+ +----------+-----------+
| | |
v v v
hazptr_acquire/release hazptr_torture_current hazptr_release
(per-CPU slot or stack) tracks pending updates (deferred or IPI)
类比
把 hazard pointer 想象成图书馆贴在书上的"在阅"便签:
- 读者进馆(
hazptr_acquire)先在便签上写 WILDCARD 占位,再确认书还在不在(Load A);写者换书前必须看便签,确认没人贴他才能替换。 - 写者扫描便签就知道"有人在看"还是"没人要了"——不用等图书馆闭馆(grace period)才能把旧书上架卖废品。
- 想把便签从 A 读者转给 B 读者去还书?必须先在 A 这边办"过户"(
hazptr_detach),否则保安(CONFIG_HAZPTR_DEBUG)当场拦下。 hazptrtorture就像压力测试团队:故意让很多人同时在馆里看书,又调座位又改空调(CPU hotplug/stutter/IPI),看哪次还书会丢书。
Highlight:风险与注意点
- 所有权语义:默认 acquire/release 必须同一执行上下文;跨任务/IPI 释放前必须
hazptr_detach()。patch 22 之前若忘 detach,DEBUG 会在hazptr_torture_read_unlock直接 OOPS。 - per-CPU 槽位仅 4 个:超过 4 个 hazard 的读者走 backup slot 链表慢路径。
- 模块参数耦合:
kthread_do_pending_ms与defer_modulus必须同时为 0 或同时非 0,否则 init 阶段pr_alert自动关掉 deferral。 - CONFIG_HAZPTR_DEBUG 仅测试用:每次 release 都检查 pid/cpu 一致性,不适合 production。
- stutter 干扰:跨 stutter 间隔的引用要主动清理,否则 false positive;patch 7 的
stutter_will_wait()让 hazptr 释放能避开。 - overflow list flip 必要性:单链表会让"扫描+并发删除"退化成 O(N²) 或死锁,必须靠双 list 翻转保证前进。
版本变化
v1 → v2 主要变化(来自 cover letter):
- 新增
hazptr_detach()(patch 19),让 acquire/release 可分布在不同执行上下文 - 新增执行上下文迁移的 torture 测试(patch 20/22),覆盖 detach/IPI 两条迁移路径
- 引入
CONFIG_HAZPTR_DEBUG误用检测;patch 22 修一个会导致测试 OOPS 的所有权 bug
一句话总结
RFC v2 把 hazard pointer 引入 Linux 内核做轻量安全回收,并配套跨任务/IPI/栈上 ctx 的 hazptrtorture 压测与 DEBUG 误用检测,让快速回收场景不再被 RCU 宽限期拖累。