原子变量
原子变量用来实现对整数的互斥访问,通常用来实现计数器。
内核定义了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); \
}