单核上的x86_64内存屏障

在x86_64上,英特尔文档,第8.2.3.2节,第3A卷,说:

The Intel-64 memory-ordering model allows neither loads nor stores to be reordered with the same kind of operation. That is, it ensures that loads are seen in program order and that stores are seen in program order

我需要确保在写入内存地址时不会重新排列变量.

我想避免使用原子xchg,因为它涉及的成本很高.在我的应用程序中,读取该值的其他cpu知道如何处理不完整的状态.

一些代码:

cli();
compiler_unoptimization(); // asm volatile("":::"memory")
volatile uint *p = 0x86648664; // address doesn't matter
*p = 1;
... // some code here
*p = 0;
sti();

所以,我是正确的假设:

> 1)cpu在* p = 1之前不会使* p = 0,而不需要sfence
> 2)编译器(gcc或clang)不会使用asm技巧反转p写入(这里需要,对吧?).

最佳答案 虽然C标准保证按顺序发出易失性对象的访问,但与非易失性对象相比,它并不能保证它.

这两个访问都是volatile,因此编译器必须按顺序生成这些,但省略号中的任何内容都可以自由移动**,除非它们也是易变的!

volatile也不意味着,硬件将按照C标准的顺序执行.这可以通过CPU的适当屏障来保证,但是 – 取决于架构和障碍 – 它可能不足以用于其余的硬件(高速缓存,总线,存储器系统等).

对于x86,保证了排序(虽然不常见:许多RISC,例如ARM和PPC更放松,因此需要更仔细编写的代码).由于您只涉及单个CPU且volatile在其外部没有副作用,因此内存系统不相关.所以你在这里安全.

对于内存映射外设和多处理器来说,情况要复杂得多,即如果你有超出单CPU的副作用.简单示例:第一次写入可能不会超过CPU缓存,因此读取相同内存页面的任何内容都可能只看到第二次写入或根本没有看到. volatile在这里还不够,你需要原子访问和(可能的)障碍.

对于您的代码,您可以将省略号中的所有变量设置为volatile(低效),或者在它们周围添加编译器障碍(在* p = 1之后;在* p = 0之前;).这样编译器就不会将指令移到屏障之外.

最后:volatile不保证原子访问.因此,* p可能不是由单个指令写入的. (我不会过分强调这一点,因为我假设uint是unsigned int,通常是32位或64位x86目标上的32位,但对于8位或16位CPU来说这将是一个问题.)要安全 – 使用_Atomic类型(自C11起).

PS:像uint这样的类型.标签类型unsigned不是更多的类型,但每个人立即知道你的意思.如果需要特定宽度,请使用stdint.h类型.在这里,您甚至应该使用_Bool / bool,因为您似乎只有一个真/假标志.

请注意,所有这些功能也可用于低级代码.特别是_Atomic(参见stdatomic.h,也适用于此类目的)并且通常不需要任何特殊的库.它们的使用通常并不比非限定类型复杂,如果它们也可以原子方式存储(也有宏表示特定类型是否为原子).

点赞