news 2026/4/18 14:49:23

ARM平台下atomic_add的底层实现:ldrex/strex指令是如何保证原子性的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ARM平台下atomic_add的底层实现:ldrex/strex指令是如何保证原子性的?

ARM平台下atomic_add的底层实现:ldrex/strex指令是如何保证原子性的?

在多核处理器成为主流的今天,原子操作的重要性愈发凸显。想象一下,当多个CPU核心同时对一个共享变量进行修改时,如何确保这个操作不会被中断?这就是原子操作要解决的核心问题。对于ARM架构的开发者来说,理解ldrex/strex这对指令的工作原理,是掌握并发编程基础的关键一步。

1. ARM原子操作的基本原理

原子操作的本质是不可分割性——要么完全执行,要么完全不执行。在ARM架构中,这一特性通过加载-存储独占(Load-Exclusive/Store-Exclusive)指令对实现。让我们先看一个典型的atomic_add实现:

1: ldrex r0, [r1] @ 加载独占 add r0, r0, r2 @ 执行加法 strex r3, r0, [r1] @ 存储独占 cmp r3, #0 @ 检查存储是否成功 bne 1b @ 若失败则重试

这段代码揭示了ARM原子操作的三个关键阶段:

  1. 加载独占阶段:ldrex指令不仅加载内存值,还会标记该内存区域为"被当前CPU核心监视"
  2. 修改阶段:在寄存器中完成算术运算
  3. 存储独占阶段:strex指令会检查内存区域是否仍被当前核心独占,如果是则存储成功

注意:ldrex/strex之间的指令序列应尽可能短,以减少被其他核心干扰的概率

与x86架构的LOCK前缀不同,ARM采用了一种更"温和"的原子性保证方式。x86会在指令执行期间直接锁定总线,而ARM则通过监视机制实现,这种设计带来了更好的可扩展性。

2. ldrex/strex的硬件实现细节

要深入理解这对指令的原子性保证,我们需要了解ARM处理器的底层监控机制。每个ARM核心都包含一个独占监视器(Exclusive Monitor),这是一个小型硬件状态机,负责跟踪内存访问情况。

独占监视器有两种实现:

  • 本地监视器:每个核心独享,处理非共享内存
  • 全局监视器:所有核心共享,处理标记为共享的内存区域

当执行ldrex指令时,处理器会:

  1. 从内存加载值到寄存器
  2. 记录被访问的内存地址(通常缓存行粒度)
  3. 设置独占监视器状态为"独占加载"

在后续的strex指令执行时,处理器会:

  1. 检查独占监视器状态
  2. 如果状态有效且内存区域未被修改,则执行存储并返回成功(0)
  3. 如果期间有其他核心修改了该内存区域,则存储失败并返回失败(1)

这种机制的精妙之处在于,它只在检测到冲突时才导致操作失败,而不是阻止其他核心的访问。下表对比了ARM与x86的原子操作实现差异:

特性ARM (ldrex/strex)x86 (LOCK前缀)
冲突处理方式乐观并发控制悲观锁
性能影响无冲突时开销小总是有锁定开销
实现复杂度需要硬件监视器支持直接锁定总线
可扩展性多核环境下表现更好核心数增多时性能下降
指令重排影响需要显式内存屏障隐含部分屏障语义

3. 内存屏障在原子操作中的作用

在ARM架构中,仅仅使用ldrex/strex并不能保证完整的内存一致性。考虑以下场景:

  1. 核心A修改了共享变量X
  2. 核心B随后读取X的新值
  3. 核心B基于X的值修改另一个共享变量Y

如果没有适当的内存屏障,核心A对X的修改可能会乱序执行,导致核心B看到不一致的内存状态。这就是为什么在Linux内核的atomic_add_return实现中会包含内存屏障:

static inline int atomic_add_return(int i, atomic_t *v) { unsigned long tmp; int result; smp_mb(); // 内存屏障 __asm__ __volatile__("@ atomic_add_return\n" "1: ldrex %0, [%3]\n" "add %0, %0, %4\n" "strex %1, %0, [%3]\n" "teq %1, #0\n" "bne 1b" : "=&r" (result), "=&r" (tmp) : "r" (&v->counter), "Ir" (i) : "cc"); smp_mb(); // 内存屏障 return result; }

ARM提供了三种基本内存屏障指令:

  1. DMB(Data Memory Barrier):确保屏障前的所有内存访问先于屏障后的内存访问完成
  2. DSB(Data Synchronization Barrier):比DMB更严格,确保所有指令都等待内存访问完成
  3. ISB(Instruction Synchronization Barrier):清空流水线,确保屏障后的指令重新从缓存或内存读取

在原子操作中,我们主要使用DMB来保证内存访问顺序。例如,在修改原子变量前插入DMB可以确保:

  • 所有先前的存储操作已完成
  • 所有先前的加载操作已完成
  • 但允许不相关的加载和存储继续执行

4. 实际应用场景与性能考量

理解ldrex/strex的底层机制对编写高效并发代码至关重要。让我们看几个典型应用场景:

4.1 引用计数

引用计数是原子操作的经典用例。在Linux内核中,许多数据结构都使用atomic_t作为引用计数器:

struct kobject { atomic_t refcount; // ... }; void kobject_get(struct kobject *kobj) { atomic_inc(&kobj->refcount); } void kobject_put(struct kobject *kobj) { if (atomic_dec_and_test(&kobj->refcount)) kobject_cleanup(kobj); }

在这种场景下,ldrex/strex的优势在于:

  • 无竞争时开销极小(单次ldrex+strex成功)
  • 竞争情况下通过重试而非锁等待来处理冲突
  • 适合读多写少的引用计数场景

4.2 自旋锁实现

虽然ARM有专门的SWP指令可用于锁实现,但在多核环境下,基于ldrex/strex的自旋锁性能更好:

void spin_lock(spinlock_t *lock) { while (1) { if (atomic_cmpxchg(&lock->val, 0, 1) == 0) break; while (atomic_read(&lock->val) != 0) cpu_relax(); } }

这里的atomic_cmpxchg内部同样使用ldrex/strex实现。相比完全基于原子交换的锁实现,这种混合方案在锁竞争时能减少总线争用。

4.3 性能优化技巧

基于对ldrex/strex工作原理的理解,我们可以总结出一些ARM平台原子操作的优化原则:

  1. 减少ldrex-strex之间的指令数:中间指令越多,被干扰的概率越大
  2. 避免在临界区调用函数:函数调用可能引入不可预测的延迟
  3. 合理使用内存屏障:只在必要时插入屏障,过度使用会降低性能
  4. 考虑变量对齐:确保原子变量独占整个缓存行,减少假共享
  5. 退避策略:在重试时适当加入延迟,减少总线争用

以下是一个优化后的atomic_add实现示例:

.align 3 1: ldrex r0, [r1] add r0, r0, r2 strex r3, r0, [r1] cbz r3, 2f @ 使用条件分支指令 yield @ 冲突时让出CPU b 1b 2: dmb ish @ 仅在实际修改后插入屏障

5. 对比其他架构的实现

理解ARM的原子操作实现后,与其他架构的对比能加深我们的认识。以下是主要架构的原子操作实现方式:

5.1 x86架构

x86使用LOCK前缀实现原子操作:

lock add dword ptr [rdi], esi

特点:

  • 通过锁定总线保证原子性
  • 隐含完整的内存屏障语义
  • 在多核环境下扩展性较差

5.2 RISC-V架构

RISC-V采用类似于ARM的加载保留/条件存储(LR/SC)指令对:

retry: lr.w t0, (a0) add t0, t0, a1 sc.w t1, t0, (a0) bnez t1, retry

特点:

  • 与ARM的ldrex/strex概念相似
  • 但规定了更严格的保留条件
  • 实现上可能更简单直接

5.3 MIPS架构

MIPS使用LL/SC(Load-Linked/Store-Conditional)指令对:

retry: ll t0, 0(a0) add t0, t0, a1 sc t0, 0(a0) beqz t0, retry

特点:

  • 概念上与ARM类似
  • 但保留标记的范围可能不同
  • 在某些实现中保留状态更易丢失

下表总结了各架构原子操作的主要差异:

架构指令对保留粒度内存模型典型重试开销
ARMldrex/strex缓存行弱一致性中等
x86LOCK前缀总线锁定强一致性
RISC-Vlr.w/sc.w实现定义弱一致性
MIPSll/sc实现定义弱一致性

在实际开发中,这种差异意味着:

  1. 可移植代码需要抽象:直接使用架构特定指令会限制代码移植性
  2. 性能特征不同:在x86上表现良好的算法可能在ARM上不理想
  3. 内存序考虑:不同架构的内存模型影响并发算法的正确性

6. 调试与问题排查

理解ldrex/strex的底层行为对调试并发问题至关重要。以下是常见的原子操作相关问题及排查方法:

6.1 常见问题

  1. 活锁:多个核心不断重试ldrex/strex,导致性能下降
  2. 内存序问题:缺少适当屏障导致的内存可见性问题
  3. ABA问题:在比较交换操作中,值从A变B又变回A导致的逻辑错误
  4. 缓存一致性:不同核心看到的内存状态不一致

6.2 调试工具与技术

  1. 内核tracepoint:ARM架构通常提供独占监视器相关的性能计数器

    perf stat -e armv8_pmuv3_0/ld_spec/ -e armv8_pmuv3_0/st_spec/
  2. 模拟器调试:QEMU等模拟器可以单步执行并观察独占监视器状态

  3. 静态分析:使用Coccinelle等工具检测潜在的原子操作误用

  4. 内存模型验证工具:如herd7可以验证算法在不同内存模型下的行为

6.3 典型错误案例

案例1:缺少内存屏障

// 错误实现 void unsafe_increment(atomic_t *v) { unsigned long tmp; int result; __asm__ __volatile__( "1: ldrex %0, [%3]\n" "add %0, %0, %4\n" "strex %1, %0, [%3]\n" "teq %1, #0\n" "bne 1b" : "=&r" (result), "=&r" (tmp) : "r" (&v->counter), "Ir" (1) : "cc"); // 缺少内存屏障可能导致其他核心看到乱序的内存访问 }

案例2:过长的临界区

// 次优实现 int atomic_complex_op(atomic_t *v) { unsigned long tmp; int result; __asm__ __volatile__( "1: ldrex %0, [%3]\n" // 过多的计算增加了被干扰的概率 "bl complex_calculation\n" "strex %1, %0, [%3]\n" "teq %1, #0\n" "bne 1b" : "=&r" (result), "=&r" (tmp) : "r" (&v->counter), "Ir" (1) : "cc", "r0", "r1", "r2", "r3", "lr"); return result; }

正确的做法是将复杂计算移到临界区外,或者考虑使用更高级别的锁机制。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 14:48:37

AutoDL租用GPU实例与PyCharm远程开发:一站式配置与高效调试指南

1. AutoDL租用GPU实例全流程指南 作为一名常年折腾深度学习模型的"炼丹师",我深刻理解算力不足的痛苦。本地显卡跑不动大模型?实验室服务器要排队?这时候云GPU租用就是救命稻草。最近半年我深度使用了AutoDL平台,实测下…

作者头像 李华
网站建设 2026/4/18 14:47:48

50+编程语言高清图标库:技术文档与博客的视觉利器

50编程语言高清图标库:技术文档与博客的视觉利器 【免费下载链接】programming-languages-logos Programming Languages Logos 项目地址: https://gitcode.com/gh_mirrors/pr/programming-languages-logos 在技术分享、项目文档或教育材料中,高质…

作者头像 李华
网站建设 2026/4/18 14:46:52

Kubernetes StatefulSet 数据持久化实践

Kubernetes StatefulSet 数据持久化实践 在云原生应用部署中,有状态服务的数据持久化一直是技术难点。Kubernetes StatefulSet 作为管理有状态工作负载的核心资源,通过独特的网络标识和持久化存储机制,为数据库、消息队列等应用提供了稳定可…

作者头像 李华
网站建设 2026/4/18 14:45:43

D2DX终极指南:5步让暗黑破坏神2在现代电脑上重获新生

D2DX终极指南:5步让暗黑破坏神2在现代电脑上重获新生 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

作者头像 李华