0/25 已展开

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.hkernel/hazptr.ckernel/rcu/hazptrtorture.ckernel/rcu/refscale.ckernel/torture.cDocumentation/admin-guide/kernel-parameters.txttools/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_cpuhazptrtorturestruct 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_msdefer_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 宽限期拖累。