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 下,原子写完立即看得见。
相关推荐
JasonHuan11231 小时前
Hyper-V-三台虚拟机安装启动使用完全指南
linux·ubuntu·hyper-v·虚拟机
深圳市恒锐丰科技杨生1 小时前
SA8346 4‑36V 单通道 H 桥电机驱动芯片|中高压直流有刷电机方案
嵌入式硬件·硬件工程
susplus1 小时前
【专题】frmaebuffer打印bmp图片
linux·bmp·framebuffer
牢姐与蒯1 小时前
Linux进程(三)之进程状态_僵尸孤儿
linux·运维·服务器·ubuntu·c
深圳市恒锐丰科技杨生1 小时前
SA8344 6.5‑25V 单通道 H 桥电机驱动芯片|消费类直流有刷电机方案
嵌入式硬件·硬件工程
LuminousCPP1 小时前
数据结构-二叉树(三):堆复杂度证明与 Top-K 问题|错位相减推导 + 海量数据内存优化
c语言·数据结构·笔记·排序算法
mengge.cloud1 小时前
云计算&服务器基础小白教程
android·linux·运维·服务器·网络·云计算
一路向北finish2 小时前
《Hadoop 高可用集群与 OpenStack Mitaka 控制节点部署全流程实操排坑指南》
大数据·linux·运维·服务器·hadoop·openstack
南棱笑笑生2 小时前
20260826解决在Ubuntu22.04系统下编译RK3572的Buildroot【linux-6.12.69】出现CMake异常的问题
linux·运维·服务器