21.原子操作.md 4.3 KB

原子操作

判别规则(核心,唯一要记的)

  • 只读/只写共享变量 → volatile 够;读-改-写(++|=)→ 必须原子操作或临界区
  • 选型:简单变量计数 → 硬件原子指令/C11 原子类型;复杂代码流程 → 临界区
  • 32 位对齐的单个读/写天然原子,但读-改-写(++)不是(LOAD→ADD→STORE 三步可被打断)

一句话:"volatile 保新鲜,原子保完整,临界区保不被打断。"

抽象描述(一句话本质)

原子操作 = "不可分割"的操作——要么完整执行要么完全不执行,中途不可能被打断。保护共享数据三手段:volatile(每次读新值)、临界区(关中断)、硬件原子指令(单条完成读-改-写)。volatile 管"看得见新值",原子管"操作不拆开",临界区管"代码不被打断"。

正反例(建立直觉)

  • 正例1count++; = LOAD→ADD→STORE 三步,中断/另一任务可插入 → 丢更新
  • 正例2(三种保护):① 临界区:关中断→操作→开中断(简单可靠但影响实时性)② 硬件原子指令:Cortex-M 的 LDREX/STREX 或 C11 atomic_int ③ 32 位 MCU 上对齐的 32 位单读/单写 = 一条指令天然原子
  • 正例3(区分):读对齐 32 位变量 → 天然原子;count++ → 三步非原子
  • 反例1:只加 volatile 不够——管不了三步被拆
  • 反例2:中断和主循环都 ++ 同一变量,两边都"觉得自己没错",实际各丢一次

易混对比

volatile 临界区 原子指令/原子类型
管什么 每次读新值(可见性) 一段代码不被中断打断 单次读-改-写不可分割
代价 关中断,影响实时性 几乎无
适用 单个读/写 任意代码段/复杂流程 简单变量操作

变体验证(3 题,全过=学会)

  1. 陷阱题:32 位 MCU 对齐 volatile uint32_t reg; 一任务写一任务读 → 不需要原子操作(对齐 32 位单读/单写天然原子,volatile 够)
  2. 陷阱题:volatile uint32_t *p 指向不对齐地址 → 不是天然原子(可能被拆成多次总线访问,如协议帧内)
  3. count |= 0x01; 中断也操作 count → 读-改-写,需临界区或原子操作

口述要点(面试怎么讲)

  • 结论先行:只读/只写 volatile 够;读-改-写必须原子/临界区;简单操作选原子指令,复杂流程选临界区
  • 为什么关中断能保护:单核下关中断后只有当前代码执行,中断和任务都插不进来。多核陷阱:关中断只管本核,另一核照样能改共享数据 → 多核必须真原子/锁
  • 临界区的代价:关中断延迟中断响应 → 牺牲实时性。++ 用关中断是杀鸡用牛刀,简单操作优先原子指令;临界区真正怕的是"太长",会饿死中断
  • taskENTER_CRITICAL vs vTaskSuspendAll:关中断 = 挡中断+任务切换(中断不能响应);挂起调度器 = 只挡任务切换(中断照常响应)。数据被中断碰 → 必须关中断;数据只有任务碰 → 挂起调度器就够
  • 易错点:以为 volatile 够原子;以为关中断多核安全;以为 ++ 不适合临界区(它恰恰是保护对象,只是原子指令更省)

关系网络

相邻概念 和本主题的关系 孤立理解会犯的错
volatile volatile 保"每次读新值",原子保"读-改-写不拆开",独立可叠加 以为 volatile 就够原子
临界区 关中断保护任意代码,代价是实时性 临界区开太久饿死中断
中断优先级 中断里只能用 FromISR 的 API 在中断里调用会阻塞的 API
SMP/多核 关中断只管本核,多核要真原子/锁 以为关中断多核也安全

学习日期

2026-08-03


每主题一页,复习时只翻本目录。