内存屏障与指令重排序
乱序为什么发生、编译器与 CPU 两层分别怎么抑制。
乱序现象与根本原因
| 层级 | 谁在乱序 | 具体表现 |
|---|---|---|
| 编译器乱序 | 编译器优化阶段 | 打乱生成的汇编指令顺序 |
| CPU 硬件乱序 | 流水线 + 写缓冲区 | 物理 RAM 读写顺序 ≠ 汇编指令顺序 |
根本原因:访问 RAM 远慢于 CPU 运算,乱序是为了减少 CPU 空转、提升执行效率(前提是保证单线程语义不变)。
代价:在外设控制、DMA、中断、多线程场景下,乱序会导致数据异常。
抑制乱序的手段分类
① 针对编译器(软件层,生成 0 条硬件指令)
| 手段 | 作用域 | 作用 |
|---|---|---|
volatile | 单个变量 | 强制每次去物理地址读写,禁止编译器优化掉;保证 volatile 变量之间的顺序,但管不到普通变量跨越它重排 |
__asm__ __volatile__("" ::: "memory") | 当前代码行 | 编译器内存屏障,在此处立一道墙,禁止任何变量(无论是否 volatile)跨越该行重排 |
② 针对 CPU 硬件(硬件层,生成真实屏障指令)
利用 ARM 专用屏障指令限制写缓冲区与流水线乱序:
| 指令 | CMSIS 封装 | 语义 |
|---|---|---|
| DMB | __DMB() | 数据内存屏障:前面的写内存全部写入 RAM 后,才允许执行后面的写内存 |
| DSB | __DSB() | 数据同步屏障:前面的写操作完全结束,CPU 才允许执行后续的任何指令 |
| ISB | __ISB() | 指令同步屏障:冲刷 CPU 流水线,强制重新抓取最新指令 |
三者的递进关系:DMB(内存序)→ DSB(全部操作完成)→ ISB(流水线级同步)。
实际工程说明
芯片厂外设库(HAL/LL 库)已内置封装:ST 等厂商在涉及 DMA 传输、中断配置、系统复位、MPU/VTOR 修改等关键外设操作时,底层源码已写好对应的内存屏障指令和 volatile 声明,常规开发无需频繁手动编写。
手动写屏障的场景集中在:无 HAL 的裸寄存器操作、D-Cache 一致性维护(如 Cortex-M7)、自研 RTOS 的临界区与任务切换。