原子操作与非原子操作
原子操作的本质
// BSRR操作(原子): GPIOA->BSRR = (1 << 5); // 单条汇编指令: STR [addr], #bitmask
-
硬件行为:CPU通过单次内存写入直接修改目标寄存器
-
不可中断性:该指令执行时不会响应中断(执行完毕后才检查中断标志)
2. 非原子操作的风险
// ODR操作(非原子): GPIOA->ODR |= (1 << 5); // 实际展开为: // 1. LDR R0, [GPIOA_ODR_addr] // 读取当前值 → 可能在此处被中断! // 2. ORR R0, R0, #(1<<5) // 修改值 // 3. STR R0, [GPIOA_ODR_addr] // 写回
-
中断插入点:任何两个步骤之间都可能被中断插入
-
破坏现场:中断若修改同一寄存器,原始值会被“覆盖”
⚡ 中断触发机制的深层原理
阶段 | 原子操作 (BSRR) | 非原子操作 (ODR) |
---|---|---|
指令开始 | 锁定总线,独占访问 | 无保护 |
执行中 | 禁止中断响应 | 可被高优先级中断抢占 |
内存写入 | 单次完成全部位修改 | 分步操作(读→改→写) |
完成时 | 释放总线,检查待处理中断 | 各步骤间均可能响应中断 |
✅ 关键结论:
中断只能在指令边界响应,而原子操作是单指令,非原子操作是多指令组合。
🧪 灾难性场景模拟(ODR操作被中断破坏)
假设初始状态:GPIOA->ODR = 0x0000
主程序尝试设置PA5:
GPIOA->ODR |= (1 << 5); // 目标: 0x0020
中断函数尝试设置PA6:
GPIOA->ODR |= (1 << 6); // 目标: 0x0040
危险时序:
主程序: [读ODR] → 读到0x0000│ 中断触发: [读ODR] → 读到0x0000[改值] → 0x0000 | 0x0040 = 0x0040[写回] → ODR=0x0040│ 主程序: [改值] → 0x0000 | 0x0020 = 0x0020 // 错误!基于旧值0x0000修改[写回] → ODR=0x0020 // PA6的修改被覆盖!
结果:PA5成功置位,但PA6的修改丢失!
🛡️ 解决方案对比
方法 | 代码示例 | 代价 | 适用场景 | |
---|---|---|---|---|
BSRR原子操作 | GPIOA->BSRR = 1<<5; | 零开销 | 首选方案 | |
关中断保护 | __disable_irq(); `GPIOA->ODR | = ...;<br> __enable_irq();` | 中断延迟 | 必须操作ODR时 |
硬件互斥锁 | LDREX/STREX 指令 | 复杂指令周期 | 多核系统 |
💎 终极结论
-
原子操作 = 1条指令 = 不可分割 = 安全
(如BSRR/BTR寄存器操作) -
非原子操作 = N条指令 = 可被中断切割 = 需保护
(如ODR的读-改-写操作)
“非原子操作多条指令,可能导致指令没执行完就被中断把0/1换了”
—— 这正是嵌入式系统中最隐蔽的Bug来源之一!