飞腾CPU体系结构之内存交换原子操作
1. 内存交换原子操作
定义:修改内存单元的数据,同时返回该内存单元原来的数据。
函数声明(以内存单元长度64位为例)
static inline u64 __xchg_case_64 (u64 x, volatile void *ptr)
static inline u64 __xchg_case_acq_64 (u64 x, volatile void *ptr)
static inline u64 __xchg_case_rel_64 (u64 x, volatile void *ptr)
static inline u64 __xchg_case_mb_64 (u64 x, volatile void *ptr)
上述四个函数都是内存交换原子操作,四个函数的主要区别在于:该内存交换原子操作与程序访存顺序保证。
- __xchg_case_64
不对整个程序访存顺序性做保证。也就是说,该内存交换原子操作之前,之中,或之后可能由于CPU乱序执行而发生其他访存操作。- __xchg_case_acq_64
该内存交换原子操作之后的访存指令不会在该内存原子操作之前得到执行。- __xchg_case_rel_64
该内存交换原子操作之前的访存指令必须在该内存原子操作之前得到执行。- __xchg_case_mb_64
该内存交换原子操作之前的访存指令在该内存原子操作之前得到执行;并且该内存交换原子操作之后的访存指令只能在该内存原子操作之后得到执行。
注意:飞腾当前不支持LSE指令(swap指令),因此内存交换原子操作只能用“独占类型”访存来实现。
2. __xchg_case_64汇编解析
static inline u64sz __xchg_case_64(u64 x, volatile void *ptr)
{
u64 ret;
unsigned long tmp;
asm volatile(
" prfm pstl1strm, %2\n"
"1: ldxr %0, %2\n"
" stxr %w1, %3, %2\n"
" cbnz %w1, 1b\n"
)
: "=&r" (ret), "=&r" (tmp), "+Q" (*(u64 *)ptr)
: "r" (x)
: );
return ret;
}
函数参数描述:
指针ptr: 描述了内存单元地址
输入x: 描述了写入内存单元的新值
返回ret: 返回内存单元原来的老值
汇编参数描述:
输出参数:
- “=&r”(ret) 表示变量ret采用普通寄存器%0(GCC编译器分配,但不能与输入参数采用相同的寄存器)
- “=&r”(tmp)表示变量tmp采用普通寄存器%1(GCC编译器分配,但不能与输入参数采用相同的寄存器)
- “+Q”(*(u64 *)ptr)表示ptr内存单元采用普通寄存器%2寻址,ptr内存单元内容同时也是输入参数。
- “r”(x)表示x采用普通寄存器%3。
上述代码整个理解为:从内存单元读取老值的同时,将该内存单元打上独占标记;检查独占标记完整的同时,将新值写入该内存单元和破环独占标记;如果检查发现独占标记不完整,就重新从内存单元读取老值,这样一直循环,直到一个读写过程独占标记是完整的。
3. __xchg_case_mb_64汇编解析
static inline u64sz __xchg_case_64(u64 x, volatile void *ptr)
{
u64 ret;
unsigned long tmp;
asm volatile(
" prfm pstl1strm, %2\n"
"1: ldaxr %0, %2\n"
" stlxr %w1, %3, %2\n"
" cbnz %w1, 1b\n"
" dmb ish "
)
: "=&r" (ret), "=&r" (tmp), "+Q" (*(u64 *)ptr)
: "r"(x)
: "memory");
return ret;
}
差异:
- 将ldxr替换成ldaxr
- 将stxr替换成stxlr
- 成功后增加dmb ish指令
- "memory"告诉GCC内存单元被修改
4. 忙转问题
如果有大量的线程对某个内存变量进行内存交换原子操作时,就会出现大量线程陷入该循环当中,从而导致看上去CPU利用率很高,其实都在忙转,即无效地运行。
在非实时性前提下,可以使用sev/sevl+wfe指令组合方式,使CPU进入低功耗模式。
static inline u64sz __xchg_case_64(u64 x, volatile void *ptr)
{
u64 ret;
unsigned long tmp;
asm volatile(
" prfm pstl1strm, %2\n"
" sevl\n"
"1: wfe\n"
" ldxr %0, %2\n"
" stxr %w1, %3, %2\n"
" cbnz %w1, 1b\n"
" sev\n"
)
: "=&r" (ret), "=&r" (tmp), "+Q" (*(u64 *)ptr)
: "r" (x)
: );
return ret;
}