如何在临界区屏蔽中断?

解读

在裸机或操作系统内核层,临界区(Critical Section)是指“一段代码在执行期间不允许被任何异步事件打断”,最常见的就是屏蔽本地 CPU 中断。Rust 作为系统级语言,必须提供与 C 同等粒度的底层能力,因此面试题考察的是:

  1. 你是否理解中断屏蔽的语义与代价
  2. 能否在Safe Rust 与 Unsafe Rust 的边界上给出可移植、可组合、零额外开销的封装;
  3. 是否熟悉国内主流 Rust 嵌入式生态(riscv、xtensa、ARM-M 系列、龙芯、海光等)的惯用法。

注意:用户态线程库(tokio、async-std)里的“临界区”只是用户态锁,并不涉及关中断,答题时务必区分上下文。

知识点

  1. 指令级原语:ARM 的 cpsid i / cpsie i、RISC-V 的 csrc mstatus, MIE、x86 的 cli / sti
  2. Rust 封装原则
    • RAII 守卫(Guard) 保证“关中断”与“开中断”严格配对,杜绝早期返回或 panic 导致的死锁
    • 内联汇编(asm!)cortex_m::interrupt::free 等官方 crate,不手写裸指针
    • #[inline(always)]compiler_fence(Ordering::SeqCst) 阻止编译器重排,确保临界区边界不被优化掉
  3. 多核场景:关中断仅影响当前核,若临界区数据可能被其它核访问,必须再叠加自旋锁总线级原子操作;国内多核 RISC-V 板卡(如全志 D1-H)面试常追问这一点。
  4. 延迟与实时性:关中断会增加中断延迟,在车载、工控场景下需评估最大关中断时间;面试可提“Rust 的抢占式中断嵌套 + 优先级天花板协议”作为后续优化方向。
  5. Safe Rust 边界:关中断本质上是unsafe 行为,因为可能破坏 Rust 的“无数据竞争”前提;需要最小化 unsafe 块,并在文档中给出Safety 契约
    • 守卫生命周期内不能 await、不能阻塞;
    • 不能调用可能 re-enable 中断的函数(如某些 FFI 驱动)。

答案

下面给出可在国内任何 ARM-M 芯片(GD32、STM32、CH32、灵动微等)上直接运行的最小封装,零依赖、零成本,面试现场手写即可:

use core::arch::asm;

/// 关中断守卫,离开作用域自动恢复中断
pub struct CriticalSection {
    primask: u32,
}

impl CriticalSection {
    /// 进入临界区,返回守卫
    #[inline(always)]
    pub fn enter() -> Self {
        let primask: u32;
        unsafe {
            // 读取并关闭中断
            asm!("mrs {0}, primask", "cpsid i", out(reg) primask, options(nostack, preserves_flags));
        }
        Self { primask }
    }
}

impl Drop for CriticalSection {
    #[inline(always)]
    fn drop(&mut self) {
        unsafe {
            // 恢复之前的中断状态,而非简单开中断
            asm!("msr primask, {0}", in(reg) self.primask, options(nostack, preserves_flags));
        }
    }
}

/// 用户接口:在闭包里执行临界区代码
#[inline(always)]
pub fn critical_section<R, F: FnOnce() -> R>(f: F) -> R {
    let _guard = CriticalSection::enter();
    compiler_fence(core::sync::atomic::Ordering::SeqCst);
    let r = f();
    compiler_fence(core::sync::atomic::Ordering::SeqCst);
    r
}

使用示例:

static mut COUNTER: u32 = 0;

fn main() {
    let v = critical_section(|| {
        unsafe { COUNTER += 1; COUNTER }
    });
    println!("{}", v);
}

关键点解释

  • primask 保存与恢复保证嵌套安全;
  • compiler_fence阻止编译器把临界区外的内存访问移到里面;
  • **inline(always)**确保无函数调用开销,真正零成本抽象
  • Drop 自动恢复panic-safe,符合 Rust 的“编译通过即正确”文化。

拓展思考

  1. 多核 RISC-V 在国内的崛起:若面试提到“全志 D1-H 64 双核”,需指出关中断仅对当前 hart 有效,必须再使用AMO 原子指令SBI 提供的 IPI 机制做核间同步。
  2. Rust for Linux 内核模块:国内厂商(阿里龙蜥、openEuler)已在内核态用 Rust 写驱动,此时不能用上述用户态封装,而应使用kernel::irqsave! / kernel::irqrestore! 宏,与 C 侧的 local_irq_save 语义完全一致
  3. 实时性认证:车载 ECU 需通过ASIL-D认证,关中断时间需静态分析。Rust 可借助rtic框架的调度器静态分析工具,给出最坏情况关中断时间(WCIT)报告,这是C 语言传统手写汇编难以做到的
  4. MISRA-Rust 规范:国内某些军工单位已引入MISRA-Rust子集,要求“任何 unsafe 块必须附带形式化验证脚本”。面试可主动提及“用 Kani 或 Crucible 对临界区封装做位级验证”,体现对高可信场景的深度思考。