ARM Atomic Operation
时间轴
2025-10-30
init
参考文档:
ARMv8.6芯片手册与独占内存访问相关的内容
- 第B2.9章 Synchronization and semaphores
- 第D1.16章节 Mechanisms for entering a low-power state
- 第C3.2.13章 Compare and Swap
- 第C3.2.13章 Atomic memory operations
- 第C3.2.14章 Swap
为什么需要原子操作
thread_A_func和thread_B_func都尝试进行i++操作

Linux内核中的基本原子操作函数
- Linux内核提供了atomic_t类型的原子变量,它的实现依赖于不同的架构
- atomic_t类型的原子操作函数可以保证了一个操作的原子性和完整性
- “读-修改-回写”机制
- 在读取原子变量的值到通用寄存器
- 在通用寄存器里修改原子变量的值
- 把新值写回内存中



如果CPU仅仅是从内存中读取(load)一个变量的值,或者仅仅是往内存中写(store)一个变量的值,都是不可打断的
下面的这些操作函数API用到了”读-修改-回写”机制



ARMv8对原子操作的支持
- ARMv8提供两种方式的原子操作
- 传统的Load-exclusive和store-exclusive方式
- 在ARMv8上都支持
- LL/SC(Load-link/store-conditional)
- LSE(Large System Extensions)支持原子操作指令
- 在ARMv8.1上开始支持,ARMv8.1-LSE
- 新增Compare and Swap instructions
- 新增Atomic memory operation instructions
- 新增Swap instruction
- 传统的Load-exclusive和store-exclusive方式

Load-exclusive和store-exclusive指令
- ldxr指令:内存独占加载指令。从内存中以独占的方式加载内存地址的值到通用寄存器里
1 | ldxr <xt> , [xn|sp] |
- stxr指令:内存独占存储指令。以独占的方式把新的数据存储到内存中。
1 | stxr <ws> , <xt> , [xn|sp] |
- Load/Store Exclusive Pair指令
1 | ldxp <Xt1>, <Xt2>, [Xn|SP] |
- 带acquire和release原语的Load/Store Exclusive指令
例子
- 通过独占监视器(exclusive monitor)来监视这个内存的访问,独占监视器会把这个内存地址标记为独占访问模式,保证以独占的方式来访问这个内存地址,不受其他因素的影响
独占监视器(Exclusive Monitor)
- 独占监视器一共有两个状态:
- 开放访问状态(Open Access state)
- 独占访问状态(Exclusive Access state)
- ldxr指令从内存加载数据时,CPU会把这个内存地址标记为独占访问状态
- 当CPU执行stxr指令时,需要根据独占监视器的状态来做决定
- 如果独占监视器的状态为独占访问状态,那么stxr指令存储成功,stxr指令返回0,独占监视器的状态变成了开放访问状态
- 如果独占监视器的状态为开放访问状态,那么stxr存储失败,stxr返回1

注意事项
- 独占监视器本身不是用来阻止CPU核心来访问被标记的内存,不会lock总线
- 独占监视器仅仅是起到监视的作用,监视状态的变化
- 不能把独占监视器看成是一个硬件的锁

独占监视器的组成架构
- 通常一个系统由多级独占监视器组成(由芯片设计时定义)
- 本地独占监视器(Local monitor),适用于非共享(Non-shareable)的内存
- 缓存一致性的全局独占监视器(Internal coherent global monitor),适用于普通类型的内存
- 外部的全局独占监视器(External global monitor),适用于设备类型的内存
- 有的Soc不支持外部的全局独占监视器。例如树莓派4b上使用的BMC2711
- 在MMU没有是能的情况下,我们访问物理内存变成了访问设备类型的内存,此时使用ldxr和stxr指令会产生不可预测的错误

- ldxr指令的使用会有很多限制,要求memory是normal memory且是shareable
- 如果访问device memory,例如MMU没有打开的情况,那就需要CPU IP核心支持以独占方式访问device memory,这个需要查询具体CPU ID手册的描述
独占监视器的粒度(Granularity of Exclusive Monitor)
- CTR_EL1寄存器中的ERG(Exclusives Reservation Granule)定义了独占监视器的最小单位
- ERG可以定义的范围是4 words~512words,不过通常是一个cache line的大小
- 举例:
- 假设ERG是2^4,即16字节。当使用ldrxb指令对0x341B4地址进行独占地读操作,那么从0x341b0~0x341bf都会标记为exclusive access
案例1:atomic_add()函数的实现

案例2:简单锁(spinlock)的实现

cbnz w2, retry
多核情况下的ldxr和stxr分析
CPU0和CPU1同时执行get_lock()操作

T0时刻 初始化状态

T1和T2时刻 CPU0执行ldxr指令

T3时刻 CPU1执行ldxr指令

T4时刻 CPU0通过stxr指令来获取了锁

T5时刻 CPU1通过stxr指令尝试获取锁

WFE指令在锁实现中的应用
- 如果CPU0获取了锁,CPUn在等待锁的时候,让CPU进入低功耗模式,那么能节省功耗和提升性能
- 获取锁的示例代码

- 释放锁的示例代码

使用 LDXR/STXR 实现原子自增(无序)
1 | loop: |
这种写法是 无序的,适合数据原子更新但不涉及线程同步。
例2:使用 LDAXR/STLXR 实现锁(有序)
1 | // try_lock |
这种写法是有序的,保证:
- 获取锁前的操作不会越过锁;
- 释放锁后的操作不会被提前执行。
WFE唤醒
- 通过WFE睡眠的CPU,下面方式唤醒
- unmasked interrupt
- Event(唤醒事情)
- 触发唤醒事件的方式:
- 执行了sev指令
- 本地CPU执行了sevl指令
- clear独占监视器,从独占状态变成开放状态
- 当持有锁的CPU通过stlr指令写入lock区域释放锁的时候,会触发一个唤醒事件,正在睡眠等待的spinlock的CPU会被唤醒

原子内存访问操作(Atomic Memory Access)
- ARMv8.1 上支持下面三种原子内存访问操作(Large System Extensions)
- Compare and Swwap instructions, CAS and CASP
- Atomic memory operation instructions
- Swap instruction
- 通过ID_AA64ISAR0_EL1寄存器中的atomic域来判断是否支持LSE

比较并交换(Compare and Swap)指令
- 比较并交换指令:检查ptr指向的值与expected是否相等。若相等,则把new的值赋给ptr;否则什么也不做。不管是相等,最终都会返回ptr的旧值

- ARMv8.1上的CAS指令
1 | CAS <Xs>, <Xt>, [Xn|SP] |
如果Xn的值(Xn是一个地址)==Xs,那么把Xt的值存储在Xn,返回值为Xs,等于Xn的旧值

CAS指令在Linux内核中的使用
- cmpxchg函数原型
cmpxchg原子地比较ptr地值是否与old的值相等,若相等,则把new的值设置到ptr地址中,返回old的值

mov x30, %x[old]
- 将 期望值
old复制到寄存器x30 x30作为 CASAL 指令的比较寄存器casal x30, %x[new], %[v]执行 CASAL 指令
参数:
x30:存储旧值,比较使用%x[new](x2):新值%[v](*ptr):内存地址
功能:
- 比较内存中的值与
x30(old) - 如果相等,写入
%x[new]到内存地址v - 如果不等,
x30被更新为当前内存值
- 比较内存中的值与
原子性 + Acquire-Release 内存序语义, 形成了一个双向内存屏障(Full fence):
1
[前面的写] ----必须在----> CASAL ----必须在----> [后面的读写]
CASAL 会在比较成功时把新值写入
[x0](即内存地址 *ptr),比较失败则返回内存中的旧值到寄存器x30
mov %x[ret], x30
- 将操作后的值写回返回值寄存器
[ret](绑定在 x0) - 返回的值可以告诉调用者:CAS 成功或失败
原子内存操作指令
- 原子加载指令(Atomic loads)
1 | LD<OP> <Xs>, <Xt>,[<Xn|SP>] |
相当于
1 | tmp = *Xn; |
原子存储指令
1 | ST<OP> <Xs>,[<Xn|SP>] |
相当于
1 | *Xn = *Xn <OP> Xs; |
- OP
| OP 操作 | 描述 |
|---|---|
| ADD | 原子加法 |
| CLR | 原子的比特位清除 |
| SET | 原子的比特位置位 |
| EOR | 原子的异或操作 |
| SMAX | 原子的有符号数的最大值 |
| SMIX | 原子的有符号数的最小值操作 |
| UMAX | 原子的无符号数的最大值 |
| UMIX | 原子的无符号数的最小值操作 |
举例:使用ldumax指令实现简单的spinlock

原子交换指令
1 | swp <Xs>, <Xt>, [<Xn|SP>] |
相当于
1 | tmp = *Xn; |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 常想一二,不思八九!
评论



