编译器barrier/CPU memory barrier

本文详细探讨了内存访问乱序的现象及其原因,并介绍了如何通过编译器barrier和CPUMemorybarrier(如mfence)来避免内存乱序访问,确保多CPU环境下程序的一致性和正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存访问乱序主要是为了提升程序运行时的性能,内存乱序访问主要在两个地方:
1. 编译时,编译器优化导致内存乱序访问(指令重排)
可以使用barrier()阻止编译器优化
2. 运行时,多cpu间交互引起内存乱序访问
使用mb/wmb/rmb刷新数据cache,保证mb/wmb/rmb之前的读写操作在后面的读写操作前完成
总之,barrier()是编译器屏障,*mb是cpu屏障


编译时内存乱序访问

// test.cpp
int x, y, r;
void f()
{
    x = r;
    y = 1;
}

首先编译此源文件,g++ -S test.cpp
得到汇编代码如下:

movl    r(%rip), %eax
movl    %eax, x(%rip)
movl    $1, y(%rip)

这里我们可以看到x=r和y=1并没有乱序,现使用优化选项O2或者O3编译上面的代码,g++ -O2 -S test.cpp,生成的汇编代码如下:

movl    r(%rip), %eax
movl    $1, y(%rip)
movl    %eax, x(%rip)

我们可以清楚的看到经过编译器优化之后 movl $1, y(%rip) 先于 movl %eax, x(%rip) 执行。避免编译时内存乱序访问的办法就是使用编译器 barrier(又叫优化 barrier)。Linux 内核提供函数 barrier() 用于让编译器保证其之前的内存访问先于其之后的完成。内核实现 barrier() 如下(X86-64 架构):
#define barrier() __asm__ __volatile__("" ::: "memory")
现在把此编译器 barrier 加入代码中:

int x, y, r;
void f()
{
    x = r;
    __asm__ __volatile__("" ::: "memory");
    y = 1;
}

这样就避免了编译器优化带来的内存乱序访问的问题了(如果有兴趣可以再看看编译之后的汇编代码)。


运行时内存乱序访问

在单个 CPU 上,硬件能够保证程序执行时所有的内存访问操作看起来像是按程序代码编写的顺序执行的,这时候CPU Memory barrier 没有必要使用,而只要考虑防止编译器优化而导致的乱序。
在单 CPU 上,不考虑编译器优化导致乱序的前提下,多线程执行不存在内存乱序访问的问题。我们从内核源码也可以得到类似的结论(代码不完全的摘录):

#ifdef CONFIG_SMP
    #define smp_mb() mb()
#else
    #define smp_mb() barrier()
#endif

这里可以看到,如果是 SMP 则使用 mb,mb 被定义为 CPU Memory barrier(后面会讲到),而非 SMP 时,直接使用编译器 barrier。

对于多个cpu的情况,我们使用CPU Memory barrier来解决内存乱乱序访问的问题(X86-64 架构下):

void run1()
{
    x = 1;
    __asm__ __volatile__("mfence" ::: "memory");
    r1 = y;
}

void run2()
{
    y = 1;
    __asm__ __volatile__("mfence" ::: "memory");
    r2 = x;
}

参考文章

理解 Memory barrier(内存屏障)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luckywang1103

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值