C11 原子操作 `atomic_int` 通俗指南

1. 是什么

atomic_int 是 C11 标准提供的原子整数类型 。它保证对变量的"读"和"写"是不可分割 的(原子),

且并发访问时不会撕裂、不会乱序。

c 复制代码
atomic_int g_list_ready = ATOMIC_VAR_INIT(0);  // 初始化 = 0
atomic_store(&g_list_ready, 1);                // 原子写 1
int val = atomic_load(&g_list_ready);          // 原子读

2. 打比方:记账本

想象一个多人共用的记账本(内存变量):

  • 普通 int :记账本是"活页纸"。老板(任务A)正在写账,店员(任务B)一把抢过纸开始写------
    两张纸都被涂花,账目撕裂。

  • atomic_int :记账本被换成带锁的磁性白板 。每个人读/写必须"整笔操作"完成,
    写一半不可能被抢走------每次读到的都是完整的一笔账。

    普通 int: atomic_int:
    ┌─────────┐ ┌──────────────┐
    │ 写一半被抢 ✗ │ 写就是一笔 ✓│
    │ 读到脏数据 ✗ │ 读就是完整 ✓│
    └─────────┘ └──────────────┘

关键 :白板保证的是"这一笔账不会花",不保证 "账本和仓库货物一致"------那是锁(info_take/info_give)的职责。


3. 为什么要用:并发撕裂问题

在嵌入式里,同一个变量常被中断 和任务同时访问:

复制代码
【任务A】                    【中断(如UART接收)】
  g_ready = 1;  ←→             if (g_ready) {...}

普通 int 的"读-改-写"在底层是多条指令,中间随时可能被打断:

asm 复制代码
LDR  R0, [addr]   ; ① 读
ADD  R0, R0, #1   ; ② 加   ← 这里被打断,抢占者读到了旧值!
STR  R0, [addr]   ; ③ 写

结果:抢占者看到"旧值",两边状态错乱。这就是 Race Condition(竞态)。

atomic_int 的 load/store 在 32 位对齐访问下单条指令完成,天然不被撕裂。


4. 底层原理:LDREX / STREX(读-改-写的保险锁)

对于"读-改-写"类原子操作(如 count++、compare_exchange),硬件用 LDREX/STREX 排他访问:

复制代码
步骤① LDREX R0, [addr]    读取 + 给地址贴"独占标签"
步骤② 中间被中断打断......    标签还在,写入作废
步骤③ STREX R1, R2, [addr] 尝试写:
        - 标签还在 → 写成功,R1=0
        - 标签丢了 → 写失败,R1=1,重来一遍
c 复制代码
// atomic_int 的 fetch_add 底层大致长这样(编译器生成,无需手写)
do {
    old = __LDREXW(addr);      // ① 读 + 贴标签
} while (__STREXW(old + 1, addr));  // ③ 标签在才写,失败重试

比喻:先占座,再结账。占座期间被插队,就放弃重排。


5. 有什么作用(清单)

作用 说明 本项目用途
原子性 读/写/RMW 不可分割,不撕裂 g_list_ready 读写
可见性 写后其他任务/中断立即看到新值 锁故障标志即时通知全店
顺序保证 默认seq_cst,前后指令不乱序 先写缓冲数据 → 再置标志
无需关中断 排他指令不关中断,不阻塞实时性 中断里也能安全用
免锁开销 32 位原子是 lock-free,比信号量快 高频状态标志

6. 什么场景用(对照本项目)

✅ 该用:多执行体共享的"小状态标志"

本项目 <sys_var_ring_ls.c> 里的这两个就是典型:

c 复制代码
static atomic_int g_list_ready;       // 全店就绪标志:中断/任务都要读
static atomic_int g_list_lock_error;  // 全店锁故障标志:多处写、多处读

特征:值小(0/1) 、读写频繁 、任务和中断都可能碰 、对实时性敏感。

✅ 该用:中断里也要操作的共享计数/标志

c 复制代码
atomic_int rx_overrun_cnt;            // 中断里 ++,任务里读清零

❌ 不该用:大的数据缓冲、结构体数组

数据区(ring 缓冲)不能用原子,必须用锁保护:

c 复制代码
sys_var_ring_write(&info->ring, ...); // 数据缓冲 → 靠 info_take/info_give 锁

原则:原子管"标志",锁管"数据"。

❌ 不该用:只有单一写者、且无中断竞争

单写者单读者且不会被抢占撕裂时,普通 int + volatile 就够了,用原子反而多操心。


7. 顺序保证:为什么"先写数据再置标志"安全

atomic_store 默认是 seq_cst(最强顺序),编译时会插入内存屏障,保证:

复制代码
写者:先写 ring 数据(普通写)  →  DMB屏障  →  atomic_store(标志=1)
读者:atomic_load(标志==1)     →  DMB屏障  →  再读 ring 数据

写者线程                        读者线程
  │ 写数据①                        │
  │ 置标志② (release)             │
  │                         读标志② 已见 ✓
  │                         读数据① 必见 ✓   ← 屏障保证不乱序

比喻:先摆货(数据),再开门(标志)。顾客看到"营业中"时,货一定已经摆好。


8. 常见 API 速查

API 作用 示例
ATOMIC_VAR_INIT(x) 初始化 ATOMIC_VAR_INIT(0)
atomic_store(p, v) 原子写 atomic_store(&g_ready, 1);
atomic_load(p) 原子读 if (atomic_load(&g_ready)) {...}
atomic_fetch_add(p, n) 原子加 atomic_fetch_add(&cnt, 1);
atomic_compare_exchange CAS 比较交换 实现自旋锁

9. 注意事项(结合本项目)

  1. 单核 Cortex-M7 可见性无忧 :所有任务/中断同 CPU 同 Cache,atomic_store 后其他执行体立即可见。
  2. 本项目 SRAM0/SRAM1 为 Cacheable:CPU 侧读写一致;DMA 缓冲都在 Non-Cacheable 的 AXI SRAM,互不干扰,无需手动 cache 维护。
  3. 编译器要求 AC6(armclang) :<stdatomic.h> 是 C11 特性,老编译器 AC5(ARMCC5)不支持,会编译报错。本项目已是 AC6 V6.16。
  4. atomic_int 是 lock-free:32 位原子不依赖 RTOS,中断里也能用,不会导致死锁。
  5. 不要拿原子操作替代业务锁 :数据缓冲一致性永远靠锁(info_take/info_give),原子只保护标志本身。

10. 一张图总结

复制代码
        ┌─────────────────────────────────────────────┐
        │            sys_var_ring_ls 业务池             │
        │                                             │
        │  原子标志(atomic_int)       数据缓冲(ring) │
        │  g_list_ready               info->ring       │
        │  g_list_lock_error          info->lock       │
        │     ↓ 原子、立即可见              ↓ 锁保护      │
        │  状态同步                   数据一致          │
        └─────────────────────────────────────────────┘

记住三句话:

  • 原子 = 白板记账,一笔不会花;
  • 标志用原子,数据用锁;
  • 单核 CM7 下,原子写完立即看得见。
相关推荐
ouynagda31 分钟前
嵌入式Linux i.MX6ULL系统移植学习笔记
linux·笔记·学习
NightReader35 分钟前
PVE 环境部署
linux
蚰蜒螟42 分钟前
一次挂载的完整旅程:Linux 内核 VFS 到 ext4 的调用链剖析
linux·运维·服务器
zbyyd44 分钟前
i.MX6ULL 裸机开发|GT9147 触控 + SPI 与 ADXL345 加速度传感器驱动
单片机·嵌入式硬件
fly_sunnn1 小时前
STM32-LED闪烁·LED流水灯·蜂鸣器
stm32·单片机·嵌入式硬件
天蓝蓝的本我1 小时前
linux本地部署Qwen3.8 27B
linux·运维·excel
今夜有雨.1 小时前
图像运算、掩膜/ROI 与绘制交互
c语言·数据结构·c++·qt·算法·计算机视觉
lisanmengmeng2 小时前
NRPE 添加命令(四)
linux·运维·服务器
知识分享小能手2 小时前
C学习教程,从入门到精通,C语言概述 —— 知识点详解(1)
c语言·开发语言·学习
想做小南娘,发现自己是女生喵2 小时前
i.MX6ULL嵌入式Linux入门学习文档
linux·运维·学习