使用gcc内联汇编的volatile与编译器屏障

时间:2011-06-24 11:40:42

标签: gcc constraints inline-assembly

在我们的产品中,我们有一个内联互斥实现,使用各种平台和编译器特定方法来处理硬件特定部分。对于试图“欺骗”的一些过度优化代码,我们的一个“规则”是,如果在互斥体之外和内部访问变量,那么该变量必须声明为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内存约束是否足以让代码用一对负载而不是一个负载生成?

1 个答案:

答案 0 :(得分:2)

__asm__ __volatile__ "memory" clobber是必需的,并将作为完整的重新排序屏障。变量volatile是不必要的。实际上,如果你看一下atomic_t的Linux内核定义,它就不会使用任何volatile修饰符,而是完全依赖于具有适当约束的__asm__ __volatile__语句。

另一方面,我认为它本身volatile实际上根本不禁止重新排序,只是完全缓存和优化价值,所以它对于同步目的来说毫无价值。