2011-06-24 112 views
5

在我们的产品中,我们有一个内嵌的互斥锁实现,使用各种平台和编译器特定的方法来实现特定于硬件的部分。对于一些试图“欺骗”的过度优化的代码,我们的一个“规则”是,如果一个变量在互斥体之外访问,那么该变量必须声明为volatile。我认为这也适用于不透明的互斥体实现(如pthread_mutex_lock/unlock),这引发了一场有趣的争论。使用gcc内联汇编的易失性与编译器屏障

有人曾声称这是编译器错误的表示(尤其是当互斥体实现内联并且对编译器“不透明”时)。我给下面的例子来争这个

int v = pSharedMem->myVariable ; 

__asm__ __volatile__(("isync" : : :"memory")) 

v = pSharedMem->myVariable ; 

在这种LinuxPPC GCC的代码片段,编译器不具备的iSync中的运行时间影响的任何知识,比我们可以通过内存约束告诉它其他。您会在互斥量采集的最后找到这样的异步指令,以防止互斥量在实际持有之前成功获取互斥量之后的任何指令执行(因此,如果在异步之前执行了加载,它会必须被丢弃)。

在此代码片段,我们有编译器屏障防止所述代码的重写,就好像它是在以下

int v = pSharedMem->myVariable ; 
v = pSharedMem->myVariable ; 

__asm__ __volatile__(("isync" : : :"memory")) 

__asm__ __volatile__(("isync" : : :"memory")) 

int v = pSharedMem->myVariable ; 
v = pSharedMem->myVariable ; 

(即:这两个编译器重新排序应该被禁止的易失性属性)

我们也有isync本身,它可以防止第一次重新排序运行时间(但我不认为阻止第二个不那么有趣)。

但是,我的问题是,如果myVariable是而不是声明为volatile,那么“memory”约束是否足以让gcc在isync之后必须重新加载“v”?我仍然倾向于对这种模式进行volatile,因为这种代码对所有平台特定的编译器内置函数都太敏感。也就是说,如果我们将讨论简化为GCC和此代码片段,那么这个asm内存约束是否足以让代码由一对加载而不是一个加载生成?

回答

2

__asm__ __volatile__"memory" clobber被要求并将充当重新排序的障碍。 volatile上的变量是不必要的。事实上,如果您查看Linux内核定义atomic_t,它不会使用任何volatile修饰符,并完全依赖具有适当约束的__asm__ __volatile__语句。另一方面,我认为volatile本身并不实际上禁止重新排序,而只是完全缓存和优化这个值,所以它对于同步目的是毫无价值的。

+0

是的,但我并不是谈论原子或锁定词的易失性,而是关于互斥体保护的数据。我也并不是暗示volatile具有任何同步值(即:同步是由互斥锁提供的,我想知道受互斥锁保护的数据)。如果数据也在互斥体之外访问,则在获取互斥体后强制重新加载该值需要volatile。 –

+0

这里有一些额外的上下文(产生这个问题的原始讨论) http://peeterjoot.wordpress.com/2011/06/21/an-unusual-volatile-required-example/ –

+0

@Peeter:我几乎确定样本存在错误。任何非内联函数调用总是强制从存储器中重新获取非本地数据,因为编译器无法确定该函数没有简单的指向该数据的指针并且只是将其写入,而这可能发生在单线程情况下那里定义了语义。另一方面,代码在有或者没有volatile的情况下是不正确的,因为在一些平台上的读取不是原子的。访问共享变量时不需要锁定,您需要使用特殊的同步原子操作。 –