Linux内核 原子操作分析

本文详细介绍了原子操作的概念,包括其定义、应用场景和原理,以及Linux内核中常用的原子操作接口。通过分析4.9版本的内核源码,展示了如何利用硬件支持和内嵌汇编实现原子操作,确保数据一致性、避免竞态条件,并探讨了Arm处理器的独占访问机制。
摘要由CSDN通过智能技术生成

1、概念

1.1 定义

原子操作是指在执行过程中不会被中断的操作,要么全部执行成功,要么全部不执行。原子操作是不可分割的,即使在多线程或并发环境下,也不会被其他操作中断。原子操作通常用于确保对共享资源的访问是线程安全的,避免竞态条件和数据不一致性。

1.2 场景

为什么要使用原子操作:

  • 确保数据的一致性:在多线程或并发环境中,多个线程同时访问共享资源可能导致数据不一致的问题,使用原子操作可以避免这种情况发生。
  • 避免竞态条件:原子操作可以保证对共享资源的操作是原子的,避免出现竞态条件,确保程序的正确性和稳定性。
  • 提高性能:原子操作通常是底层硬件支持的操作,执行效率高,可以提高程序的性能。

1.3. 原理

原子操作的实现通常依赖于硬件的支持或者操作系统提供的原子操作指令。在现代处理器中,通常会提供一些原子操作指令,如 Compare-and-Swap (CAS) 指令,用于实现原子操作。CAS 操作是一个原子指令,它会比较内存中的值和一个期望的值,如果相等,则将新值写入内存,否则不做任何操作。

2、分析

2.1 常见接口介绍

内核中使用到的一些常见的原子操作接口:

函数名函数原型说明
atomic_setvoid atomic_set(atomic_t *v, int i)设置原子变量的值
atomic_readint atomic_read(const atomic_t *v)读取原子变量的值
atomic_addvoid atomic_add(int i, atomic_t *v)原子地将一个值加到原子变量上
atomic_subvoid atomic_sub(int i, atomic_t *v)原子地将一个值从原子变量上减去
atomic_incvoid atomic_inc(atomic_t *v)原子地增加原子变量的值
atomic_decvoid atomic_dec(atomic_t *v)原子地减少原子变量的值
atomic_inc_and_testint atomic_inc_and_test(atomic_t *v)原子地增加原子变量的值,并检查是否为零
atomic_dec_and_testint atomic_dec_and_test(atomic_t *v)原子地减少原子变量的值,并检查是否为零
atomic_add_returnint atomic_add_return(int i, atomic_t *v)原子地将一个值加到原子变量上,并返回加法后的值
atomic_sub_returnint atomic_sub_return(int i, atomic_t *v)原子地将一个值从原子变量上减去,并返回减法后的值
atomic_inc_returnint atomic_inc_return(atomic_t *v)原子地增加原子变量的值,并返回增加后的值
atomic_dec_returnint atomic_dec_return(atomic_t *v)原子地减少原子变量的值,并返回减少后的值
atomic_cmpxchgint atomic_cmpxchg(atomic_t *v, int old, int new)比较并交换操作,原子地比较原子变量的值并在匹配时替换为新值

2.2 分析

我们以atomic_add为例分析内核中原子操作的实现,选用4.9版本的内核源码,在文件arch/arm/include/asm/atomic.h中有以下定义:

#define ATOMIC_OPS(op, c_op, asm_op)			\
	ATOMIC_OP(op, c_op, asm_op)					\
	ATOMIC_OP_RETURN(op, c_op, asm_op)			\
	ATOMIC_FETCH_OP(op, c_op, asm_op)

ATOMIC_OPS(add, +=, add)
ATOMIC_OPS(sub, -=, sub)

将ATOMIC_OPS(add, +=, add)使用上面的宏定义进行解析可以获得:

ATOMIC_OP(add, +=, add)
ATOMIC_OP_RETURN(add, +=, add)
ATOMIC_FETCH_OP(add, +=, add)

以其中的第一个宏ATOMIC_OP为例,在内核中有如下代码:

// ARMV6架构以上
#if __LINUX_ARM_ARCH__ >= 6

/*
 * ARMv6 UP and SMP safe atomic ops.  We use load exclusive and
 * store exclusive to ensure that these are atomic.  We may loop
 * to ensure that the update happens.
 */

#define ATOMIC_OP(op, c_op, asm_op)							\
static inline void atomic_##op(int i, atomic_t *v)			\
{															\
	unsigned long tmp;										\
	int result;												\
															\
	prefetchw(&v->counter);									\
	__asm__ __volatile__("@ atomic_" #op "\n"				\
"1:	ldrex	%0, [%3]\n"										\
"	" #asm_op "	%0, %0, %4\n"								\
"	strex	%1, %0, [%3]\n"									\
"	teq	%1, #0\n"											\
"	bne	1b"													\
	: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)		\
	: "r" (&v->counter), "Ir" (i)							\
	: "cc");												\
}	
……
……
……
								
#else /* ARM_ARCH_6 */

#define ATOMIC_OP(op, c_op, asm_op)					\
static inline void atomic_##op(int i, atomic_t *v)	\
{													\
	unsigned long flags;							\
													\
	raw_local_irq_save(flags);						\
	v->counter c_op i;								\
	raw_local_irq_restore(flags);					\
}													\
……
……
……
#endif /* __LINUX_ARM_ARCH__ */

其中上半部分是对于ArmV6架构以上,下半部分是针对ArmV6架构,先看下半部分,将宏定义中的参数带入后得到以下代码,可以看到对于ArmV6架构,在操作原子变量前进行关闭中断处理,操作完成后进行中断恢复处理,因为ArmV6是单核CPU,不支持SMP,因此不需要考虑会有其他线程抢占,只需要保证不被中断影响即可。

static inline void atomic_add(int i, atomic_t *v)	\
{													\
	unsigned long flags;							\
													\
	raw_local_irq_save(flags);						\
	v->counter += i;								\
	raw_local_irq_restore(flags);					\
}

对于ArmV6以上的版本,带入参数后得到的代码如下:

static inline void atomic_add(int i, atomic_t *v)			\
{															\
	unsigned long tmp;										\
	int result;												\
															\
	prefetchw(&v->counter);									\
	__asm__ __volatile__("@ atomic_" add "\n"				\
"1:	ldrex	%0, [%3]\n"										\
"	add 	%0, %0, %4\n"								    \
"	strex	%1, %0, [%3]\n"									\
"	teq	%1, #0\n"											\
"	bne	1b"													\
	: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)		\
	: "r" (&v->counter), "Ir" (i)							\
	: "cc");												\
}

代码中使用了内嵌汇编的编程,在执行读取-修改-存入三步的时候,读取和存入使用了ldrex 和strex ,这两条命令则是实现原子操作的关键:

  • 当一个处理器执行 ldrex 指令时,ldrex 指令会将内存位置的值加载到寄存器中,并且标记该内存位置为“独占”状态。strex 用于尝试将一个值写回到内存中,并且会检查内存位置是否仍处于“独占”状态。如果内存位置仍处于“独占”状态,strex 操作会成功,否则会失败。
  • 在一个处理器执行ldrex指令获取独占访问权后,另一个处理器也尝试获取独占访问权。这种情况下,如果两个处理器都执行ldrex指令后,其中一个处理器执行了strex指令成功,而另一个处理器在执行strex指令时会发现独占访问已经被释放,此时第二个处理器的strex操作会失败。这种情况下,ARM处理器提供了一种机制来解决这种并发情况,即在strex指令执行失败时,第二个处理器可以重新执行ldrexstrex指令,或者采取其他处理方式。这样可以确保在并发情况下,内存的操作仍然是原子的。

Exclusive Access(独占访问)是由ARM处理器硬件实现的机制。ARM处理器提供了一些特定的指令(如ldrexstrex)来支持独占访问的操作。当处理器执行这些指令时,硬件会负责处理内存访问的原子性和独占状态的管理。

在上述代码中teq    %1, #0和bne    1b就是对strex的返回值进行处理,如果返回失败的话就重新跳转到"1:    ldrex    %0, [%3]\n"处进行执行:

3、总结

本文结合内核源码简单讲解了原子操作的实现过程。

  • 28
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值