Skip to content

原子操作

原子操作底层与架构紧密相关,ARMv8.0 的实现如下:

linux/arch/arm64/include/asm/atomic_ll_sc.h
#define ATOMIC_OP(op, asm_op, constraint)               \
static inline void                          \
__ll_sc_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"                  \
    "   " #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.1增加的LSE(Large System Extension)feature使用的是ldadd指令。

独占内存访问指令

LDXRSTXR指令,用于实现对变量的原子操作。

LDXR是独占内存加载指令,它以独占的方式加载内存地址的值到通用寄存器。

STXR是独占内存存储指令,它以独占的方式将通用寄存器中的值存储到内存地址。执行的结果放在 Ws 寄存器中,如果该寄存器为0则执行成功。

LDXPSTXP是多字节的独占内存访问指令。

独占监视器

独占内存访问指令LDXRSTXR通过独占监视器来监控对内存的访问。

独占监视器会把对应内存地址标记为独占访问模式,

独占访问例子

1:
    ldxr x2, [x1]
    orr x2, x2, x0
    stxr w3, x2, [x1]
    cbnz w3, 1b

最后一行通过判断 W3 寄存器的值来判断是否执行成功,如果不为0则跳转到标签1处重新执行。

注意,LDXRSTXR指令必须配对使用,位于这两条指令之间的代码是原子的。

独占监视器一共有两种状态——开放访问状态独占访问状态

当 CPU 通过LDXR指令从内存加载数据时,CPU 会把这个内存地址标记为独占访问,然后 CPU 内部的独占监视器的状态就变为独占访问状态。当执行到STXR指令时,需要根据独占监视器的状态来做决定:

  • 如果是独占访问状态并且STXR指令要存储的地址正好是刚才标记过的地址,那么STXR指令执行成功,返回0,并且独占监视器的状态变为开放访问状态。
  • 如果是开放访问状态,那么STXR指令执行失败,返回1,并且独占监视器的状态仍然保持开放访问状态。

ARMv8 体系结构根据缓存一致性的层次关系分为了多个监视器:

  • 本地独占监视器:监视本地 CPU
  • 内部缓存一致性全局独占监视器:监视内部缓存一致性
  • 外部全局独占监视器:监视外部缓存一致性

原子内存访问指令

ARMv8 体系结构中新增了原子内存访问指令,该指令需要 AMBA 5总线中的 CHI(Coherent Hub Interface)的支持。AMBA 5总线引入了原子事务(atomic transaction)的概念,允许将原子操作发送到数据,并且允许原子操作在靠近数据的地方执行,而不需要加载到高速缓存中处理。原子事务非常适合要操作的数据离处理器核心比较远的地方,比如数据在内存中。

原子内存访问指令与独占内存访问指令最大区别在于效率。设想一个 SMP 系统,假如共享资源存储在内存中,使用独占内存访问指令会导致所有 CPU 核心都将锁加载到 L1 高速缓存中,然后不停地尝试获取锁和检查独占监视器的状态,在锁竞争激烈的时候会造成高速缓存颠簸现象,并且整个过程还需要 MESI(缓存一致性)协议来处理 L1 高速缓存一致性。这个场景在 NUMA 架构下更加明显,远端节点的 CPU 需要不断地跨节点访问数据。另外一个问题是不公平,当锁持有者释放锁时,所有的 CPU 都需要争抢这把锁,有可能最先申请锁的 CPU 反而没有抢到锁。