ARM 内存屏障:DMB、DSB、ISB详解
ARM 内存屏障:DMB、DSB、ISB详解
1. 什么是内存屏障
1.1. 内存屏障的定义
内存屏障(Memory Barrier)是一类特殊指令,用来约束屏障前后指令的顺序、完成状态或生效时机。
1.2. 为什么需要内存屏障
ARM 采用弱内存模型(Weakly-ordered memory model),程序里的访存顺序和其他观察者(其他核、DMA、外设)看到的顺序可能不一致,原因有三层:
- 编译器重排:编译器在不改变单线程语义的前提下调整指令顺序。
- CPU 乱序执行:流水线、Store Buffer、推测执行、Write Combining 等都会改变访存的实际顺序。
- 多核与缓存:写操作通常先进入缓存,不会立刻到达内存,不经过该缓存的观察者可能从内存中读到旧值。另外,多核时每个核有自己的 L1 缓存,写操作对其他核的可见时间和顺序也会存在不确定性。
观察者(Observer):指任何能发起内存访问、能"看到"内存内容的硬件单元,例如 CPU 核、DMA 控制器、GPU 和其他总线主设备(如网卡、存储控制器等)。
通常我们都会使用术语“观察者”来描述内存屏障的作用对象;内存屏障约束的正是这些观察者看到各次访存的先后顺序。
2. DMB、DSB、ISB 是什么
DMB、DSB、ISB 是 ARM 架构下的三条内存屏障指令,用来约束访存和指令执行的顺序与完成状态。
| 指令 | 全称 | 说明 |
|---|---|---|
| DMB | Data Memory Barrier | 约束内存访问的观察顺序:保证按程序顺序位于 DMB 之前的显式内存访问,先于 DMB 之后的显式内存访问被指定共享域内的观察者观察到。 |
| DSB | Data Synchronization Barrier | 保证相关内存操作完成后再继续执行:DSB 之后的指令必须等待 DSB 之前符合要求的显式内存访问,以及相关的 Cache、TLB、分支预测器等维护操作达到架构规定的完成条件后,才能执行。 |
| ISB | Instruction Synchronization Barrier | 冲刷流水线,使后续指令重新取指。 |
3. ARM 下内存屏障的具体实现
3.1. 指令格式
三条屏障指令的汇编语法:
DMB <option>
DSB <option>
ISB {<option>} // 只有一个有效选项 SY,可省略
3.1.1. option 取值
<option> 由共享域 + 访问类型组合而成。
| option | 共享域 | 访问类型 | 说明 |
|---|---|---|---|
SY | 全系统 | load + store → load + store | 最强屏障,缺省值。用于需要对所有观察者(含非一致性设备)生效的场合 |
ST | 全系统 | store → store | 保证之前的 store 先于之后的 store 被系统内所有观察者看到 |
LD | 全系统 | load → load/store | 保证之前的 load 先于之后的任何访问 |
ISH | 同 OS 的所有核(Inner Shareable) | load + store → load + store | 多核同步的通用选择 |
ISHST | 同 OS 的所有核(Inner Shareable) | store → store | 多核间发布数据的写端屏障,开销小于 ISH |
ISHLD | 同 OS 的所有核(Inner Shareable) | load → load/store | 多核间读取数据的读端屏障 |
NSH | 仅本核(Non Shareable) | load + store → load + store | 只保证本核观察顺序,不对其他观察者生效。 |
NSHST | 仅本核(Non Shareable) | store → store | 仅本核的 store 顺序 |
NSHLD | 仅本核(Non Shareable) | load → load/store | 仅本核的 load 顺序 |
OSH | ISH域 + 外部设备(Outer Shareable) | load + store → load + store | CPU 与 DMA/GPU 交互的全序屏障 |
OSHST | ISH域 + 外部设备(Outer Shareable) | store → store | 保证 CPU 写入的数据/描述符先于后续写被设备(DMA/GPU)看到 |
OSHLD | ISH域 + 外部设备(Outer Shareable) | load → load/store | 保证读完设备(DMA/GPU) 相关数据后再进行后续访问 |
注意: 表格中列出的 option 取值为 ARM 架构规范所定义的完整集合,但具体芯片未必全部支持。
load(加载): 从内存读数据到寄存器。ARM 中对应
LDR、LDAR等指令。
store(存储): 把寄存器中的数据写到内存。ARM 中对应STR、STLR等指令。
屏障约束的正是这两类访存之间的先后顺序,所以访问类型里才会出现LD(load)和ST(store)。
-
共享域
"共享"指一块内存被哪些处理器或设备共同访问, "域"即这些访问者所构成的范围。屏障中的共享域用来限定顺序保证的作用范围: 只需要在真正需要的范围内保证访存顺序。因为,范围越小, 硬件需要等待和同步的对象越少, 屏障开销越低, 性能越好。

-
访问类型
描述的是屏障前的哪类访问与屏障后的哪类访问之间保持顺序。例如 option 为LD时,表示屏障之前的 load,先于屏障之后的 load 和 store 被观察到。 -
其他说明
上述屏障指令适用于 ARMv7 和 ARMv8 架构。在 ARMv6 及更早的架构中, 没有专用的屏障指令, 需要通过协处理器 CP15 的操作来实现,具体如下所示:
mcr p15, 0, r0, c7, c10, 5 @ DMB mcr p15, 0, r0, c7, c10, 4 @ DSB mcr p15, 0, r0, c7, c5, 4 @ ISB
3.2 内联汇编实现
为在C代码中便捷地插入ARM架构屏障指令,采用如下内联汇编宏封装。
#define dmb(opt) __asm__ volatile("dmb " #opt : : : "memory")
#define dsb(opt) __asm__ volatile("dsb " #opt : : : "memory")
#define isb() __asm__ volatile("isb" : : : "memory")
3.3. CMSIS 实现
以 GCC 工具链 + Cortex-M 为例。
3.3.1 接口
__ISB(); // Instruction Synchronization Barrier
__DSB(); // Data Synchronization Barrier
__DMB(); // Data Memory Barrier
说明:Cortex-M 对应的 Armv6-M / Armv7-M / Armv8-M 架构只支持 SY,没有 ISH、ST、LD 等细分选项,所以接口不带参数。
3.3.2 CMSIS 底层实现
CMSIS 的 ARM 屏障底层实现本质上是内联汇编封装。典型实现形式如下:
//option 固定为 0xF,即 SY(全系统)
__STATIC_FORCEINLINE void __DMB(void)
{
__ASM volatile ("dmb 0xF" ::: "memory");
}
__STATIC_FORCEINLINE void __DSB(void)
{
__ASM volatile ("dsb 0xF" ::: "memory");
}
__STATIC_FORCEINLINE void __ISB(void)
{
__ASM volatile ("isb 0xF" ::: "memory");
}
3.4. Linux 实现
以arch/arm64/include/asm/barrier.h为例。
#define sev() asm volatile("sev" : : : "memory")
#define wfe() asm volatile("wfe" : : : "memory")
#define wfi() asm volatile("wfi" : : : "memory")
#define isb() asm volatile("isb" : : : "memory")
#define dmb(opt) asm volatile("dmb " #opt : : : "memory")
#define dsb(opt) asm volatile("dsb " #opt : : : "memory")
/* 强制屏障:含设备访问 */
#define mb() dsb(sy)
#define rmb() dsb(ld)
#define wmb() dsb(st)
/* DMA 屏障:普通内存 <-> 设备 */
#define dma_mb() dmb(osh)
#define dma_rmb() dmb(oshld)
#define dma_wmb() dmb(oshst)
/* SMP 屏障:核间同步 */
#define smp_mb() dmb(ish)
#define smp_rmb() dmb(ishld)
#define smp_wmb() dmb(ishst)
4. DMB:数据内存屏障
4.1. 作用
保证屏障前后的访存按序被观察到,不等待访存完成,不影响屏障后的非访存指令(比如算术运算)继续执行。
- 保证内存访问顺序:屏障之前的内存访问,在屏障之后的内存访问之前,对其他观察者可见。
- 限制乱序执行:阻止处理器、内存系统等将屏障前后的内存访问以违反屏障语义的顺序对外呈现。
- 不保证访问完成:DMB 不保证屏障之前的所有内存访问已经完成,只保证它们满足规定的顺序关系。
- 不刷新 Cache:DMB 不会主动清理 Cache,也不会将 Cache 中的数据强制写回 DDR。
4.2. 使用举例
// 生产者:先写共享数据,再更新状态标志
share_buffer[0] = 0x12345678;
dmb(sy) // 保证数据写入先于 ready 标志更新
ready = 1;
// 消费者:检测到 ready 后,再读取共享数据
if (ready == 1) {
dmb(sy); // 保证读取 ready 之后,再读取共享数据
data = share_buffer[0];
}
//没有这两个 DMB,消费者可能看到 `ready=1` 但读到旧的 `data`。
场景2:多核 CPU 之间共享内存,进行完整的读写顺序同步
操作示例同场景1,区别在于屏障的共享域:多核之间只需约束同一 Inner Shareable 域内的观察者,使用dmb(ish) 即可。
场景 3:系统级外设访问顺序控制
例如先配置外设的 DMA 地址,再启动 DMA。此处的观察者包含片上外设,需要全系统范围的屏障,使用 sy:
DMA->ADDR = buffer_addr;
dmb(sy); //保证 DMA 地址配置操作先于 DMA 启动操作对外呈现
DMA->CTRL = DMA_START;
5. DSB:数据同步屏障
5.1. 作用
保证屏障之前的显式内存访问以及相关维护操作完成后,才允许屏障之后的指令继续执行。
- 保证内存访问完成:屏障之前的内存访问必须达到架构定义的完成状态。
- 保证后续指令执行:在屏障之前要求完成的操作完成之前,处理器不能继续执行屏障之后的指令。
- 同步 Cache 和 TLB 维护操作:保证之前发起的相关维护操作完成。
- 不等于 Cache 刷新:DSB 本身不执行 Cache Clean 或 Invalidate 操作。
5.2. 使用举例
场景 1:Cache 维护后启动 DMA
CleanInvalidateDCache(); // 执行 Cache Clean / Invalidate 等维护操作
dsb(sy); // 等待 Cache 维护操作在全系统范围内完成
/* 后续可以启动 DMA 读取该内存 */
页表修改的标准序列是:修改页表项 → dsb(ishst) → tlbi → dsb(ish) → isb()。
page_table[index] = new_descriptor; // 修改页表描述符
dsb(ishst); // 仅需保证之前的 store(页表写入)先对其他核可见
__set_TLB_invalidate(); // 执行 TLB Invalidate
dsb(ish); // 等待 TLB 维护操作在 Inner Shareable 域内全部完成
isb(); // 冲刷流水线,使后续指令使用新的地址翻译
场景 3:系统进入低功耗
writel_relaxed(PWR_REQ_SUSPEND, pmu_base + PMU_CTRL); // 请求电源控制器掉电
dsb(sy); // 确保之前所有访存(含设备写)完成
wfi(); // 进入低功耗,等待唤醒事件
6. ISB:指令同步屏障
6.1. 作用
冲刷流水线,丢弃已预取和已译码的指令,使屏障之后的指令在上下文变更生效后重新取指和执行。
- 刷新指令流水线:使屏障之后的指令重新取指。
- 同步处理器执行上下文:确保后续指令基于屏障之前已经完成的系统控制状态变化执行。
- 确保控制寄存器修改生效于后续指令执行上下文:例如修改 SCTLR、TTBR 等系统控制寄存器之后。
- 不负责数据同步:ISB 不保证之前的内存访问完成,也不负责 Cache 数据清理。
6.2. 使用举例
场景 1:修改系统控制寄存器后
在修改系统寄存器系统接口实现函数内部都带有 isb()。
SCB->SCTLR |= SCTLR_Z_Msk; // 修改处理器系统控制寄存器
isb(); // 冲刷流水线,使后续指令在更新后的控制状态下执行
场景 2:TLB 维护后
见上面 DSB 场景 2,其中最后的 isb() 用于丢弃已经按旧地址翻译预取的指令,让后续指令重新取指。
场景 3:切换异常向量表后
extern char vectors[];
write_sysreg((unsigned long)vectors, vbar_el1); // 设置新的异常向量表基址
isb(); // 保证此后发生的异常,一定使用新的向量表
7. 总结
- ARM 采用弱内存模型:程序中的访问顺序不一定等同于其他观察者看到的顺序,因此需要通过内存屏障指令,在必要时约束内存访问和指令执行顺序。
- DMB 管顺序,DSB 管完成,ISB 管指令同步:三者分别解决“内存访问顺序如何保证”,“屏障前的操作是否达到架构规定的完成条件”,“后续指令是否基于更新后的执行上下文执行”三个问题,不能互相替代。
- 屏障指令不等于 Cache 操作:Cache 维护需要显式的维护指令,屏障指令(DSB) 只负责等待其完成。
- 按需选择屏障选项:ARM 屏障指令可以通过
<option>指定共享域(NSH / ISH / OSH / SY)和访问类型(无后缀 / ST / LD)。应根据观察者范围和访问类型选择合适的选项,避免不必要的同步开销。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)