在x86_64上,英特尔文档,第8.2.3.2节,第3A卷,说:
Intel-64内存订购模型不允许使用相同类型的操作重新排序加载或存储。也就是说,它确保按程序顺序看到负载,并且按程序顺序查看存储
我需要确保在写入内存地址时不会重新排列变量。
我想避免使用原子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写入(这里需要,对吗?)。
答案 0 :(得分:2)
虽然C标准保证按顺序发出易失性对象的访问,但与非易失性对象相比,它并不能保证它。
这两次访问都是volatile
,因此编译器必须按顺序生成这些,但省略号中的任何内容都可以自由移动**除非这些是{{1也是!
同样volatile
并不意味着,硬件将按照C标准的顺序执行。这可以通过CPU的适当屏障来保证,但是 - 取决于架构和障碍 - 它可能不足以用于其余硬件(高速缓存,总线,存储器系统等)
对于x86,保证排序(虽然不常见:许多RIS,例如ARM和PPC更放松,因此需要更仔细编写的代码)。由于您只引用单个CPU而volatile
在其外部没有副作用,因此内存系统不相关。所以你在安全方面这里。
对于内存映射外设和多处理器来说,情况要复杂得多,即如果你有超出单CPU的副作用。简单示例:第一次写入可能不会超过CPU缓存,因此读取相同内存页面的任何内容都可能只看到第二次写入或根本没有看到。 volatile
在这里还不够,你需要原子访问和(可能的)障碍。
对于您的代码,您可以在省略号volatile
(低效)中创建所有变量,或在它们周围添加编译器障碍(在volatile
之后和{{1之前) }})。这样编译器就不会将指令移到屏障之外。
最后:*p = 1;
不保证原子访问。因此,* p可能不是由单个指令写入的。 (我不会过分强调这一点,因为我假设*p = 0;
是volatile
,它通常是32位或64位x86目标上的32位,但对于8位或16位CPU来说这将是一个问题。 )为了安全起见,请使用uint
类型(自C11起)。
PS:类似unsigned int
的类型。标准类型_Atomic
并不是更多的类型,但每个人都会立即知道你的意思。如果您需要特定宽度,请使用uint
类型。在这里,您甚至应该使用unsigned
/ stdint.h
,因为您似乎只有一个_Bool
/ bool
标记。
请注意,所有这些功能也可用于低级代码。特别是true
(参见false
)也适用于此类目的,通常不需要任何特殊的库。如果它们也可以原子方式存储,它们的使用通常也不比非限定类型复杂(如果特定类型是原子的话,还有宏表示信号)。