深入 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_cmpxchgarch_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) ptrpold 为指针指针,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 会将 *ptrold 的比较结果作为返回值,而且当两者不等时,会将 old 的值改为 *ptr 。也正因为 try_cmpxchg64 可能会改变 old 的值,因此传入的参数是 old 的指针 pold

cmpxchgtry_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 许可协议。转载请注明出处!

相关推荐
妙哉73612 分钟前
零基础学安全--shell(8)脚本相互利用
linux·运维·服务器
Curtis098029 分钟前
RHCE——SELinux
linux·运维·服务器
团子tuan1 小时前
Ubuntu20.04下安装Matlab2018
linux·matlab
daizikui1 小时前
内网穿透产品 frp ngrok FastTunnel
linux·运维·网络·nginx
HealthScience1 小时前
如何正确书写sh文件/sh任务?bash任务
linux·运维·服务器
木子Linux1 小时前
【Linux打怪升级记 | 报错02】-bash: 警告:setlocale: LC_TIME: 无法改变区域选项 (zh_CN.UTF-8)
linux·运维·服务器·centos·ssh
LinuxST2 小时前
30、Firefly-rk3399定时器
linux·windows·stm32·嵌入式硬件·ubuntu
fancybit2 小时前
ubuntu 20 桌面版安装备忘
linux·运维·ubuntu
滴滴哒哒答答2 小时前
Ubuntu 硬盘分区并挂载
linux·运维·ubuntu
xiaobai12 32 小时前
VM+Ubuntu18.04+XSHELL+VSCode环境配置
linux·ubuntu·ssh