一、原子操作的介绍
在计算机领域里,如果要在多线程的情况下要保持数据的同步,需要引入称作Load-Link(LL)和Store-Conditional(SC)的操作,通常简称为LL/SC。
LL操作返回一个内存地址上当前存储的值,并且处理器会监控这个内存地址, 看其他处理器是否修改该内存地址,后面的SC操作,会向这个内存地址写入一个新值。但是,只有在这个内存地址上存储的值,从上个LL操作开始直到现在都没有发生改变的情况下,写入操作才能成功,否则就会失败。这个操作非常重要,是很多平台实现基本原子操作的基础。 对于ARM平台来说,也在硬件层面上提供了对LL/SC的支持,LL操作用的是LDREX指令,SC操作用的是STREX指令。
一个原子的 LL/SC 操作就是通过 LL 读取值, 进行一些计算, 最后通过 SC 来写回。 如果 SC 失败,那么重新开始整个操作。 LL/SC 常常用于实现无锁算法与“读-修改-回写” 原子操作。很多 RISC体系结构实现了这种 LL/SC 机制,对于ARM平台来说,也在硬件层面上提供了对LL/SC的支持,LL操作用的是LDREX指令,SC操作用的是STREX指令。
二、LDXR 与 STXR 指令
LDXR 指令是内存独占加载指令,它从内存中以独占方式加载内存地址的值到通用寄存器里。
以下是 LDXR 指令的原型,它把 Xn 或者 SP 地址的值原子地加载到 Xt 寄存器里。
ldxr <xt>, [xn | sp]
STXR 指令是内存独占存储指令,它把Xt寄存器的值原子地存储到到Xn或者SP地址里,执行的结果反映到Ws寄存器中。若Ws寄存器为0, 说明ldxr和stxr指令执行完成;如果结果不为0,说明执行错误,需要重新跳到ldxr处重新执行原子加载和原子存储。
stxr <ws>, <xt>, [xn, sp]
LDXP和STXP是多字节独占内存访问指令,一条指令可以独占地加载和存储16字节,也就是说这两条指令能够实现两个数据的同时操作。
ldxp <xt1>, <xt2>, [xn|sp]
stxp <ws>, <xt1>, <xt2>, [<xn|sp>]
应用场景: ldxr和stxr指令可以和加载-获取,存储-释放内存屏障原语结合使用。构成一个类似临界区的内存屏障。在一些自旋锁的实现场景下非常有用。
使用
void atomic_add(int i, atomic_t *v)
{
unsigned long tmp;
int result;
asm volatile("// atomic_add\n"
"1: ldxr%w0, [%2]\n"
" add%w0, %w0, %w3\n"
" stxr%w1, %w0, [%2]\n"
" cbnz%w1, 1b"
: "=&r" (result), "=&r" (tmp)
: "r" (&v->counter), "Ir" (i)
: "cc");
}
// 其中 atomic_t 变量的定义如下。
typedef struct {
int counter;
} atomic_t;
参考:
独占访问指令与内存管理:局部与全局监视器-CSDN博客
https://zhuanlan.zhihu.com/p/692422838