如何在临界区屏蔽中断?
解读
在裸机或操作系统内核层,临界区(Critical Section)是指“一段代码在执行期间不允许被任何异步事件打断”,最常见的就是屏蔽本地 CPU 中断。Rust 作为系统级语言,必须提供与 C 同等粒度的底层能力,因此面试题考察的是:
- 你是否理解中断屏蔽的语义与代价;
- 能否在Safe Rust 与 Unsafe Rust 的边界上给出可移植、可组合、零额外开销的封装;
- 是否熟悉国内主流 Rust 嵌入式生态(riscv、xtensa、ARM-M 系列、龙芯、海光等)的惯用法。
注意:用户态线程库(tokio、async-std)里的“临界区”只是用户态锁,并不涉及关中断,答题时务必区分上下文。
知识点
- 指令级原语:ARM 的
cpsid i / cpsie i、RISC-V 的csrc mstatus, MIE、x86 的cli / sti。 - Rust 封装原则:
- 用 RAII 守卫(Guard) 保证“关中断”与“开中断”严格配对,杜绝早期返回或 panic 导致的死锁;
- 用 内联汇编(asm!) 或 cortex_m::interrupt::free 等官方 crate,不手写裸指针;
- 用 #[inline(always)] 与 compiler_fence(Ordering::SeqCst) 阻止编译器重排,确保临界区边界不被优化掉。
- 多核场景:关中断仅影响当前核,若临界区数据可能被其它核访问,必须再叠加自旋锁或总线级原子操作;国内多核 RISC-V 板卡(如全志 D1-H)面试常追问这一点。
- 延迟与实时性:关中断会增加中断延迟,在车载、工控场景下需评估最大关中断时间;面试可提“Rust 的抢占式中断嵌套 + 优先级天花板协议”作为后续优化方向。
- 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 的“编译通过即正确”文化。
拓展思考
- 多核 RISC-V 在国内的崛起:若面试提到“全志 D1-H 64 双核”,需指出关中断仅对当前 hart 有效,必须再使用AMO 原子指令或SBI 提供的 IPI 机制做核间同步。
- Rust for Linux 内核模块:国内厂商(阿里龙蜥、openEuler)已在内核态用 Rust 写驱动,此时不能用上述用户态封装,而应使用kernel::irqsave! / kernel::irqrestore! 宏,与 C 侧的 local_irq_save 语义完全一致。
- 实时性认证:车载 ECU 需通过ASIL-D认证,关中断时间需静态分析。Rust 可借助rtic框架的调度器静态分析工具,给出最坏情况关中断时间(WCIT)报告,这是C 语言传统手写汇编难以做到的。
- MISRA-Rust 规范:国内某些军工单位已引入MISRA-Rust子集,要求“任何 unsafe 块必须附带形式化验证脚本”。面试可主动提及“用 Kani 或 Crucible 对临界区封装做位级验证”,体现对高可信场景的深度思考。