我在i686架构中使用cmpxchg(比较和交换)进行32位比较和交换,如下所示。
(编者注:最初的32位示例是错误的,但问题不在于它。我相信这个版本是安全的,并且作为奖励也可以正确编译x86-64。还要注意不需要或建议使用内联asm; __atomic_compare_exchange_n
或较早__sync_bool_compare_and_swap
适用于i486和x86-64 上的int32_t
或int64_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;
}
答案 0 :(得分:7)
x86_64
指令集具有cmpxchgq
(四字q
)指令,用于8字节(64位)比较和交换。
还有一条cmpxchg8b
指令可用于8字节数量,但设置起来比较复杂,需要您使用edx:eax
和ecx: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位模式下的cmpxchg8b
或cmpxchgq
)。
答案 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
指令比较aAL
或rAX
注册表中的值 第一个(目标)操作数,和 设置算术标志(ZF
,OF
,SF
, 根据结果,AF
,CF
,PF
)。 如果比较的值相等,则 源操作数被加载到 目的地操作数。如果他们不是 等于,第一个操作数被加载 进入累加器。CMPXCHG
可以 过去试图拦截信号量, 即测试其状态是否是免费的,如果 所以,将一个新值加载到 信号量,使其状态繁忙。该 进行测试和加载 原子地,这样并发 使用的进程或线程 信号量访问共享对象 不会发生冲突。
CMPXCHG8B
指令比较64位值 在EDX:EAX
寄存器中使用64位 记忆位置。如果值是 等于,零标志(ZF
)被设置,并且ECX:EBX
值被复制到 记忆位置。否则,ZF
标志被清除,并且内存值 被复制到EDX:EAX
。
CMPXCHG16B
指令比较128位值 在RDX:RAX
和RCX: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