深入 Linux kernel 中的 try_cmpxchg64()

介绍

在 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 into r/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

本文作者:文七安

本文链接:juejin.cn/post/732273...

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关推荐
雯宝2 小时前
mnt_init 函数
linux
律宏阔7 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔8 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴8 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千8 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei378 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei378 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei378 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW8 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW8 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux