快速链接:
.
👉👉👉 个人博客笔记导读目录(全部) 👈👈👈
说明:
在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 5.14
通常我们代码中的a = a + 1
这样的一行语句,翻译成汇编后蕴含着3条指令:
ldr x0, &a
add x0,x0,#1
str x0,&a
即
(1)从内存中读取a变量到X0寄存器
(2)X0寄存器加1
(3)将X0写入到内存a中
既然是3条指令,那么就有可能并发,也就意味着返回的结果可能不说预期的。
然后在linux kernel的操作系统中,提供访问原子变量的函数,用来解决上述问题。其中部分原子操作的API如下:
- atomic_read
- atomic_add_return(i,v)
- atomic_add(i,v)
- atomic_inc(v)
- atomic_add_unless(v,a,u)
- atomic_inc_not_zero(v)
- atomic_sub_return(i,v)
- atomic_sub_and_test(i,v)
- atomic_sub(i,v)
- atomic_dec(v)
- atomic_cmpxchg(v,old,new)
那么操作系统(仅仅是软件而已)是如何保证原子操作的呢?(还是得靠硬件),硬件原理是什么呢?
以上的那些API函数,在底层调用的其实都是如下__lse_atomic_add_return##name
宏的封装,这段代码中最核心的也就是ldadd
指令了,这是armv8.1增加的LSE(Large System Extension)feature。
(linux/arch/arm64/include/asm/atomic_lse.h)
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; \
}
那么系统如果没有LSE扩展呢,即armv8.0,其实现的原型如下所示,这段代码中最核心的也就是ldxr
、stxr
指令了
(linux/arch/arm64/include/asm/atomic_ll_sc.h)
static inline void __ll_sc_atomic_##op(int i, atomic_t *v)\
{ \
unsigned long tmp; \
int result; \
\
asm volatile("// atomic_" #op "\n" \
__LL_SC_FALLBACK( \
" prfm pstl1strm, %2\n" \
"1: ldxr %w0, %2\n" \
" " #asm_op " %w0, %w0, %w3\n" \
" stxr %w1, %w0, %2\n" \
" cbnz %w1, 1b\n") \
: "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \
: __stringify(constraint) "r" (i)); \
}
那么在armv8.0之前呢,如armv7是怎样实现的? 如下所示, 这段代码中最核心的也就是ldrex
、strex
指令了
(linux/arch/arm/include/asm/atomic.h)
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"); \
}
总结:
在很早期,使用arm的exclusive机制来实现的原子操作,exclusive相关的指令也就是ldrex
、strex
了,但在armv8后,exclusive机制的指令发生了变化变成了ldxr
、stxr
。但是又由于在一个大系统中,处理器是非常多的,竞争也激烈,使用独占的存储和加载指令可能要多次尝试才能成功,性能也就变得很差,在armv8.1为了解决该问题,增加了ldadd
等相关的原子操作指令