在我们的产品中,我们有一个内联互斥实现,使用各种平台和编译器特定方法来处理硬件特定部分。对于试图“欺骗”的一些过度优化代码,我们的一个“规则”是,如果在互斥体之外和内部访问变量,那么该变量必须声明为volatile。我认为这也适用于不透明的互斥体实现(例如pthread_mutex_lock / unlock),这引发了一场有趣的争论。
一个人断言这是编译器错误的指示(特别是当互斥锁实现被内联并且对编译器“不透明”时)。我给出了以下示例来解释这个
int v = pSharedMem->myVariable ;
__asm__ __volatile__(( "isync" : : :"memory" ))
v = pSharedMem->myVariable ;
在这个LinuxPPC gcc代码片段中,编译器不了解isync的运行时效果,除了我们可以通过内存约束告诉它的内容。你会在互斥锁的尾端发现这样的isync指令,以防止在实际保持互斥锁之前执行成功获取互斥锁之后的指令(所以如果在异步之前已经执行了加载,那么必须被丢弃。
在这段代码片段中,我们有编译器屏障,可以防止重写代码,就好像它是以下一样
int v = pSharedMem->myVariable ;
v = pSharedMem->myVariable ;
__asm__ __volatile__(( "isync" : : :"memory" ))
或
__asm__ __volatile__(( "isync" : : :"memory" ))
int v = pSharedMem->myVariable ;
v = pSharedMem->myVariable ;
(即: volatile 属性应禁止这两个编译器重新排序)
我们还有isync本身可以防止在运行时进行第一次重新排序(但我认为不会阻止第二次重新排序)。
但是,我的问题是,如果myVariable 不声明为volatile,那么“内存”约束是否足以使gcc在异步后必须重新加载“v”?我仍然倾向于为这种模式强制使用volatile,因为这种代码对于所有特定于平台的编译器内置都太敏感了。也就是说,如果我们将讨论简化为GCC和这个代码片段,这个asm内存约束是否足以让代码用一对负载而不是一个负载生成?
答案 0 :(得分:2)
__asm__ __volatile__
"memory"
clobber是必需的,并将作为完整的重新排序屏障。变量volatile
是不必要的。实际上,如果你看一下atomic_t的Linux内核定义,它就不会使用任何volatile
修饰符,而是完全依赖于具有适当约束的__asm__ __volatile__
语句。
另一方面,我认为它本身volatile
实际上根本不禁止重新排序,只是完全缓存和优化价值,所以它对于同步目的来说毫无价值。