快捷搜索:  汽车  科技

linux中内核配置的三种方法(一文读懂Linux内核中的原子操作函数)

linux中内核配置的三种方法(一文读懂Linux内核中的原子操作函数)如果一个原子操作函数要返回修改之前的原子变量的值,那么该函数名会含有“_fetch”字符串,并且是在表示具体操作字符串的前面。例如,atomic_fetch_and、atomic_fetch_or_acquire等。3)要返回修改之前的值除了atomic_t类型外,Linux内核还支持对64位和长整形的atomic64_t和atomic_long_t类型,它们也都有对应的基本原子操作函数,只不过函数前缀名分别是atomic64_和atomic_long_,具体就不在赘述了。2)要返回修改过后的值如果一个原子操作函数要返回修改过后的原子变量的值,那么该函数名会含有“_return”字符串,并且是在表示具体操作字符串的后面。例如,atomic_add_return、atomic_dec_return_relaxed等。

一、原子操作的命名规则

Linux内核中提供了各种各样的原子操作函数。除了最原始的读取和设置之外,还包括各种运算,以及位置操作等等。而且有的原子操作还要返回操作过后变量的值,有的要返回操作之前变量的值,如果再牵涉到内存屏障的问题,将这些因素组合起来,有非常多的原子操作函数。这些原子操作函数看似非常杂乱,其实函数命名是有规律的。

1)基本型

基本型包括非RMW(Read Modifiy Write)的读写操作,以及RMW的算术和位操作等。

非RMW的操作很简单,只有两个,即用来读取的atomic_read()和用来写入的atomic_set()。一般对单独变量的读取或写入操作本身都是原子的,如果代码中只对单个变量进行读写操作,而从来没对它使用RMW操作,那一般说明你用错了,没必要使用内核提供的原子操作。

除了atomic_t类型外,Linux内核还支持对64位和长整形的atomic64_t和atomic_long_t类型,它们也都有对应的基本原子操作函数,只不过函数前缀名分别是atomic64_和atomic_long_,具体就不在赘述了。

2)要返回修改过后的值

如果一个原子操作函数要返回修改过后的原子变量的值,那么该函数名会含有“_return”字符串,并且是在表示具体操作字符串的后面。例如,atomic_add_return、atomic_dec_return_relaxed等。

3)要返回修改之前的值

如果一个原子操作函数要返回修改之前的原子变量的值,那么该函数名会含有“_fetch”字符串,并且是在表示具体操作字符串的前面。例如,atomic_fetch_and、atomic_fetch_or_acquire等。

更多Linux内核视频教程文档资料免费领取后台私信【内核】自行获取。

linux中内核配置的三种方法(一文读懂Linux内核中的原子操作函数)(1)

4)有Acquire和Release单向屏障语义

如果一个原子操作函数还需要包括Acquire或者Release单向屏障语义,那么该函数名会有“_acquire”或者“_release”后缀。例如,atomic_xchg_acquire、atomic_cmpxchg_release等。

相反的,如果一个原子操作函数名有“_relaxed”后缀,表示这个函数没有被任何内存屏障保护,可以被任意重排序。例如,atomic_add_return_relaxed、atomic_xchg_relaxed等。

只有当一个原子操作函数要返回值的时候才有可能添加_acquire、_release和_relaxed后缀。

二、原子操作的重排序规则

Linux内核的原子操作只保证对单一变量的某个操作是原子的,多个CPU同时操作时,不会出现中间的错误状态。但是,对这个原子操作本身,并不一定保证其执行的顺序,在SMP系统下,有可能会出现重排序的问题。因此,前面也提到过,有些原子操作函数自己就带了一定的内存屏障的语义。具体有没有带,带了多少,可以通过函数名看出来,具体规则如下:

  • 非RMW的原子操作可以被任意重排序;
  • RMW的原子操作,如果没有返回值可以被任意重排序;
  • RMW的原子操作,如果有返回值,并且没有_relaxed、_acquire和_release后缀,是有内存屏障保护的,可以保证不被重排序;
  • RMW的原子操作,如果有返回值,且有_relaxed后缀,没有任何内存屏障保护,可以被任意重排序;
  • RMW的原子操作,如果有返回值,且有_acquire后缀,表示读(RMW中的R)操作是有Acquire单向屏障保护的;
  • RMW的原子操作,如果有返回值,且有_release后缀,表示写(RMW中的W)操作是有Release单向屏障保护的;
  • RMW的原子操作,如果有条件判断,那么条件是否的那部分会被任意重排序。

对于那些不提供内存屏障语义的原子操作来说,为了保证其本身不被重排序,还需要显式地在其前面或后面使用内存屏障。Linux内核提供了两个函数,分别是smp_mb__before_atomic()用于原子操作函数的前面,和smp_mb__after_atomic()用于原子操作函数的后面。注意,函数的中间是“__”而不是“_”,而且说起来是两个不同的函数,但是内核实际上都把它们映射成了普通的通用内存屏障:

#ifndef __smp_mb__before_atomic #define __smp_mb__before_atomic() __smp_mb() #endif #ifndef __smp_mb__after_atomic #define __smp_mb__after_atomic() __smp_mb() #endif ...... #ifndef smp_mb__before_atomic #define smp_mb__before_atomic() __smp_mb__before_atomic() #endif #ifndef smp_mb__after_atomic #define smp_mb__after_atomic() __smp_mb__after_atomic() #endif三、ARMv8架构下原子操作的实现

下面我们来看看上面提到的所有原子操作函数在Linux内核中是如何实现的,由于原子操作函数实在太多了,我们挑出几个有代表性的来分析,分别是atomic_add_return_acquire()、atomic_fetch_or_release()和atomic_cmpxchg()。它们的定义如下(代码位于include/asm-generic/atomic-instrumented.h中):

#if defined(arch_atomic_add_return_acquire) static inline int atomic_add_return_acquire(int i atomic_t *v) { kasan_check_write(v sizeof(*v)); return arch_atomic_add_return_acquire(i v); } #define atomic_add_return_acquire atomic_add_return_acquire #endif ...... #if defined(arch_atomic_fetch_or_release) static inline int atomic_fetch_or_release(int i atomic_t *v) { kasan_check_write(v sizeof(*v)); return arch_atomic_fetch_or_release(i v); } #define atomic_fetch_or_release atomic_fetch_or_release #endif ...... #if !defined(arch_atomic_cmpxchg_relaxed) || defined(arch_atomic_cmpxchg) static inline int atomic_cmpxchg(atomic_t *v int old int new) { kasan_check_write(v sizeof(*v)); return arch_atomic_cmpxchg(v old new); } #define atomic_cmpxchg atomic_cmpxchg #endif

可以看出来,具体的原子操作实现都是和架构相关的。那万一哪个原子操作在当前平台下没有被定义怎么办呢?例如,在ARMv8下,arch_atomic_try_cmpxchg就没有被定义,那么atomic_try_cmpxchg也不会被定义:

#if defined(arch_atomic_try_cmpxchg) static inline bool atomic_try_cmpxchg(atomic_t *v int *old int new) { kasan_check_write(v sizeof(*v)); kasan_check_write(old sizeof(*old)); return arch_atomic_try_cmpxchg(v old new); } #define atomic_try_cmpxchg atomic_try_cmpxchg #endif

对于那些没有平台架构没有定义的原子操作,还有一次补救的机会,例如在ARMv8下,atomic_try_cmpxchg函数真正的定义如下(代码位于include/linux/atomic-fallback.h中):

#ifndef atomic_try_cmpxchg static inline bool atomic_try_cmpxchg(atomic_t *v int *old int new) { int r o = *old; r = atomic_cmpxchg(v o new); if (unlikely(r != o)) *old = r; return likely(r == o); } #define atomic_try_cmpxchg atomic_try_cmpxchg #endif

所以,atomic_try_cmpxchg函数其实是基于已有的atomic_cmpxchg函数来实现的。

接着上面说,所以atomic_add_return_acquire函数实际调用的是arch_atomic_add_return_acquire函数,atomic_fetch_or_release函数实际调用的是arch_atomic_fetch_or_release函数,atomic_cmpxchg函数实际调用的是arch_cmpxchg_relaxed函数,它们分别定义如下(代码位于arch/arm64/include/asm/atomic.h中):

#define arch_atomic_add_return_acquire arch_atomic_add_return_acquire ...... #define arch_atomic_fetch_or_release arch_atomic_fetch_or_release ...... #define arch_atomic_cmpxchg(v old new) \ arch_cmpxchg(&((v)->counter) (old) (new))

arch_cmpxchg函数先放一下,后面说。arch_atomic_add_return_acquire函数和atomic_fetch_or_release函数都是由下面的宏定义的:

#define ATOMIC_FETCH_OP(name op) \ static inline int arch_##op##name(int i atomic_t *v) \ { \ return __lse_ll_sc_body(op##name i v); \ } #define ATOMIC_FETCH_OPS(op) \ ATOMIC_FETCH_OP(_relaxed op) \ ATOMIC_FETCH_OP(_acquire op) \ ATOMIC_FETCH_OP(_release op) \ ATOMIC_FETCH_OP( op) ATOMIC_FETCH_OPS(atomic_fetch_andnot) ATOMIC_FETCH_OPS(atomic_fetch_or) ATOMIC_FETCH_OPS(atomic_fetch_xor) ATOMIC_FETCH_OPS(atomic_fetch_add) ATOMIC_FETCH_OPS(atomic_fetch_and) ATOMIC_FETCH_OPS(atomic_fetch_sub) ATOMIC_FETCH_OPS(atomic_add_return) ATOMIC_FETCH_OPS(atomic_sub_return) #undef ATOMIC_FETCH_OP #undef ATOMIC_FETCH_OPS

所以,这里定义了一组以arch_打头的原子操作函数,且它们都是内联(inline)的。但是最终都是被映射成了__lse_ll_sc_body宏(代码位于arch/arm64/include/asm/lse.h中):

static inline bool system_uses_lse_atomics(void) { return (static_branch_likely(&arm64_const_caps_ready)) && static_branch_likely(&cpu_hwcap_keys[ARM64_HAS_LSE_ATOMICS]); } #define __lse_ll_sc_body(op ...) \ ({ \ system_uses_lse_atomics() ? \ __lse_##op(__VA_ARGS__) : \ __ll_sc_##op(__VA_ARGS__); \ })

system_uses_lse_atomics函数用来判断当前系统是否支持ARMv8.1及之后才新增的所谓LSE(Large System Extention)指令,其中有很多原子操作指令,方便许多,不用再用LL/SC操作了。但是,如果当前Arm平台不支持的话,只能继续通过老的LL/SC操作来实现原子操作了。

所以,最终arch_atomic_add_return_acquire被映射成内联函数__lse_atomic_add_return_acquire或__ll_sc_atomic_add_return_acquire,arch_atomic_fetch_or_release被映射成内联函数__lse_atomic_fetch_or_release或__ll_sc_atomic_fetch_or_release。

我们先来开老的使用LL/SC操作的实现(代码位于arch/arm64/include/asm/atomic_ll_sc.h):

#define ATOMIC_OPS(...) \ ATOMIC_OP(__VA_ARGS__) \ ATOMIC_OP_RETURN( dmb ish l "memory" __VA_ARGS__)\ ATOMIC_OP_RETURN(_relaxed __VA_ARGS__)\ ATOMIC_OP_RETURN(_acquire a "memory" __VA_ARGS__)\ ATOMIC_OP_RETURN(_release l "memory" __VA_ARGS__)\ ATOMIC_FETCH_OP ( dmb ish l "memory" __VA_ARGS__)\ ATOMIC_FETCH_OP (_relaxed __VA_ARGS__)\ ATOMIC_FETCH_OP (_acquire a "memory" __VA_ARGS__)\ ATOMIC_FETCH_OP (_release l "memory" __VA_ARGS__) ATOMIC_OPS(add add I) ATOMIC_OPS(sub sub J) #undef ATOMIC_OPS #define ATOMIC_OPS(...) \ ATOMIC_OP(__VA_ARGS__) \ ATOMIC_FETCH_OP ( dmb ish l "memory" __VA_ARGS__)\ ATOMIC_FETCH_OP (_relaxed __VA_ARGS__)\ ATOMIC_FETCH_OP (_acquire a "memory" __VA_ARGS__)\ ATOMIC_FETCH_OP (_release l "memory" __VA_ARGS__) ATOMIC_OPS(and and K) ATOMIC_OPS(or orr K) ATOMIC_OPS(xor eor K) ATOMIC_OPS(andnot bic ) #undef ATOMIC_OPS #undef ATOMIC_FETCH_OP #undef ATOMIC_OP_RETURN #undef ATOMIC_OP

又是一堆宏大定义,__ll_sc_atomic_add_return_acquire函数是通过宏ATOMIC_OPS(add add I)定义出的一堆关于add原子操作函数其中的一个,也就是接着通过宏ATOMIC_OP_RETURN(_acquire a "memory" __VA_ARGS__)定义出来的函数:

#define ATOMIC_OP_RETURN(name mb acq rel cl op asm_op constraint)\ static inline int \ __ll_sc_atomic_##op##_return##name(int i atomic_t *v) \ { \ unsigned long tmp; \ int result; \ \ asm volatile("// atomic_" #op "_return" #name "\n" \ __LL_SC_FALLBACK( \ " prfm pstl1strm %2\n" \ "1: ld" #acq "xr %w0 %2\n" \ " " #asm_op " %w0 %w0 %w3\n" \ " st" #rel "xr %w1 %w0 %2\n" \ " cbnz %w1 1b\n" \ " " #mb ) \ : "=&r" (result) "=&r" (tmp) " Q" (v->counter) \ : __stringify(constraint) "r" (i) \ : cl); \ \ return result; \ }

这是一段嵌入C语言中的汇编代码,ATOMIC_OP_RETURN宏的参数name对应于_acquire,参数mb对应的是空,参数acq对应的是a,参数rel对应的是空,参数cl对应的是"memory",参数op对应的是add,参数asm_op对应的也是add,参数constrait对应的是I。

这段汇编代码被包含在了__LL_SC_FALLBACK宏中:

#if IS_ENABLED(CONFIG_ARM64_LSE_ATOMICS) && IS_ENABLED(CONFIG_AS_LSE) #define __LL_SC_FALLBACK(asm_ops) \ " b 3f\n" \ " .subsection 1\n" \ "3:\n" \ asm_ops "\n" \ " b 4f\n" \ " .previous\n" \ "4:\n" #else #define __LL_SC_FALLBACK(asm_ops) asm_ops #endif

如果内核配置了CONFIG_ARM64_LSE_ATOMICS和CONFIG_AS_LSE,则将包含的代码放到当前段中的名字为“1”的一个子段中,在函数的入口处跳入进来,当执行完之后在跳回去;如果内核没有配置的话,则是空的,什么都没做。

这里假设__LL_SC_FALLBACK的定义是空的,那么__ll_sc_atomic_add_return_acquire函数扩展后为:

static inline int __ll_sc_atomic_add_return_acquire(int i atomic_t *v) { unsigned long tmp; int result; asm volatile( " // 将v->counter预取到CPU缓存\n" \ " prfm pstl1strm %2\n" \ " // 将v->counter独占的读入result中\n" \ "1: ldaxr %w0 %2\n" \ " // result = result i\n" \ " add %w0 %w0 %w3\n" \ " // 将result的值独占的存入v->counter中\n" \ " // 如果存入时独占标记被清除则将tmp置1\n" \ " stxr %w1 %w0 %2\n" \ " // 如果tmp被置1则从头再次执行一遍\n" \ " cbnz %w1 1b\n" \ ) \ : "=&r" (result) "=&r" (tmp) " Q" (v->counter) \ : __stringify(I) "r" (i) \ : "memory"); \ \ return result; \ }

ldxr和stxr是ARMv8指令集下的实现LL/SC操作的独占访问指令,而ldaxr多了一个a,表示比ldxr指令多了一个Load-Acquire语义,这也就是__ll_sc_atomic_add_return_acquire函数后面_acquire后缀的由来。操作数中加上w(比如%w0,而不是%0)表示是要操作32位的数,使用wn寄存器,否则在64位下,默认使用xn寄存器。所以,这段函数的原理就是独占读取count的值,然后加上i,最后试着独占存入count。如果发现独占存入失败就重新执行一次上述操作,直到成功为止。

我们接着来看__ll_sc_atomic_fetch_or_release函数,其是通过宏ATOMIC_OPS(or orr K)定义出的一堆关于or原子操作函数其中的一个,也就是接着通过宏ATOMIC_FETCH_OP (_release l "memory" __VA_ARGS__)定义出来的函数:

#define ATOMIC_FETCH_OP(name mb acq rel cl op asm_op constraint) \ static inline int \ __ll_sc_atomic_fetch_##op##name(int i atomic_t *v) \ { \ unsigned long tmp; \ int val result; \ \ asm volatile("// atomic_fetch_" #op #name "\n" \ __LL_SC_FALLBACK( \ " prfm pstl1strm %3\n" \ "1: ld" #acq "xr %w0 %3\n" \ " " #asm_op " %w1 %w0 %w4\n" \ " st" #rel "xr %w2 %w1 %3\n" \ " cbnz %w2 1b\n" \ " " #mb ) \ : "=&r" (result) "=&r" (val) "=&r" (tmp) " Q" (v->counter) \ : __stringify(constraint) "r" (i) \ : cl); \ \ return result; \ }

ATOMIC_FETCH_OP宏的参数name对应于_release,参数mb对应的是空,参数acq对应的是空,参数rel对应的是l,参数cl对应的是"memory",参数op对应的是or,参数asm_open对应的是orr,参数constraint对应的是K。同样,将__ll_sc_atomic_fetch_or_release函数扩展后为:

static inline int \ __ll_sc_atomic_fetch_or_release(int i atomic_t *v) \ { \ unsigned long tmp; \ int val result; \ \ asm volatile( \ " // 将v->counter预取到CPU缓存\n" \ " prfm pstl1strm %3\n" \ " // 将v->counter独占的读入result中\n" \ "1: ldxr %w0 %3\n" \ " // val = result | i\n" \ " orr %w1 %w0 %w4\n" \ " // 将val的值独占的存入v->counter中\n" \ " // 如果存入时独占标记被清除则将tmp置1\n" \ " stlxr %w2 %w1 %3\n" \ " // 如果tmp被置1则从头再次执行一遍\n" \ " cbnz %w2 1b\n" \ : "=&r" (result) "=&r" (val) "=&r" (tmp) " Q" (v->counter) \ : __stringify(K) "r" (i) \ : "memory"); \ \ return result; \ }

这里用到了stlxr指令,比stxr指令多了一个l,表示比stxr指令多了一个Store-Release语义,这也就是__ll_sc_atomic_fetch_or_release函数后面_release后缀的由来。由于要返回计算之前的值,因此还需要一个临时变量val用来存放计算之后的值,而result用来存放原来的值。

如果当前平台支持LSE指令,那就会调用函数__lse_atomic_add_return_acquire和__lse_atomic_fetch_or_release。我们先来看__lse_atomic_add_return_acquire函数(代码位于arch/arm64/include/asm/atomic_lse.h中):

#define ATOMIC_OP_ADD_RETURN(name mb cl...) \ static inline int __lse_atomic_add_return##name(int i atomic_t *v) \ { \ u32 tmp; \ \ asm volatile( \ __LSE_PREAMBLE \ " ldadd" #mb " %w[i] %w[tmp] %[v]\n" \ " add %w[i] %w[i] %w[tmp]" \ : [i] " r" (i) [v] " Q" (v->counter) [tmp] "=&r" (tmp) \ : "r" (v) \ : cl); \ \ return i; \ } ATOMIC_OP_ADD_RETURN(_relaxed ) ATOMIC_OP_ADD_RETURN(_acquire a "memory") ATOMIC_OP_ADD_RETURN(_release l "memory") ATOMIC_OP_ADD_RETURN( al "memory") #undef ATOMIC_OP_ADD_RETURN

所以,__lse_atomic_add_return_acquire函数由宏ATOMIC_OP_ADD_RETURN(_acquire a "memory")进行定义。参数name对应于_acquire,参数mb对应于a,参数cl对应于"memory"。宏__LSE_PREAMBLE定义成了:

#define __LSE_PREAMBLE ".arch_extension lse\n"

因此,__lse_atomic_add_return_acquire函数最终被扩展成:

static inline int __lse_atomic_add_return_acquire(int i atomic_t *v) \ { \ u32 tmp; \ \ asm volatile( \ ".arch_extension lse\n" \ "// tmp = v->count; v->count = v->count i;\n" \ "ldadda %w[i] %w[tmp] %[v]\n" \ "// i = i tmp;\n" \ "add %w[i] %w[i] %w[tmp]" \ : [i] " r" (i) [v] " Q" (v->counter) [tmp] "=&r" (tmp) \ : "r" (v) \ : "memory"); \ \ return i; \ }

ldadd本身就是一个原子操作,但是它只会返回加操作之前内存的值,但是函数要返回的是加之后的值,所以后面还要再执行一次加的操作。不过这并不会影响原子性,因为内存中的值真的已经被原子的改变过了。ldadda指令相对于ldadd指令多了一个a,表示比ldadd指令多了一个Load-Acquire语义,这也就是__lse_atomic_add_return_acquire函数后面_acquire后缀的由来。

我们接着看__lse_atomic_fetch_or_release函数

#define ATOMIC_FETCH_OP(name mb op asm_op cl...) \ static inline int __lse_atomic_fetch_##op##name(int i atomic_t *v) \ { \ asm volatile( \ __LSE_PREAMBLE \ " " #asm_op #mb " %w[i] %w[i] %[v]" \ : [i] " r" (i) [v] " Q" (v->counter) \ : "r" (v) \ : cl); \ \ return i; \ } #define ATOMIC_FETCH_OPS(op asm_op) \ ATOMIC_FETCH_OP(_relaxed op asm_op) \ ATOMIC_FETCH_OP(_acquire a op asm_op "memory") \ ATOMIC_FETCH_OP(_release l op asm_op "memory") \ ATOMIC_FETCH_OP( al op asm_op "memory") ATOMIC_FETCH_OPS(andnot ldclr) ATOMIC_FETCH_OPS(or ldset) ATOMIC_FETCH_OPS(xor ldeor) ATOMIC_FETCH_OPS(add ldadd) #undef ATOMIC_FETCH_OP #undef ATOMIC_FETCH_OPS

所以,__lse_atomic_fetch_or_release函数由ATOMIC_FETCH_OPS(or ldset)宏和ATOMIC_FETCH_OP(_release l op asm_op "memory")宏共同定义。ATOMIC_FETCH_OP宏的name参数对应于_release,mb参数对应于l,op参数对应于or,asm_op参数对应于ldset,cl参数对应于"memory"。扩展过后,__lse_atomic_fetch_or_release被定义为:

static inline int __lse_atomic_fetch_or_release(int i atomic_t *v) \ { \ asm volatile( \ ".arch_extension lse\n" \ "ldsetl %w[i] %w[i] %[v]" \ : [i] " r" (i) [v] " Q" (v->counter) \ : "r" (v) \ : cl); \ \ return i; \ }

ldset指令本身就可以保证原子的或操作,而且会返回没修改之前的值,因此一条指令就搞定了。ldsetl指令相对于ldset指令多了一个l,表示比ldset指令多了一个Store-Release语义,这也就是__lse_atomic_fetch_or_release函数后面_release后缀的由来。

最后,我们再回过头来看看arch_cmpxchg函数的实现:

#define __cmpxchg_wrapper(sfx ptr o n) \ ({ \ __typeof__(*(ptr)) __ret; \ __ret = (__typeof__(*(ptr))) \ __cmpxchg##sfx((ptr) (unsigned long)(o) \ (unsigned long)(n) sizeof(*(ptr))); \ __ret; \ }) ...... #define arch_cmpxchg(...) __cmpxchg_wrapper( _mb __VA_ARGS__)

所以,arch_cmpxchg最终被宏定义成了__cmpxchg_wrapper,而它又调用了__cmpxchg_mb函数:

#define __CMPXCHG_GEN(sfx) \ static __always_inline unsigned long __cmpxchg##sfx(volatile void *ptr \ unsigned long old \ unsigned long new \ int size) \ { \ switch (size) { \ case 1: \ return __cmpxchg_case##sfx##_8(ptr old new); \ case 2: \ return __cmpxchg_case##sfx##_16(ptr old new); \ case 4: \ return __cmpxchg_case##sfx##_32(ptr old new); \ case 8: \ return __cmpxchg_case##sfx##_64(ptr old new); \ default: \ BUILD_BUG(); \ } \ \ unreachable(); \ } __CMPXCHG_GEN() __CMPXCHG_GEN(_acq) __CMPXCHG_GEN(_rel) __CMPXCHG_GEN(_mb) #undef __CMPXCHG_GEN

__cmpxchg_mb函数是靠宏__CMPXCHG_GEN(_mb)定义的,按照要交换数据的大小,分别调用了__cmpxchg_case_mb_8、__cmpxchg_case_mb_16、__cmpxchg_case_mb_32或__cmpxchg_case_mb_64:

#define __CMPXCHG_CASE(name sz) \ static inline u##sz __cmpxchg_case_##name##sz(volatile void *ptr \ u##sz old \ u##sz new) \ { \ return __lse_ll_sc_body(_cmpxchg_case_##name##sz \ ptr old new); \ } __CMPXCHG_CASE( 8) __CMPXCHG_CASE( 16) __CMPXCHG_CASE( 32) __CMPXCHG_CASE( 64) __CMPXCHG_CASE(acq_ 8) __CMPXCHG_CASE(acq_ 16) __CMPXCHG_CASE(acq_ 32) __CMPXCHG_CASE(acq_ 64) __CMPXCHG_CASE(rel_ 8) __CMPXCHG_CASE(rel_ 16) __CMPXCHG_CASE(rel_ 32) __CMPXCHG_CASE(rel_ 64) __CMPXCHG_CASE(mb_ 8) __CMPXCHG_CASE(mb_ 16) __CMPXCHG_CASE(mb_ 32) __CMPXCHG_CASE(mb_ 64)

最后又回到了宏__lse_ll_sc_body,我们这里只分析一下大小为16的情况。所以,如果当前平台支持LSE,那么将调用__lse_cmpxchg_case_mb_16函数,否则将调用__ll_sc_cmpxchg_case_mb_16函数。

我们先来看LL/SC方式的实现__ll_sc_cmpxchg_case_mb_16函数:

#define __CMPXCHG_CASE(w sfx name sz mb acq rel cl constraint) \ static inline u##sz \ __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr \ unsigned long old \ u##sz new) \ { \ unsigned long tmp; \ u##sz oldval; \ \ if (sz < 32) \ old = (u##sz)old; \ \ asm volatile( \ __LL_SC_FALLBACK( \ " prfm pstl1strm %[v]\n" \ "1: ld" #acq "xr" #sfx "\t%" #w "[oldval] %[v]\n" \ " eor %" #w "[tmp] %" #w "[oldval] %" #w "[old]\n" \ " cbnz %" #w "[tmp] 2f\n" \ " st" #rel "xr" #sfx "\t%w[tmp] %" #w "[new] %[v]\n" \ " cbnz %w[tmp] 1b\n" \ " " #mb "\n" \ "2:") \ : [tmp] "=&r" (tmp) [oldval] "=&r" (oldval) \ [v] " Q" (*(u##sz *)ptr) \ : [old] __stringify(constraint) "r" (old) [new] "r" (new) \ : cl); \ \ return oldval; \ } __CMPXCHG_CASE(w b 8 K) __CMPXCHG_CASE(w h 16 K) __CMPXCHG_CASE(w 32 K) __CMPXCHG_CASE( 64 L) __CMPXCHG_CASE(w b acq_ 8 a "memory" K) __CMPXCHG_CASE(w h acq_ 16 a "memory" K) __CMPXCHG_CASE(w acq_ 32 a "memory" K) __CMPXCHG_CASE( acq_ 64 a "memory" L) __CMPXCHG_CASE(w b rel_ 8 l "memory" K) __CMPXCHG_CASE(w h rel_ 16 l "memory" K) __CMPXCHG_CASE(w rel_ 32 l "memory" K) __CMPXCHG_CASE( rel_ 64 l "memory" L) __CMPXCHG_CASE(w b mb_ 8 dmb ish l "memory" K) __CMPXCHG_CASE(w h mb_ 16 dmb ish l "memory" K) __CMPXCHG_CASE(w mb_ 32 dmb ish l "memory" K) __CMPXCHG_CASE( mb_ 64 dmb ish l "memory" L) #undef __CMPXCHG_CASE

定义__ll_sc_cmpxchg_case_mb_16函数,__CMPXCHG_CASE宏的w参数对应于w,sfx参数对应于空,name参数对应于mb_,sz参数对应于16,mb参数对应于dmb ish,acq参数对应于空,rel参数对应于l,cl参数对应于"memory",constraint参数对应于K。所以,__ll_sc_cmpxchg_case_mb_16函数扩展开来的实现是:

static inline u16 \ __ll_sc__cmpxchg_case_mb_16(volatile void *ptr \ unsigned long old \ u16 new) \ { \ unsigned long tmp; \ u16 oldval; \ \ old = (u16)old; \ \ asm volatile( \ " // 将*ptr的值预取到CPU缓存\n" \ " prfm pstl1strm %[v]\n" \ " // oldval = *ptr;\n" \ "1: ldxr %w[oldval] %[v]\n" \ " // tmp = oldval ^ old;\n" \ " eor %w[tmp] %w[oldval] %w[old]\n" \ " cbnz %w[tmp] 2f\n" \ " // *ptr = new;\n" \ " stlxr %w[tmp] %w[new] %[v]\n" \ " cbnz %w[tmp] 1b\n" \ " dmb ish\n" \ "2:" \ : [tmp] "=&r" (tmp) [oldval] "=&r" (oldval) \ [v] " Q" (*(u16 *)ptr) \ : [old] __stringify(K) "r" (old) [new] "r" (new) \ : "memory"); \ \ return oldval; \ }

定义__ll_sc_cmpxchg_case_mb_16函数,__CMPXCHG_CASE宏的w参数对应于w,sfx参数对应于空,name参数对应于mb_,sz参数对应于16,mb参数对应于dmb ish,acq参数对应于空,rel参数对应于l,cl参数对应于"memory",constraint参数对应于K。所以,__ll_sc_cmpxchg_case_mb_16函数扩展开来的实现是:

static inline u16 \ __ll_sc__cmpxchg_case_mb_16(volatile void *ptr \ unsigned long old \ u16 new) \ { \ unsigned long tmp; \ u16 oldval; \ \ old = (u16)old; \ \ asm volatile( \ " // 将*ptr的值预取到CPU缓存\n" \ " prfm pstl1strm %[v]\n" \ " // oldval = *ptr;\n" \ "1: ldxr %w[oldval] %[v]\n" \ " // tmp = oldval ^ old;\n" \ " eor %w[tmp] %w[oldval] %w[old]\n" \ " cbnz %w[tmp] 2f\n" \ " // *ptr = new;\n" \ " stlxr %w[tmp] %w[new] %[v]\n" \ " cbnz %w[tmp] 1b\n" \ " dmb ish\n" \ "2:" \ : [tmp] "=&r" (tmp) [oldval] "=&r" (oldval) \ [v] " Q" (*(u16 *)ptr) \ : [old] __stringify(K) "r" (old) [new] "r" (new) \ : "memory"); \ \ return oldval; \ }

使用了eor异或指令判断新老值是否相等,如果不等则直接退出,如果相等则尝试独占写入新的数据。这里使用了stlxr指令而不是普通的stxr指令,添加了Store-Release语义,这是为了保证写入内存的数据能立即被系统中其它模块感知到。最后添加了一个作用于内部共享域的数据内存屏障,这也印证了前面提到的命名规则,即RMW的原子操作,如果有返回值,并且没有_relaxed、_acquire和_release后缀,是有内存屏障保护的,可以保证不被重排序。

再来看一下__lse_cmpxchg_case_mb_16函数的实现:

#define __CMPXCHG_CASE(w sfx name sz mb cl...) \ static __always_inline u##sz \ __lse__cmpxchg_case_##name##sz(volatile void *ptr \ u##sz old \ u##sz new) \ { \ register unsigned long x0 asm ("x0") = (unsigned long)ptr; \ register u##sz x1 asm ("x1") = old; \ register u##sz x2 asm ("x2") = new; \ unsigned long tmp; \ \ asm volatile( \ __LSE_PREAMBLE \ " mov %" #w "[tmp] %" #w "[old]\n" \ " cas" #mb #sfx "\t%" #w "[tmp] %" #w "[new] %[v]\n" \ " mov %" #w "[ret] %" #w "[tmp]" \ : [ret] " r" (x0) [v] " Q" (*(unsigned long *)ptr) \ [tmp] "=&r" (tmp) \ : [old] "r" (x1) [new] "r" (x2) \ : cl); \ \ return x0; \ } __CMPXCHG_CASE(w b 8 ) __CMPXCHG_CASE(w h 16 ) __CMPXCHG_CASE(w 32 ) __CMPXCHG_CASE(x 64 ) __CMPXCHG_CASE(w b acq_ 8 a "memory") __CMPXCHG_CASE(w h acq_ 16 a "memory") __CMPXCHG_CASE(w acq_ 32 a "memory") __CMPXCHG_CASE(x acq_ 64 a "memory") __CMPXCHG_CASE(w b rel_ 8 l "memory") __CMPXCHG_CASE(w h rel_ 16 l "memory") __CMPXCHG_CASE(w rel_ 32 l "memory") __CMPXCHG_CASE(x rel_ 64 l "memory") __CMPXCHG_CASE(w b mb_ 8 al "memory") __CMPXCHG_CASE(w h mb_ 16 al "memory") __CMPXCHG_CASE(w mb_ 32 al "memory") __CMPXCHG_CASE(x mb_ 64 al "memory") #undef __CMPXCHG_CASE

定义__lse_cmpxchg_case_mb_16函数,__CMPXCHG_CASE宏的w参数对应于w,sfx参数对应于空,name参数对应于mb_,sz参数对应于16,mb参数对应于al,cl参数对应于"memory"。所以,__ll_sc_cmpxchg_case_mb_16函数扩展开来的实现是:

static __always_inline u16 \ __lse__cmpxchg_case_mb_16(volatile void *ptr \ u16 old \ u16 new) \ { \ register unsigned long x0 asm ("x0") = (unsigned long)ptr; \ register u16 x1 asm ("x1") = old; \ register u16 x2 asm ("x2") = new; \ unsigned long tmp; \ \ asm volatile( \ " .arch_extension lse\n" \ " // tmp = old;\n" \ " mov %w[tmp] %w[old]\n" \ " casal %w[tmp] %w[new] %[v]\n" \ " // ret = tmp;\n" \ " mov %[ret] %w[tmp]" \ : [ret] " r" (x0) [v] " Q" (*(unsigned long *)ptr) \ [tmp] "=&r" (tmp) \ : [old] "r" (x1) [new] "r" (x2) \ : "memory"); \ \ return x0; \ }

cas指令是LSE中原生提供的一个比较交换指令,会比较第一个寄存器中的值是否和内存中的值相同,如果相同的话就将第二个寄存器的值写入内存,并且会在第一个寄存器中放入修改内存之前的值。casal指令相对于cas指令多了al,表示比cas指令多了一个Load-Acquire和Store-Release语义,其实就是一个完整的内存屏障,保证不被重排序。

猜您喜欢: