ARM64:LDXR/STXR与LDAXR/STLXR

Dav*_*Lee 7 arm atomic arm64

在iOS上,有两个类似的功能,OSAtomicAdd32OSAtomicAdd32Barrier.我想知道你什么时候需要Barrier变种.

拆卸后,它们是:

_OSAtomicAdd32:
ldxr    w8, [x1]
add     w8, w8, w0
stxr    w9, w8, [x1]
cbnz    w9, _OSAtomicAdd32
mov     x0, x8
ret     lr

_OSAtomicAdd32Barrier:
ldaxr   w8, [x1]
add     w8, w8, w0
stlxr   w9, w8, [x1]
cbnz    w9, _OSAtomicAdd32Barrier
mov     x0, x8
ret     lr
Run Code Online (Sandbox Code Playgroud)

在哪种情况下,您需要后者的Load-Acquire/Store-Release语义?可以LDXR/ STXR指令重新排序吗?如果可以,原子更新是否有可能在没有障碍的情况下"丢失"?从我所读到的,似乎不会发生这种情况,如果是真的,那你为什么需要这个Barrier变体呢?也许只有你碰巧还需要一个DMB其他目的?

谢谢!

Not*_*hat 13

哦,弱记忆秩序的令人费解的恐怖......

第一个片段是您的基本原子读取 - 修改 - 写入 - 如果其他人触及任何地址x1点,则存储专用将失败并且它将再次尝试直到成功.到现在为止还挺好.但是,这仅适用于独占监视器覆盖的地址(或更正确的区域),因此虽然它对原子性有好处,但对于除该值之外的任何其他内容的同步都是无效的.

考虑CPU1正在等待CPU0将一些数据写入缓冲区的情况.CPU1坐在那里等待某种同步对象(假设是一个信号量),等待CPU0更新它以表示新数据准备就绪.

  1. CPU0写入数据地址.
  2. CPU0递增信号量(原子地,就像你那样),恰好在内存中的其他地方.
  3. ???
  4. CPU1看到新的信号量值.
  5. CPU1读取一些数据,这些数据可能是也可能不是旧数据,新数据或两者的某种混合.

现在,第3步发生了什么?也许这一切都按顺序发生.很可能,硬件决定由于没有地址依赖性,它会让信号量的商店超过商店到数据地址.也许信号量存储在缓存中命中而数据没有.也许只是这样做是因为复杂的原因只有那些硬件人才明白.无论哪种方式,CPU1都可以在新数据到达内存之前查看信号量更新,从而读回无效数据.

要解决这个问题,CPU0必须在步骤1和步骤2之间有一个屏障,以确保在写入信号量之前已经写入了数据.原子写入一个障碍是一个很好的简单方法来做到这一点.但是,由于屏障性能相当差,因此您需要轻量级无屏障版本以及不需要这种完全同步的情况.

现在,更不直观的部分是CPU1还可以重新排序其负载.由于没有地址依赖性,因此可以在信号量加载之前推测数据加载,而不管CPU0的障碍.因此,CPU1在步骤4和5之间也需要它自己的屏障.

对于更权威,但相当重的版本,阅读ARM的Barrier Litmus测试和食谱.请注意,这些东西可能会令人困惑 ;)

顺便一提,在这种情况下,获取/发布的架构语义使事情进一步复杂化.由于它们只是单向障碍,虽然OSAtomicAdd32Barrier相对于代码之前和之后的代码增加了一个完整的障碍,它实际上并不保证相对于原子操作本身的任何顺序 - 请参阅Linux的讨论以获得更多解释.当然,这是从建筑的理论角度来看; 在现实中它不是不可思议的A7硬件已布线了的"简单"的选项LDAXR,只是不DMB+LDXR,等等,这意味着他们可以不用它,因为他们是在自由编写自己的实现,而不是规格.


Var*_*der 8

OSAtomicAdd32Barrier()存在于那些正在使用OSAtomicAdd()超越原子增量的东西的人.具体来说,他们正在实现自己的多处理同步原语OSAtomicAdd().例如,创建自己的互斥库. OSAtomicAdd32Barrier()使用重屏障指令在原子操作的两侧强制执行内存排序.这在正常使用中是不希望的.

总结一下:

1)如果您只想以线程安全的方式递增整数,请使用 OSAtomicAdd32()

2)如果你遇到一堆愚蠢地认为OSAtomicAdd32()可以用作处理器内存排序和推测障碍的旧代码,请将其替换为OSAtomicAdd32Barrier()