
一、内存乱序场景
ARMv8 指令集提供了三种内存屏障指令,按作用范围从弱到强排序为:DMB < DSB < ISB。
1、数据存储屏障(DMB)
- 确保前面的内存操作必须在后面的内存操作之前完成
- 允许处理器内部优化指令执行顺序
- 保证其他处理器能观察到正确的内存操作顺序
2、数据同步屏障(DSB)
- 强制所有之前的内存操作完全完成后再执行后续操作
- 禁止任何指令重排序
- 让所有处理器都能立即看到内存操作的结果
3、指令同步屏障(ISB)
- 确保之前的所有指令完全执行完毕
- 重置指令流水线
- 特别用于处理自修改代码或异常返回等场景
简单总结
DMB 维护内存操作顺序,DSB 强制全局内存同步,ISB 确保指令执行顺序。三者按强度递增,ISB 效果最强。
二、经典内存屏障接口函数
1、编译器屏障
barrier():阻止编译器打乱代码顺序以优化性能,确保代码执行顺序与书写顺序一致。
2、内存屏障(通用)
mb():同时处理读和写的内存屏障,适用于多 CPU 系统(SMP)和单 CPU 系统(UP)。确保所有 CPU 可见一致的内存操作顺序。
3、读/写专用屏障
rmb():仅处理读操作的内存屏障,保证读操作的顺序性,适用于 SMP 和 UP 系统。wmb():仅处理写操作的内存屏障,保证写操作的顺序性,适用于 SMP 和 UP 系统。
4、多 CPU 专用屏障(SMP)
smp_mb():仅在多 CPU 系统(SMP)中起作用的内存屏障。在单 CPU 系统(UP)中,它退化为编译器屏障,仅确保汇编代码与 C 代码的内存操作顺序一致。smp_rmb():多 CPU 系统的读屏障,类似rmb()但仅在 SMP 生效。smp_wmb():多 CPU 系统的写屏障,类似wmb()但仅在 SMP 生效。
5、特殊场景屏障
smp_read_barrier_depends():处理读操作依赖的屏障(例如,先读取指针再读取数据),确保依赖关系的正确性。smp_mb_before_atomic/smp_mb_after_atomic:在原子操作(如atomic_t)前后插入通用内存屏障,确保原子操作与前后内存操作的顺序一致。
6、关键区别总结
| 函数 | 适用场景 | 作用范围 | 在 UP 系统的行为 |
|---|---|---|---|
barrier() | 编译器优化 | 阻止指令重排 | 同样有效 |
mb()、rmb()、wmb() | SMP 和 UP 系统 | 内存顺序保证 | 完整生效 |
smp_*() 系列 | 仅 SMP 系统 | CPU 间内存同步 | 退化为编译器屏障(如 barrier()) |
在 ARM64 Linux 内核中实现内存屏障函数的代码如下:
c
// arch/arm64/include/asm/barrier.h
#define mb() dsb(sy)
#define rmb() dsb(ld)
#define wmb() dsb(st)
#define dma_rmb() dmb(oshld)
#define dma_wmb() dmb(oshst)1
2
3
4
5
6
2
3
4
5
6

三、案例
在 Linux 内核中有很多使用内存屏障指令的例子,下面举两个例子。
1、案例 1:网卡驱动
在网卡驱动中发送数据包时,先把数据包内容写入内存缓冲区。之后,硬件 DMA 控制器会自动发送这些数据。此时需要使用 wmb() 函数,它能确保 DMA 开始传输前,所有数据已经完整地写入缓冲区——避免 DMA 提前读取到未完成的数据。
文件位置
drivers/net/ethernet/realtek/8139too.c
c
static netdev_tx_t rtl8139_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);
wmb();
RTL_W32_F(TxStatus0 + (entry * sizeof(u32)),
tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));
// ...
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2、案例 2:等待事件唤醒
在 Linux 系统中,当一个进程需要等待某个条件发生(比如等待数据准备好)时,它会通过类似 wait_event() 这样的函数进入"睡眠"状态暂停执行。这个过程需要用到内存屏障技术来保证数据同步。
具体来说:
- 当进程调用
wait_event()时,会先检查等待条件是否满足 - 如果条件不满足,进程会暂停自己(进入睡眠)
- 内存屏障指令在这里的作用是:确保在进入睡眠前的所有操作都已完成,并且这些操作的结果对其他 CPU 是可见的
当发生唤醒事件时:
- 唤醒函数会先更新共享数据(比如标记数据已就绪)
- 内存屏障指令会强制保证:在发出唤醒信号前,所有相关数据更新必须完成并写入内存
- 这样其他 CPU 在被唤醒后检查条件时,就能看到最新的数据状态
形象理解
内存屏障就像交通信号灯,控制着数据操作的顺序,确保不同 CPU 之间的通信不会因为缓存或指令重排而出现混乱。这就像在说:"在继续下一步操作之前,请先完成并同步所有未完成的数据修改"。