sched discussion
[PATCH v4 00/17] Refcounted interrupt disable and SpinLockIrq for Rust
LLM 分析
Linux locking/scheduler:引用计数中断禁用与 Rust SpinLockIrq v4
系列概况
- 标题:[PATCH v4 00/17] Refcounted interrupt disable and SpinLockIrq for Rust
- 作者:Boqun Feng;参与者包括 Lyude Paul、Joel Fernandes、Heiko Carstens。
- 版本:v4,17 个 patch;另有 patch 05/17 的 v4.1 修订。
- 规模:初始 cover letter 报告 36 个文件,新增 1060 行、删除 91 行。
- 修改文件:涉及
include/linux/{preempt.h,hardirq.h,interrupt_rc.h,spinlock*.h}、kernel/{irq,locking,sched}、x86/arm64/s390/OpenRISC 架构代码,以及 Rust helper、interrupt 和 spinlock 模块。 - 代码统计:新增
interrupt_rc.h、中断引用计数 KUnit 测试、Rustinterrupt.rs;Rustspinlock.rs增加SpinLockIrq及其快路径。 - Message-ID:首封为
20260804161447.84806-1-boqun@kernel.org。 - 完整性:输入包含 17 个系列 patch、v4.1 修订、tip-bot 合入通知、patch 07 的后续 revert,以及随后关于 Rust IRQ API 的延伸讨论;部分后续回复正文为空,因此只能忠实标注为空回复。
补丁目的
本系列的核心目标是把嵌套的本地中断禁用从 local_irq_save()/local_irq_restore() 的“保存并恢复现场”模式,改成可计数的 disable/enable 模式。
这样,多个锁或 guard 嵌套时,只有最外层调用真正关闭硬件中断,只有最后一次 enable 才恢复原始状态,避免内层释放操作意外重新打开中断。
在此基础上,系列为 Rust 增加 LocalInterruptDisabled 标记类型和 SpinLockIrq,让 Rust 可以用生命周期表达“该锁的整个保护区内本地中断必须关闭”。
旧流程的问题
irqsave/irqrestore 的每一层调用都保存自己的 flags。若锁的释放顺序与获取顺序不匹配,内层 irqrestore 可能恢复“中断开启”的旧状态,即使另一个 IRQ-safe 锁仍然持有。
典型情况是:
spin_lock_irqsave(l1, flags1);
spin_lock_irqsave(l2, flags2);
spin_unlock_irqrestore(l1, flags1);
/* l2 仍持有,但本地中断可能已开启 */
cleanup guard 和细粒度锁的 hand-to-hand 传递会让这种问题更容易发生,也使 Rust 难以提供安全的 IRQ-safe 锁抽象。
新流程
preempt_count()增加 8-bitHARDIRQ_DISABLE字段,记录中断禁用嵌套深度。- 新增
__preempt_count_add_return()和__preempt_count_sub_return(),修改计数后直接返回新值。 - 第一次
local_interrupt_disable()保存原始 flags 并真正关闭中断;后续嵌套调用只增加计数。 - 只有计数降为零时,
local_interrupt_enable()才恢复原始 flags。 - 新增
spin_lock_irq_disable()、spin_unlock_irq_enable()和 trylock 变体。 - 32-bit 路径把 NMI 嵌套深度移到 per-CPU
nmi_nesting;支持 64-bit 计数的架构则可启用HAS_SEPARATE_PREEMPT_RESCHED_BITS。 - Rust 用 RAII guard 管理中断禁用,并用
LocalInterruptDisabled证明已处于 no-IRQ 上下文。
disable depth hardware IRQ operation
------------ ------------ ---------
0 enabled initial state
1 disabled first disable saves flags
2 disabled nested disable only increments
1 disabled inner enable only decrements
0 restored outer enable restores flags
Patch 概览
| Patch | 作用 |
|---|---|
| 01 | 把 NMI 嵌套计数移到 per-CPU 变量,释放 preempt_count 位 |
| 02 | 增加 HARDIRQ_DISABLE_BITS,为中断禁用计数分配 8 位 |
| 03 | 增加返回新计数值的 preempt 原语 |
| 04 | 修复 OpenRISC smp.h 对 cpumask.h 的隐式依赖 |
| 05 | 实现引用计数中断 API 和 IRQ-safe spinlock 变体 |
| 06 | 增加中断引用计数 KUnit 测试 |
| 07 | 将 cleanup guard 改用 _irq_disable/_irq_enable |
| 08、09 | 清理 scheduler 检查逻辑,避免无用参数和有符号比较 |
| 10–12 | 建立并在 x86、arm64、s390 上启用独立 resched 位方案 |
| 13–15 | 增加 Rust interrupt 模块、C helpers 和导入整理 |
| 16 | 增加 Rust SpinLockIrq |
| 17 | 增加 lock_with()/try_lock_with() 快路径 |
关键实现
1. 重新划分 preempt_count
普通 32-bit 路径大致变为:
#define HARDIRQ_DISABLE_BITS 8
#define HARDIRQ_DISABLE_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS)
#define HARDIRQ_DISABLE_OFFSET (1UL << HARDIRQ_DISABLE_SHIFT)
#define hardirq_disable_enter() \
__preempt_count_add_return(HARDIRQ_DISABLE_OFFSET)
#define hardirq_disable_exit() \
__preempt_count_sub_return(HARDIRQ_DISABLE_OFFSET)
NMI 嵌套不再占用多个 preempt_count 位,而由 nmi_nesting 保存;preempt_count 中保留 NMI 状态标志。
2. 引用计数中断 API
static inline void local_interrupt_disable(void)
{
int new_count = hardirq_disable_enter();
if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET)
_local_interrupt_disable();
}
static inline void local_interrupt_enable(void)
{
int new_count = hardirq_disable_exit();
if ((new_count & HARDIRQ_DISABLE_MASK) == 0)
_local_interrupt_enable();
}
_local_interrupt_disable() 只在最外层保存 per-CPU flags 并调用 local_irq_save();对应的 enable 只在最外层恢复。
3. IRQ-safe spinlock
SMP、UP 和 PREEMPT_RT 路径都增加新的 lock/unlock 变体。SMP 路径的逻辑是:
local_interrupt_disable()增加 IRQ 禁用深度;preempt_disable();- 获取 raw spinlock;
- 解锁时先释放 spinlock;
local_interrupt_enable()减少 IRQ 禁用深度;preempt_enable()。
v4.1 删除了 trylock 失败/成功路径中多余的 spin_acquire(),避免 lockdep 状态重复记录。
4. 独立 PREEMPT_NEED_RESCHED 位
启用 HAS_SEPARATE_PREEMPT_RESCHED_BITS 时,64-bit 架构将计数扩展到 64-bit,并把 reschedule 状态放在独立字中。这样可以让 preempt_count 使用更多有效位,同时避免更新 reschedule 标志时破坏计数。
arm64 原本已经采用类似布局,因此只需启用 Kconfig。s390 则修改 lowcore 布局和汇编操作,从 32-bit preempt count 转为 64-bit。
5. Rust 类型封装
Rust 新增:
LocalInterruptDisabled:零大小、不可在线程间转移的中断关闭凭证;SpinLockIrq<T>:使用spin_lock_irq_disable()的锁;SpinLockIrqGuard:释放时解锁并减少 IRQ 禁用计数;new_spinlock_irq!():初始化宏。
pub fn local_interrupt_disable() -> LocalInterruptDisabled {
unsafe { bindings::local_interrupt_disable() };
LocalInterruptDisabled(NotThreadSafe)
}
pub fn lock_with<'a>(
&'a self,
context: &'a LocalInterruptDisabled,
) -> SpinLockGuard<'a, T> {
self.as_lock_in_interrupt(context).lock()
}
lock_with() 要求调用者提供与 guard 同生命周期的 LocalInterruptDisabled 引用,因此在锁仍然存活时不能重新打开本地中断。
类比
旧的 irqsave/irqrestore 像每个房间各自保管一把“电闸钥匙”:房间 A 和 B 嵌套进入后,A 先离开并恢复自己的钥匙状态,可能把整栋楼的电闸重新打开,即使 B 还在进行危险操作。
新 API 像大楼总控室维护一个进入深度:
- 第一个人进入时拉下总闸并保存原状态;
- 后续人员只登记人数;
- 中间人员离开只减少人数;
- 最后一个人离开时才恢复总闸。
LocalInterruptDisabled 则是总控室发出的凭证。持有凭证时,Rust 可以调用 lock_with(),直接使用普通 spinlock 后端,不必重复切换中断状态。
Highlight:风险与注意点
- patch 07 已被 revert:cleanup guard 的生命周期和
_irq_disable/_irq_enable语义存在不兼容,Peter Zijlstra 后续提交了 revert,并进入locking/urgent。 - 计数溢出问题仍需关注:
HARDIRQ_DISABLE只有 8 位,理论最大深度为 256;讨论中没有形成通用运行时溢出检查,因为检查本身可能获取 IRQ-safe 锁并产生递归问题。 - NMI 深度限制没有消失:32-bit 路径仍检查最大 15 层;64-bit 路径利用
preempt_count中的额外位表达 NMI 状态。 - 架构布局必须同步:
preempt_count、NMI_MASK和PREEMPT_NEED_RESCHED的定义在内核与 BPF selftest 头文件中必须保持一致。 transmute依赖布局等价:SpinLockIrq和SpinLock当前都以spinlock_t为底层状态;若以后 Rust 锁结构布局变化,as_lock_in_interrupt()的安全前提必须重新审查。- PREEMPT_RT 语义不同:RT 路径直接使用
rt_spin_lock()/rt_spin_unlock(),不能简单套用非 RT 路径的硬件中断切换假设。 - 性能数据仍属微基准:
lock_with()在部分 arm64 测试中更快,但 x86 测试有时仅持平;真实工作负载收益尚未确定。 - 后续方向发生变化:Thomas Gleixner 随后提出把
local_irq_enable/disable()本身迁入 Rust,说明最终 API 形态仍可能继续调整。 - 署名和提交链曾有问题:Peter 指出 patch 02 的
From与 Signed-off-by 顺序不正确,Boqun 表示会修正。
版本变化
- v3 → v4:删除
local_interrupt_enable()中的preempt_count检查;简化 per-CPU 状态结构;调整 CFS timer 调用方;完善HAS_SEPARATE_PREEMPT_RESCHED_BITS文档;更新 s390 汇编注释;修正署名、格式和链接。 - v4 → v4.1:删除
_raw_spin_trylock_irq_disable()中多余的spin_acquire()。 - v4 → tip/locking/core:多个系列 patch 被 tip-bot 通知进入
locking/core。 - tip/locking/core → locking/urgent:patch 07 因 cleanup guard 回归被单独 revert。
- 后续 Rust IRQ patch:Thomas Gleixner 在 2026-09-04 提交
irq: Move local_irq_enable/disable() into Rust,并继续与 Boqun 讨论设计方向。
一句话总结
该系列为 Linux 建立了以 preempt_count 为基础的引用计数中断禁用机制,并为 Rust 提供 SpinLockIrq 与类型安全的 no-IRQ 凭证;但 cleanup guard 迁移已被 revert,整体 Rust IRQ API 仍在继续演进。