0/59 已展开

LLM 分析

Rust 同步层与计数式中断关闭:v7.3 pull request 评审

系列概况

  • 标题:[GIT PULL] [PATCH 00/24] Rust synchronization changes for v7.3
  • 作者:Boqun Feng(合作者 Lyude Paul、Heiko Carstens、Philipp Stanner、Gary Guo 等)
  • 版本:基于 commit 5e601ab3615c...(2026-07-29),相对 tag rust-sync.20260731a,最新 commit 93ff5657a5f1...(2026-07-30)
  • 规模:1 封 cover letter + 24 个 patch + 34 封回复,共 59 封邮件
  • 修改文件:include/linux/{preempt,hardirq,spinlock,interrupt_rc,spinlock_api_smp,spinlock_api_up,spinlock_rt,kernel,h}.harch/{x86,arm64,s390,openrisc}/...kernel/{softirq,sched/core,locking/spinlock}.ckernel/irq/{Makefile,refcount_interrupt_test.c}rust/{kernel/lib.rs, kernel/interrupt.rs, kernel/sync/*, kernel/revocable.rs, helpers/*}
  • 代码统计:单 patch diff 普遍 +5~+120 行;新增文件 interrupt_rc.hkernel/irq/refcount_interrupt_test.crust/kernel/interrupt.rsrust/helpers/{interrupt,barrier}.c
  • Message-ID:<20260731203031.13679-1-boqun@kernel.org>
  • 完整性:所有 patch 都有 From/SoB 链;多数 Rust patch 带 Reviewed-by;回复中 Peter 的多封回信只有正文片段,body 字段在 lore 抓取里为空,但 message-id 完整

补丁目的

为 Rust 内核代码提供一套可与 spinlock/RCU/memory barrier 协作的 IRQ 关闭型同步原语——SpinLockIrq

它要做三件事:

  1. 让 Rust 可以写出"进入临界区自动关 IRQ、退出自动开 IRQ"的 RAII guard,不再靠手动 local_irq_save/irqrestore
  2. 把 IRQ 关闭嵌套层数记到 preempt_count 的新 8 位 HARDIRQ_DISABLE_BITS,避免嵌套 lock 时 flags 错配;
  3. 配套补全 Rust 端的 synchronize_rcu() 安全封装和基于 trait 的 generic memory barrier。

旧流程的问题

  • local_irq_save/irqrestore 需要调用者自带 flags 局部变量保存现场,多层 spin_lock_irqsave 嵌套时极易写错:
    • 例如 spin_lock_irqsave(l1, flag1) 后再 spin_lock_irqsave(l2, flag2),如果中间某一层忘了 restore,锁仍持有但 IRQ 已开 → 保护数据出现竞争;
  • scoped_guard(spin_lock_irqsave, ...) 组合时同样会出现"锁未释放、IRQ 已开"的不对称;
  • 因此 Rust 无法用 safe API 包住 spin_lock_irq_disable 这类语义,只能 unsafe 模拟。

新流程

  1. 增加 8-bit HARDIRQ_DISABLE_BITS,计数 IRQ 关闭嵌套层数;
  2. local_interrupt_disable() 增加计数,仅当"0→1"才真正 local_irq_save(flags) 并把 flags 存到 percpu local_interrupt_disable_state
  3. local_interrupt_enable() 减计数,仅当"1→0"才 local_irq_restore(flags)
  4. spin_lock_irq_disable() / spin_unlock_irq_enable() 用上述成对 API 实现;
  5. Rust 端基于它实现 LocalInterruptDisabled marker guard 与 SpinLockIrqLock 后端。
        enter (depth 0 -> 1)              leave (depth 1 -> 0)
  caller ------------------ local_interrupt_disable()
                              |  hardirq_disable_enter()  // preempt_count += HARDIRQ_DISABLE_OFFSET
                              |  if depth==1: local_irq_save -> percpu.flags
                              v
                       critical section
                              ^
  caller ------------------ local_interrupt_enable()
                                 hardirq_disable_exit()   // preempt_count -= ...
                                 if depth==0: local_irq_restore(percpu.flags)

Patch 概览

编号主题性质
01preempt: Track NMI nesting to separate per-CPU counter基础底座
02preempt: Introduce HARDIRQ_DISABLE_BITS基础底座
03preempt: Introduce __preempt_count_{sub,add}_return()基础底座
04openrisc: Include <linux/cpumask.h> in smp.hbugfix
05irq & spin_lock: Add counted interrupt disabling/enabling核心 feature
06irq: Add KUnit test for refcounted interrupt enable/disable测试
07locking: Switch to _irq_{disable,enable}() variants in cleanup guardscleanup
08sched: Remove the unused preempt_offset parameter of __cant_sleep()cleanup
09sched: Avoid signed comparison of preempt_count() in __cant_migrate()cleanup
10preempt: Introduce HAS_SEPARATE_PREEMPT_RESCHED_BITS基础底座
11/12arm64 / s390 启用 HAS_SEPARATE_PREEMPT_RESCHED_BITS架构适配
13irq: Optimize reschedule check in local_interrupt_enable()优化
14rust: Introduce interrupt moduleRust API
15rust: helper: Add spin_{un,}lock_irq_{enable,disable}() helpersRust 桥
16rust: sync: use super::* in spinlock.rscleanup
17rust: sync: Add SpinLockIrq核心 feature
18rust: sync: Introduce SpinLockIrq::lock_with() and friends性能优化
19rust: sync: Add abstraction for synchronize_rcu()Rust API
20/21rust: {revocable,sync/poll}: Use safe synchronize_rcu() abstractioncleanup
22rust: sync: Add helpers for mb, dma_mb and friendsRust API
23rust: sync: Add generic memory barriersRust API
24rust: revocable: Use LKMM atomics instead of Rust atomicscleanup

关键实现

Patch 01 — NMI 嵌套拆分

/* include/linux/hardirq.h */
DECLARE_PER_CPU(unsigned int, nmi_nesting);
...
__preempt_count_add(HARDIRQ_OFFSET);
BUG_ON(__this_cpu_read(nmi_nesting) >= 15);
__this_cpu_inc(nmi_nesting);
preempt_count_set(preempt_count() | NMI_MASK);

把 4 位 NMI 计数拆成 1 位 NMI 标志 + percpu nmi_nesting,腾出 3 位留给 HARDIRQ_DISABLE_BITS

Patch 02 — 引入 HARDIRQ_DISABLE_BITS

/* bits 16-23 hardirq disable, 24-27 hardirq count, 28 NMI flag */
#define HARDIRQ_DISABLE_BITS 8
#define HARDIRQ_DISABLE_MASK (__IRQ_MASK(HARDIRQ_DISABLE_BITS) << HARDIRQ_DISABLE_SHIFT)
#define HARDIRQ_DISABLE_OFFSET (1UL << HARDIRQ_DISABLE_SHIFT)

Patch 05 — 核心:计数式 IRQ disable

static inline void local_interrupt_disable(void)
{
    unsigned long flags;
    int new_count;

    WARN_ON_ONCE(in_nmi());
    new_count = hardirq_disable_enter();

    if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET) {
        local_irq_save(flags);
        raw_cpu_write(local_interrupt_disable_state.flags, flags);
    }
}

并新增 spin_lock_irq_disable / spin_unlock_irq_enable 两族 API(_raw_spin_lock_irq_disablespinlock_api_smp.h、RT 路径在 spinlock_rt.h、UP 路径在 spinlock_api_up.h)。

Patch 10 — HAS_SEPARATE_PREEMPT_RESCHED_BITS

/* arch/x86 */
DECLARE_PER_CPU_CACHE_HOT(unsigned long, __preempt_count);
#define PREEMPT_NEED_RESCHED (~(((unsigned long)-1L) >> 1))

64 位架构可借此把 NMI 嵌套放回 preempt_count,省去 percpu nmi_nesting

Patches 14–18 — Rust 端消费

  • kernel/interrupt.rsLocalInterruptDisabled(NotThreadSafe) zero-sized marker guard,Droplocal_interrupt_enable()
  • kernel/sync/lock/spinlock.rs::SpinLockIrqBackendbindings::spin_lock_irq_disable/unlock_irq_enable/spin_trylock_irq_disable 实现 Backend trait;
  • patch 18 的 lock_with(&LocalInterruptDisabled) 在 IRQ 已关闭时跳过 local_irq_save,基准测试显示 aarch64 上 100 次 lock 节省约 200 ns。

Patches 19–24 — RCU & memory barrier

// PATCH 19
pub fn synchronize_rcu() {
    // SAFETY: `synchronize_rcu()` is always safe to call from process context.
    unsafe { bindings::synchronize_rcu() };
}

// PATCH 23
trait MemoryBarrier<Flavour = ()> { fn run(); }
define_barrier!(Full,       mb);
define_barrier!(Read,       rmb);
define_barrier!(Write,      wmb);
define_barrier!([Dma]  Full, dma_mb);
define_barrier!([Dma]  Read, dma_rmb);
define_barrier!([Dma]  Write, dma_wmb);
define_barrier!([Smp]  Full, smp_mb);
define_barrier!([Smp]  Read, smp_rmb);
define_barrier!([Smp]  Write, smp_wmb);
pub fn mb<T: MemoryBarrier>(_: T) { T::run() }

类比

preempt_count 想成游乐园入口的"门票计数器"。每位游客刷卡进站(local_interrupt_disable),累计计数;只有第一位进站时检票员才真正关门(local_irq_save)。离场时每位都刷卡(local_interrupt_enable),最后一位离场时检票员才开门(local_irq_restore)。SpinLockIrq 就像游乐设施里借给游客的储物柜钥匙——借出时增加门票计数,归还时同步减少,出了最外层临界区大门自动复位。

Highlight:风险与注意点

  1. patch 02 SoB 链无效:Peter Zijlstra 在 <20260803091056.GB49951@noisy.programming.kicks-ass.net> 指出 From: BoqunSigned-off-by: Lyude/Boqun 顺序冲突,重发前需要修作者归属或 SoB 链。
  2. struct interrupt_disable_state 包装过度:Peter 在 <20260803092910.GD49951@noisy.programming.kicks-ass.net> 质疑这个 struct 现在只包一个 unsigned long flags,是空洞抽象;并担心把 percpu 变量 export 出去会让后续扩展更难。
  3. local_interrupt_enable() 手动 __preempt_schedule() 路径冗余:Peter 在 <20260803102155.GI49951@noisy.programming.kicks-ass.net> 指出 local_irq_restore 本来就处理了 lazy reschedule;多加这段会和 IRQ exit 的 resched 处理冲突,需要改成走标准的 local_irq_restore 通道或在退出后正确更新 PREEMPT_NEED_RESCHED
  4. HAS_SEPARATE_PREEMPT_RESCHED_BITS 后续正确性:patch 10 之后 preempt_count() 可能为高位置位(看起来像负数),所有依赖 preempt_count() < 0WARN_ON 都得改成无符号比较——patch 10 已处理 preempt_count_sub,其它路径(lockdep、tracing)需要再过一遍。
  5. lock_with() 的 lifetime 正确性:patch 18 用 core::mem::transmuteSpinLockIrq<T> 视作 SpinLock<T>,依赖 LocalInterruptDisabledGuard 同 lifetime 来保证 IRQ 不会在被持有期间被重新开启——这一不变量未来若有人修改 LocalInterruptDisabledDrop 行为(如 assume_disabled)需要小心破坏。

版本变化

  • 早期版本(patch.msgid.link 2026-01/03/05/06 系列)→ v7.3(本拉取):
    • patch 13 把 __preempt_count_{add,sub}_return() 返回值改为 unsigned long,让 local_interrupt_enable() 复用同一份结果判断是否需要 reschedule,省一次 preempt_count 读取;
    • patch 10 引入 HAS_SEPARATE_PREEMPT_RESCHED_BITS 把 64 位架构的 NMI 嵌套放回 preempt_count,配套修改 arch/s390 的 lowcore 布局和 arch/x86 的 cmpxchg 宽度;
    • patch 17 的 SpinLockIrq 在 Lyude 原设计基础上由 Boqun 改为 guard 类型并通过 #[kunit_tests] 跑 condvar 用例。

一句话总结

Boqun Feng 推送 24 patch 的 Rust 同步层更新,把 IRQ disable 嵌套层数记进 preempt_count,从而提供 Rust 端安全的 SpinLockIrqsynchronize_rcu() 和泛型 memory barrier;目前 Peter Zijlstra 在 SoB 链、interrupt_disable_state 包装和手动 reschedule 检查等几处仍有异议。