x86_64 单核内存屏障

x86_64 memory barrier on single core

关于 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();

那么,我的假设是否正确:

虽然 C 标准保证按顺序发出对易失性对象的访问,但与非易失性对象相比,它并不保证这一点。

你在这里有两个访问权限volatile,因此编译器必须按顺序生成这些,但是省略号中的任何内容都可以自由移动**除非这些也是 volatile

此外 volatile 并不意味着,硬件将按照 C 标准 的顺序执行 。这将通过 CPU 的适当屏障来保证,但是 - 根据架构和屏障 - 它可能不足以满足其余硬件(缓存,总线,内存系统等

对于 x86,顺序是有保证的(虽然不是典型的:许多 RISC,例如 ARM 和 PPC 更宽松,因此需要更仔细地编写代码)。由于您只引用单个 CPU 并且 volatile 在它之外没有副作用,因此内存系统不相关。所以你是安全的在这里

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

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

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

PS:像 uint 这样的类型。标准类型 unsigned 并没有多打多少字,但每个人都能立即明白你的意思。如果您需要特定宽度,请使用 stdint.h 类型。在这里,你甚至应该使用 _Bool/bool,因为你似乎只有一个 true/false 标志。

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