gcc的原子操作和代码生成

LiK*_*Kao 14 c assembly gcc code-generation atomic

我正在考虑通过gcc查看为原子操作生成的一些程序集.我尝试了以下短序列:

int x1;
int x2;

int foo;

void test()
{
  __atomic_store_n( &x1, 1, __ATOMIC_SEQ_CST );
  if( __atomic_load_n( &x2  ,__ATOMIC_SEQ_CST ))
    return;

  foo = 4;
}
Run Code Online (Sandbox Code Playgroud)

看看Herb Sutter关于代码生成的原子武器谈话,他提到X86手册要求xchg用于原子存储和简单mov的原子读取.所以我期待的是:

test():
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $1, %eax
    xchg    %eax, x1(%rip)
    movl    x2(%rip), %eax
    testl   %eax, %eax
    setne   %al
    testb   %al, %al
    je      .L2
    jmp     .L1
.L2:
    movl    $4, foo(%rip)
.L1:
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

由于锁定xchg指令,内存栅栏是隐式的.

但是,如果我使用gcc -march=core2 -S test.cc以下方法编译它:

test():
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $1, %eax
    movl    %eax, x1(%rip)
    mfence
    movl    x2(%rip), %eax
    testl   %eax, %eax
    setne   %al
    testb   %al, %al
    je      .L2
    jmp     .L1
.L2:
    movl    $4, foo(%rip)
.L1:
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

因此,xchggcc 不是使用操作,而是使用mov + mfence组合.这个代码生成的原因是什么,它与根据Herb Sutter的x86架构强制要求的代码不同?

amd*_*mdn 11

xchg当目标是内存位置时,该指令隐含了锁定语义.这意味着您可以原子方式将寄存器的内容与内存位置的内容进行交换.

问题中的例子是做一个原子商店,而不是交换.x86架构内存模型保证在多处理器/多核系统中,由一个线程完成的存储将由其他线程以该顺序看到......因此内存移动就足够了.话虽如此,有较旧的Intel CPU和一些克隆,这个区域存在缺陷,并且xchg需要在这些CPU上作为解决方法.请参阅此维基百科有关自旋锁的文章的重要优化部分:

http://en.wikipedia.org/wiki/Spinlock#Example_implementation

哪个州

上面的简单实现适用于使用x86架构的所有CPU.但是,可以进行许多性能优化:

在后来的x86架构实现中,spin_unlock可以安全地使用解锁的MOV而不是速度较慢的锁定XCHG.这是由于微妙的内存排序规则支持这一点,即使MOV不是一个完整的内存屏障.但是,某些处理器(一些Cyrix处理器,一些Intel Pentium Pro修订版(由于错误)以及早期的Pentium和i486 SMP系统)将做错事,受锁保护的数据可能会被破坏.在大多数非x86体系结构中,必须使用显式内存屏障或原子指令(如示例中所示).在某些系统上,例如IA-64,有一些特殊的"解锁"指令可以提供所需的内存排序.

内存屏障mfence确保所有存储都已完成(CPU内核中的存储缓冲区为空并且值存储在缓存或内存中),它还确保将来的加载不会无序执行.

在1999年英特尔架构师对Linus Torvalds的回复中,"正式"澄清了一个MOV足以解锁互斥锁(无需序列化或内存障碍)的事实.

http://lkml.org/lkml/1999/11/24/90.

我想后来发现它不适用于一些旧的x86处理器.