🔍 原子操作 vs 非原子操作
特性 | 原子操作 (Atomic) | 非原子操作 (Non-Atomic) |
---|---|---|
定义 | 不可中断的完整执行单元 | 可被中断拆分的多步骤操作 |
执行过程 | 单指令完成(如 BSRR 写操作) |
需多指令完成(如"读-改-写"流程) |
中断影响 | 不会被中断打断 | 可能被中断打断导致数据不一致 |
线程安全 | 天然安全(无需锁) | 需额外同步机制(如关中断/互斥锁) |
硬件支持 | 由CPU指令直接保证 | 依赖软件保护 |
典型场景 | STM32的 BSRR /BRR 寄存器操作 |
STM32的 ODR 直接修改 |
⚠️ 非原子操作的风险场景(以STM32的ODR为例)
假设在 主循环 和 中断函数 中同时修改 GPIOA->ODR
:
// 主程序流程
void main() {
GPIOA->ODR |= (1 << 5); // 步骤1: 读取ODR
// 步骤2: 修改bit5
// 步骤3: 写回ODR
}
// 中断服务函数
void TIM_IRQHandler() {
GPIOA->ODR |= (1 << 6); // 在步骤1-3之间可能被触发!
}
危险时序:
主程序: [读ODR] -> [改bit5] -> [写回ODR]
│ ▲ ▲
中断触发: └─────────┘ │
[读ODR] -> [改bit6] -> [写回ODR]
结果 :
主程序对 PA5
的修改被中断中的 PA6
修改覆盖 ,导致 PA5
设置失败!
🛡️ 为什么BSRR是原子操作?
STM32 的 BSRR
寄存器设计精妙:
// 置位PA5(低16位有效)
GPIOA->BSRR = (1 << 5); // 单指令完成:0→1
// 清零PA5(高16位有效)
GPIOA->BSRR = (1 << (5 + 16)); // 单指令完成:1→0
硬件机制:
-
CPU 通过单次总线写操作 修改
BSRR
-
硬件自动解析位操作,直接改变ODR状态
-
无中间状态,不会被中断打断
🔧 非原子操作的解决方案
1. 关中断保护(裸机系统)
__disable_irq(); // 关中断
GPIOA->ODR |= (1 << 5); // 安全修改
__enable_irq(); // 开中断
2. 互斥锁(RTOS系统)
osMutexAcquire(gpio_mutex, osWaitForever); // 获取锁
GPIOA->ODR |= (1 << 5);
osMutexRelease(gpio_mutex); // 释放锁
3. 硬件原子指令(Cortex-M3/4/7)
// 使用LDREX/STREX指令(需汇编或C封装)
__atomic_or_fetch(&GPIOA->ODR, (1<<5), __ATOMIC_SEQ_CST);
💡 关键理解
-
原子性 = 操作不可分割
像物理学中的原子一样不可再分,要么完整执行,要么完全不执行。
-
非原子操作的本质风险
数据竞争(Data Race):多个执行流(主程序+中断/多线程)同时访问共享资源(如ODR寄存器)且至少一方在写。 -
嵌入式场景的典型非原子操作
-
多步寄存器修改(如
ODR
,CR
等) -
非对齐数据访问(如32位机读写64位数据)
-
外设状态机切换(如先读标志位再写命令)
-
📚 现实类比
场景 | 原子操作 | 非原子操作 |
---|---|---|
银行转账 | 金库直接搬钱 | 先查账→计算→写回 |
交通控制 | 整条路封闭施工 | 车道轮流放行 |
GPIO控制 | BSRR直接开关LED | ODR分步修改 |
非原子操作如同「拆墙时被人塞新砖」------ 最终墙体状态不可预测!