Linux内核深度解析之内核互斥技术——原子变量

原子变量

原子变量用来实现对整数的互斥访问,通常用来实现计数器。

内核定义了3种原子变量:

(1)整数原子变量,数据类型是atomic_t

include/linux/types.h
typedef struct {
	int counter;
} atomic_t;

(2)长整数原子变量,数据类型是atomic_long_t

(3)64位整数原子变量,数据类型是atomic64_t

初始化静态原子变量的方法(以整数原子变量为例):

atomic_t <name> = ATOMIC_INIT(n);

在运行中动态初始化原子变量的方法(以整数原子变量为例):

atomic_set(v, i);

把原子变量v的值初始化为i。

常用的原子变量的操作(以整数原子变量为例):

(1)读取原子变量v的值

atomic_read(v);

(2)把原子变量v的值加上i,并且返回新值

atomic_add_reture(i, v);

(3)把原子变量v的值加上i

atomic_add(i, v);

(4)把原子变量v的值加上1

 atomic_inc(v);

(5)如果原子变量v的值不是u,那么把原子变量v的值加上a,并且返回1,否则返回0

bool atomic_add_unless(atomic_t *v, int a, int u);

(6)如果原子变量v的值不是0,那么把原子变量v的值加上1,并且返回1,否则返回0

atomic_inc_not_zero(v);

(7)把原子变量v的值减去i,并且返回新值

atomic_sub_return(i, v);

(8)把原子变量v的值减去i,测试新值是否为0,如果为0,返回真

atomic_sub_and_test(i, v);

(9)把原子变量v的值减去i

atomic_sub(i, v);

(10)把原子变量v的值减去1

atomic_dec(v)

(11)执行原子比较交换,如果原子变量v的值等于old,那么把原子变量v的值设置为new,返回值总是原子变量v的旧值

atomic_cmpxchg(v, old, new);

ARM64处理器的原子变量实现

原子变量需要各种处理器架构提供特殊的指令支持,ARM64处理器提供了以下指令:

(1)独占加载指令ldxr(Load Exclusive Register)

(2)独占存储指令stxr(Store Exclusive Register)

独占加载指令从内存加载32位或64位数据到寄存器中,把访问的物理地址标记为独占访问。

独占存储指令从寄存器存储32位或64位数据到内存中,检查目标内存地址是否被标记为独占访问。如果是独占访问,那么存储到内存中,并且返回状态值0来表示存储成功;否则不存储到内存中,并且返回1。

例如,在函数atomic_add(i, v)的功能把原子变量v的值加上i,ARM64架构的实现如下:

arch/arm64/include/asm/atomic_ll_sc.h
__LL_SC_INLINE void							\
__LL_SC_PREFIX(atomic_##op(int i, atomic_t *v))				\
{									\
	unsigned long tmp;						\
	int result;							\
									\
	asm volatile("// atomic_" #op "\n"				\
"	prfm	pstl1strm, %2\n"					\
"1:	ldxr	%w0, %2\n"						\		/* 使用独占加载指令把原子变量v的值加载到32位寄存器中 */
"	" #asm_op "	%w0, %w0, %w3\n"				\		/* 把寄存器的值加/减/...i */
"	stxr	%w1, %w0, %2\n"						\		/* 使用独占存储指令把寄存器的值写到原子变量v */
"	cbnz	%w1, 1b"						\		/* 如果独占存储指令返回1,表示存储失败,那么回到标号1处,代码重新执行 */
	: "=&r" (result), "=&r" (tmp), "+Q" (v->counter)		\
	: "Ir" (i));							\
}									\
__LL_SC_EXPORT(atomic_##op);

在非常大的系统中,处理器很多,竞争很激烈,使用独占加载指令和独占存储指令可能需要重试很多次才能成功,性能很差。ARM v8.1标准实现了大系统扩展(Large System Extensions,LSE),专门设计了原子指令,提供了原子加载指令stadd:首先从内存加载32位或64位数据到寄存器中,然后把寄存器加上指定值,把结果写会内存。

arch/arm64/include/asm/atomic_lse.h
#define ATOMIC_OP(op, asm_op)						\
static inline void atomic_##op(int i, atomic_t *v)			\
{									\
	register int w0 asm ("w0") = i;					\
	register atomic_t *x1 asm ("x1") = v;				\
									\
	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(op),		\
"	" #asm_op "	%w[i], %[v]\n")					\
	: [i] "+r" (w0), [v] "+Q" (v->counter)				\
	: "r" (x1)							\
	: __LL_SC_CLOBBERS);						\
}

猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/106961279