cmpxchg 64位整数的示例

时间:2009-05-07 06:16:53

标签: gcc assembly x86-64 inline-assembly

我在i686架构中使用cmpxchg(比较和交换)进行32位比较和交换,如下所示。

(编者注:最初的32位示例是错误的,但问题不在于它。我相信这个版本是安全的,并且作为奖励也可以正确编译x86-64。还要注意不需要或建议使用内联asm; __atomic_compare_exchange_n或较早__sync_bool_compare_and_swap适用于i486和x86-64 上的int32_tint64_t。但是这个问题是关于使用内联asm进行的,以防你仍然想要。)

// note that this function doesn't return the updated oldVal
static int CAS(int *ptr, int oldVal, int newVal)
{
    unsigned char ret;
    __asm__ __volatile__ (
            "  lock\n"
            "  cmpxchgl %[newval], %[mem]\n"
            "  sete %0\n"
            : "=q" (ret), [mem] "+m" (*ptr), "+a" (oldVal)
            : [newval]"r" (newVal)
            : "memory");    // barrier for compiler reordering around this

    return ret;   // ZF result, 1 on success else 0
}

64位比较和交换的x86_64架构的等价物

static int CAS(long *ptr, long oldVal, long newVal)
{
    unsigned char ret;
    // ?
    return ret;
}

4 个答案:

答案 0 :(得分:7)

x86_64指令集具有cmpxchgq(四字q)指令,用于8字节(64位)比较和交换。

还有一条cmpxchg8b指令可用于8字节数量,但设置起来比较复杂,需要您使用edx:eaxecx:ebx而不是更自然的64-位rax。其存在的原因几乎肯定与英特尔在x86_64出现之前很久就需要进行64位比较和交换操作这一事实有关。它仍然存在于64位模式,但不再是唯一的选择。

但是,如上所述,cmpxchgq可能是64位代码的更好选择。


如果需要cmpxchg一个16字节的对象,则cmpxchg8b的64位版本为cmpxchg16b。最早的AMD64 CPU缺少它,因此编译器不会在16B对象上为std::atomic::compare_exchange生成它,除非你启用-mcx16(对于gcc)。但是汇编程序会组装它,但要注意你的二进制文件不会在最早的K8 CPU上运行。 (这仅适用于cmpxchg16b,而不适用于64位模式下的cmpxchg8bcmpxchgq)。

答案 1 :(得分:2)

CMPXCHG8B

__forceinline int64_t interlockedCompareExchange(volatile int64_t & v,int64_t exValue,int64_t cmpValue)
{
  __asm {
    mov         esi,v
    mov         ebx,dword ptr exValue
    mov         ecx,dword ptr exValue + 4
    mov         eax,dword ptr cmpValue
    mov         edx,dword ptr cmpValue + 4
    lock cmpxchg8b qword ptr [esi]
  }
}

答案 2 :(得分:1)

x64架构支持使用良好的旧cmpexch指令进行64位比较交换。或者您也可以使用更复杂的cmpexch8b指令(来自“AMD64 Architecture Programmer's Manual Volume 1: Application Programming”):

  

CMPXCHG指令比较a   ALrAX注册表中的值   第一个(目标)操作数,和   设置算术标志(ZFOFSF,   根据结果​​,AFCFPF)。   如果比较的值相等,则   源操作数被加载到   目的地操作数。如果他们不是   等于,第一个操作数被加载   进入累加器。 CMPXCHG可以   过去试图拦截信号量,   即测试其状态是否是免费的,如果   所以,将一个新值加载到   信号量,使其状态繁忙。该   进行测试和加载   原子地,这样并发   使用的进程或线程   信号量访问共享对象   不会发生冲突。

     

CMPXCHG8B   指令比较64位值   在EDX:EAX寄存器中使用64位   记忆位置。如果值是   等于,零标志(ZF)被设置,并且   ECX:EBX值被复制到   记忆位置。否则,ZF   标志被清除,并且内存值   被复制到EDX:EAX

     

CMPXCHG16B   指令比较128位值   在RDX:RAXRCX:RBX寄存器中   具有128位内存位置。如果   值相等,零标志(ZF)   已设置,RCX:RBX值为。{1}   复制到内存位置。   否则,清除ZF标志,并且   内存值被复制到rDX:rAX

如果无法推断操作数的大小,则不同的汇编程序语法可能需要具有指令助记符中指定的操作的长度。对于GCC的内联汇编程序可能就是这种情况 - 我不知道。

答案 3 :(得分:-1)

使用AMD64架构程序员手册V3中的cmpxchg8B:

将EDX:EAX寄存器与64位存储器位置进行比较。如果相等,则将零标志(ZF)设置为1并将ECX:EBX寄存器复制到存储器位置。除此以外, 将内存位置复制到EDX:EAX并清除零标志。

我使用cmpxchg8B在x86-64机器中实现一个简单的互斥锁定功能。这是代码

.text
.align 8
.global mutex_lock
mutex_lock:
    pushq   %rbp
    movq    %rsp,   %rbp

    jmp .L1

.L1:
    movl    $0, %edx
    movl    $0, %eax
    movl    $0, %ecx
    movl    $1, %ebx
    lock    cmpxchg8B   (%rdi)
    jne .L1
    popq    %rbp
    ret