一、原子操作的命名规则

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

1)基本型

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

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

RMW操作就很多了,包括下面几种:

  1. 算术操作:包括加、减、递增和递减,命名形式是atomic_{add,sub,inc,dec}();
  2. Bit位操作:包括与、或、异或和与非,命名形式是atomic_{and,or,xor,andnot}();
  3. 交换操作:包括交换(atomic_xchg())、比较交换(atomic_cmpxchg())和添加了返回成功与否的比较交换(atomic_try_cmpxchg())。

除了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等。

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;                            \
}

使用了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语义,其实就是一个完整的内存屏障,保证不被重排序。

Linux内核同步原语之原子操作(Atomic)相关推荐

  1. Linux内核同步机制之(四):spin lock【转】

    转自:http://www.wowotech.net/kernel_synchronization/spinlock.html 一.前言 在linux kernel的实现中,经常会遇到这样的场景:共享 ...

  2. linux内核同步问题

    linux内核同步问题 Linux内核设计与实现 十.内核同步方法 [手把手教Linux驱动5-自旋锁.信号量.互斥体概述]() 基础概念: 并发:多个执行单元同时进行或多个执行单元微观串行执行,宏观 ...

  3. linux 内核互斥体,Linux 内核同步(六):互斥体(mutex)

    互斥体 互斥体是一种睡眠锁,他是一种简单的睡眠锁,其行为和 count 为 1 的信号量类似.(关于信号量参考:Linux 内核同步(四):信号量 semaphore). 互斥体简洁高效,但是相比信号 ...

  4. Linux内核同步机制之信号量与锁

    Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制--信号量.锁. 一.信号量 首先还是看看内核中是怎么 ...

  5. Linux内核同步 - Read/Write spin lock

    一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...

  6. linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障

    内核同步 内核中可能造成并发的原因: 中断–中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码. 软中断和tasklet–内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在 ...

  7. linux kernle 同步原语

    转载:同步原语 如何避免由于对共享数据的不安全访问导致的数据崩溃? 内核使用的各种同步技术: 技术 说明 适用范围 每CPU变量 在CPU之间复制数据结构 所有CPU 原子操作 对一个计数器原子地&q ...

  8. Linux内核同步:RCU

    linux内核 RCU机制详解 简介 RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的 ...

  9. Linux 内核同步(七):RCU机制

    简介 RCU 的全称是(Read-Copy-Update),意在读写-复制-更新,在 Linux 提供的所有内核互斥的设施当中属于一种免锁机制.在之前讨论过的读写自旋锁(rwlock).顺序锁(seq ...

  10. linux 内核 同步机制

    原子操作   原子操作是由编译器来保证的,保证一个线程对数据的操作不会被其他线程打断.    自旋锁 原子操作只能用于临界区只有一个变量的情况,实际应用中,临界区的情况要复杂的多.对于复杂的临界区,L ...

最新文章

  1. nvm npm不是内部命令_npm作弊表-最常见的命令和nvm
  2. 程序员之间的门户之见有多深?
  3. sqlconfigdatasource mysql_MFC odbc 连接MySQL 的 (SQLConfigDataSource动态DSN)
  4. centos proftp_在CentOS上禁用ProFTP
  5. Python中的符号
  6. golang 包含文本_Golang教程之Web篇(七)
  7. 解决Another app is currently holding the yum lock; waiting for it to exit...问题
  8. 设计模式之Builder模式 (C++实现)
  9. 快速排序查询第k大元素C语言,快速排序和查找第K大元素
  10. s8050三极管经典电路_电子电路系统的基本概念有哪些?
  11. 深度学习图像分类(十四): EifficientNet系列(V1, V2)
  12. 路畅畅云固件升级教程_【图】【折腾导航】路畅导航固件升级、刷机、实现一机多图教程!...
  13. IDEA 插件开发 鼠标事件
  14. 计算机老师教师节祝福语,教师节祝福语大全简短
  15. linux安装frps服务,ubuntu搭建frps服务
  16. android fastboot原理,Android 手机进入不了fastboot模式的解决方案
  17. 第三届艺星超级品牌日钜献·世界名曲音乐会,全网直播!
  18. 如何用python画出一般函数图_python如何画函数图像
  19. VAE(变分自编码器)原理简介
  20. 前端架构设计第六课工程化构建、编译、运行

热门文章

  1. vlookup匹配的文字显示0_Excel | VLOOKUP函数使用详解
  2. Python - poetry(4)管理环境
  3. httpwatch9.1 安装包
  4. java promise实现,[Java教程]promise的理解和应用
  5. 国产手机后台弹出界面检测方法
  6. Iphone 和 Ipad 开发 区别
  7. Python爬虫——使用三种方法来爬取酷狗音乐的TOP榜
  8. Kindeditor在线 HTML 编辑器使用
  9. 2019校招阿里数据研发/数据挖掘三面总结
  10. kettle spoon 连接mysql数据库