介绍
在 Linux 内核中,常常用到 try_cmpxchg64()
这类操作。其实,它属于 SMP 系统中,提供数据交换的原子操作 API 之一,属于 xchg(), cmpxchg(), try_cmpxchg()
的变种。更多原子操作 API 可以参考:
Atomic types --- The Linux Kernel documentation
try_cmpxchg()
的用法如下:
c
bool try_cmpxchg(u{8,16,32,64} *ptr, u{8,16,32,64} *val, u{8,16,32,64} new);
该函数在下面 patch 中引入:
[PATCH 3/5] atomic: Introduce atomic_try_cmpxchg() - Peter Zijlstra
try_cmpxchg64{,_acquire,_release,_relaxed}
从下面的 patch 中引入,其中"64"表示操作的对象是 64 位。
[PATCH v3 0/2] locking/atomic/x86: Introduce arch_try_cmpxchg64 - Uros Bizjak
对于 x86 架构, cmpxchg(), try_cmpxchg()
的系列变体,其实现都类似。本质是通过一个宏定义,底层调用 CMPXCHG
指令。相较而言,带前缀 try_
的 API 和没有前缀的差别较大。
c
#define try_cmpxchg64(ptr, oldp, ...) \
({ \
typeof(ptr) __ai_ptr = (ptr); \
typeof(oldp) __ai_oldp = (oldp); \
kcsan_mb(); \
instrument_atomic_read_write(__ai_ptr, sizeof(*__ai_ptr)); \
instrument_read_write(__ai_oldp, sizeof(*__ai_oldp)); \
raw_try_cmpxchg64(__ai_ptr, __ai_oldp, __VA_ARGS__); \
})
CMPXCHG
指令
(SDM Vol 2.)
含义: Compare and Exchange,即比较并交换。
格式: CMPXCHG r/m64, r64
Compares the value in the AL, AX, EAX, or RAX register with the first operand (destination operand).
- If the two values are equal, the second operand (source operand) is loaded into the destination operand.
- Otherwise, the destination operand is loaded into the AL, AX, EAX or RAX register.
Compare RAX with r/m64
.
- If equal, ZF is set and
r64
is loaded intor/m64
. - Else, clear ZF and load
r/m64
into RAX.
cmpxchg64
and try_cmpxchg64
两者的实现过程如下,最终会分别指向 arch_cmpxchg
和 arch_try_cmpxchg
。
c
cmpxchg64
raw_cmpxchg64
arch_cmpxchg64
arch_cmpxchg
try_cmpxchg64
raw_try_cmpxchg64
arch_try_cmpxchg64
arch_try_cmpxchg
arch_cmpxchg
arch_cmpxchg(ptr, old, new)
ptr
为指针变量,old
, new
为普通变量。 比较 old
和 *ptr
的值是否相等 (*ptr
表示该指针指向的值) ,
- 若相等,将
new
赋给*ptr
,返回值为*ptr
的原始值,也即是*old
的值; - 否则,返回值为
*ptr
。
c
#define arch_cmpxchg(ptr, old, new) \
__cmpxchg(ptr, old, new, sizeof(*(ptr)))
#define __cmpxchg(ptr, old, new, size) \
__raw_cmpxchg((ptr), (old), (new), (size), LOCK_PREFIX)
arch_cmpxchg
最终的实现是 __raw_cmpxchg
。
需要注意的是,下面这段内联汇编使用的是 AT&T 语法,而 Intel SDM 手册中介绍的 CMPXCHG
指令用法是采用 Intel 语法,因此 cmpxchgb
的源操作数和目的操作数会和 SDM 的定义刚好相反。
c
#define __raw_cmpxchg(ptr, old, new, size, lock) \
({ \
__typeof__(*(ptr)) __ret; \
__typeof__(*(ptr)) __old = (old); \
__typeof__(*(ptr)) __new = (new); \
// 根据 `size` 分别使用指令 cmpxchgb/cmpxchgw/cmpxchgl/cmpxchgq
switch (size) { \
case __X86_CASE_B: \
{ \
volatile u8 *__ptr = (volatile u8 *)(ptr); \
asm volatile(lock "cmpxchgb %2,%1" \
: "=a" (__ret), "+m" (*__ptr) \
: "q" (__new), "0" (__old) \
: "memory"); \
break; \
} \
case __X86_CASE_W: \
// ...
} \
__ret; \
})
arch_try_cmpxchg
arch_try_cmpxchg(ptr, pold, new)
ptr
和 pold
为指针指针,new
, size
为普通变量。比较 *pold
和 *ptr
是否相等,
- 若相等,将
new
赋给*ptr
; - 否则,将
*ptr
赋给*pold
。 返回值表示之前的比较结果,1 为相等,0 为不等。
c
#define arch_try_cmpxchg(ptr, pold, new) \
__try_cmpxchg((ptr), (pold), (new), sizeof(*(ptr)))
#define __try_cmpxchg(ptr, pold, new, size) \
__raw_try_cmpxchg((ptr), (pold), (new), (size), LOCK_PREFIX)
arch_try_cmpxchg
最终的实现是 __raw_try_cmpxchg
。
c
#define __raw_try_cmpxchg(_ptr, _pold, _new, size, lock) \
({ \
bool success; \
__typeof__(_ptr) _old = (__typeof__(_ptr))(_pold); \
__typeof__(*(_ptr)) __old = *_old; \
__typeof__(*(_ptr)) __new = (_new); \
// 根据 `size` 分别使用指令 cmpxchgb/cmpxchgw/cmpxchgl/cmpxchgq
switch (size) { \
case __X86_CASE_B: \
{ \
volatile u8 *__ptr = (volatile u8 *)(_ptr); \
asm volatile(lock "cmpxchgb %[new], %[ptr]" \
CC_SET(z) \
: CC_OUT(z) (success), \
[ptr] "+m" (*__ptr), \
[old] "+a" (__old) \
: [new] "q" (__new) \
: "memory"); \
break; \
} \
case __X86_CASE_W: \
// ...
} \
if (unlikely(!success)) \
*_old = __old; \
likely(success); \
})
区别
cmpxchg64()
和 try_cmpxchg64()
功能类似,但后者使用起来更加简洁。
不同于 cmpxchg64
直接返回 *ptr
的原始值, try_cmpxchg64
会将 *ptr
和 old
的比较结果作为返回值,而且当两者不等时,会将 old
的值改为 *ptr
。也正因为 try_cmpxchg64
可能会改变 old
的值,因此传入的参数是 old
的指针 pold
。
cmpxchg
和 try_cmpxchg
也分别是对 __raw_cmpxchg
和 __raw_try_cmpxchg
的封装,下面两种实现是等效的:
c
# opt 1
for (;;) {
new = val $op $imm;
old = cmpxchg(ptr, val, new);
if (old == val)
break;
val = old;
}
# opt 2
do {
} while (!try_cmpxchg(ptr, &val, val $op $imm));
Linux 中一个使用 try_cmpxchg64
优化 cmpxchg64
的例子:
[PATCH] KVM: x86/mmu: Use try_cmpxchg64 in tdp_mmu_set_spte_atomic - Uros Bizjak
本文作者:文七安
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!