sched discussion
[GIT PULL] [PATCH 00/24] Rust synchronization changes for v7.3
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),相对 tagrust-sync.20260731a,最新 commit93ff5657a5f1...(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}.h、arch/{x86,arm64,s390,openrisc}/...、kernel/{softirq,sched/core,locking/spinlock}.c、kernel/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.h、kernel/irq/refcount_interrupt_test.c、rust/kernel/interrupt.rs、rust/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。
它要做三件事:
- 让 Rust 可以写出"进入临界区自动关 IRQ、退出自动开 IRQ"的 RAII guard,不再靠手动
local_irq_save/irqrestore; - 把 IRQ 关闭嵌套层数记到
preempt_count的新 8 位HARDIRQ_DISABLE_BITS,避免嵌套 lock 时 flags 错配; - 配套补全 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 模拟。
新流程
- 增加 8-bit
HARDIRQ_DISABLE_BITS,计数 IRQ 关闭嵌套层数; local_interrupt_disable()增加计数,仅当"0→1"才真正local_irq_save(flags)并把 flags 存到 percpulocal_interrupt_disable_state;local_interrupt_enable()减计数,仅当"1→0"才local_irq_restore(flags);spin_lock_irq_disable()/spin_unlock_irq_enable()用上述成对 API 实现;- Rust 端基于它实现
LocalInterruptDisabledmarker guard 与SpinLockIrq的Lock后端。
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 概览
| 编号 | 主题 | 性质 |
|---|---|---|
| 01 | preempt: Track NMI nesting to separate per-CPU counter | 基础底座 |
| 02 | preempt: Introduce HARDIRQ_DISABLE_BITS | 基础底座 |
| 03 | preempt: Introduce __preempt_count_{sub,add}_return() | 基础底座 |
| 04 | openrisc: Include <linux/cpumask.h> in smp.h | bugfix |
| 05 | irq & spin_lock: Add counted interrupt disabling/enabling | 核心 feature |
| 06 | irq: Add KUnit test for refcounted interrupt enable/disable | 测试 |
| 07 | locking: Switch to _irq_{disable,enable}() variants in cleanup guards | cleanup |
| 08 | sched: Remove the unused preempt_offset parameter of __cant_sleep() | cleanup |
| 09 | sched: Avoid signed comparison of preempt_count() in __cant_migrate() | cleanup |
| 10 | preempt: Introduce HAS_SEPARATE_PREEMPT_RESCHED_BITS | 基础底座 |
| 11/12 | arm64 / s390 启用 HAS_SEPARATE_PREEMPT_RESCHED_BITS | 架构适配 |
| 13 | irq: Optimize reschedule check in local_interrupt_enable() | 优化 |
| 14 | rust: Introduce interrupt module | Rust API |
| 15 | rust: helper: Add spin_{un,}lock_irq_{enable,disable}() helpers | Rust 桥 |
| 16 | rust: sync: use super::* in spinlock.rs | cleanup |
| 17 | rust: sync: Add SpinLockIrq | 核心 feature |
| 18 | rust: sync: Introduce SpinLockIrq::lock_with() and friends | 性能优化 |
| 19 | rust: sync: Add abstraction for synchronize_rcu() | Rust API |
| 20/21 | rust: {revocable,sync/poll}: Use safe synchronize_rcu() abstraction | cleanup |
| 22 | rust: sync: Add helpers for mb, dma_mb and friends | Rust API |
| 23 | rust: sync: Add generic memory barriers | Rust API |
| 24 | rust: revocable: Use LKMM atomics instead of Rust atomics | cleanup |
关键实现
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_disable 在 spinlock_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.rs:LocalInterruptDisabled(NotThreadSafe)zero-sized marker guard,Drop调local_interrupt_enable();kernel/sync/lock/spinlock.rs::SpinLockIrqBackend用bindings::spin_lock_irq_disable/unlock_irq_enable/spin_trylock_irq_disable实现Backendtrait;- 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:风险与注意点
- patch 02 SoB 链无效:Peter Zijlstra 在
<20260803091056.GB49951@noisy.programming.kicks-ass.net>指出From: Boqun与Signed-off-by: Lyude/Boqun顺序冲突,重发前需要修作者归属或 SoB 链。 struct interrupt_disable_state包装过度:Peter 在<20260803092910.GD49951@noisy.programming.kicks-ass.net>质疑这个 struct 现在只包一个unsigned long flags,是空洞抽象;并担心把 percpu 变量 export 出去会让后续扩展更难。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。HAS_SEPARATE_PREEMPT_RESCHED_BITS后续正确性:patch 10 之后preempt_count()可能为高位置位(看起来像负数),所有依赖preempt_count() < 0的WARN_ON都得改成无符号比较——patch 10 已处理preempt_count_sub,其它路径(lockdep、tracing)需要再过一遍。lock_with()的 lifetime 正确性:patch 18 用core::mem::transmute把SpinLockIrq<T>视作SpinLock<T>,依赖LocalInterruptDisabled和Guard同 lifetime 来保证 IRQ 不会在被持有期间被重新开启——这一不变量未来若有人修改LocalInterruptDisabled的Drop行为(如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 用例。
- patch 13 把
一句话总结
Boqun Feng 推送 24 patch 的 Rust 同步层更新,把 IRQ disable 嵌套层数记进 preempt_count,从而提供 Rust 端安全的 SpinLockIrq、synchronize_rcu() 和泛型 memory barrier;目前 Peter Zijlstra 在 SoB 链、interrupt_disable_state 包装和手动 reschedule 检查等几处仍有异议。