ARMv8 同步和信号量(读写一致性问题):Load-Exclusive/Store-Exclusive指令详解

目录

一,Local Monitor 与 Global Monitor

1,Local Monitor

2,Global Monitor

 二,Exclusive 指令的简单使用

三,Exclusive 示例程序

1,原子自加1程序

 2,原子锁程序

四, 多处理器多线程中Exclusive指令执行解析


在上篇文章中介绍了ARMv8中同步与信号量的基本原理:

ARMv8 同步和信号量(Synchronization and semaphores)简介_SOC罗三炮的博客-CSDN博客

接下来本文将继续围绕这个话题,详细介绍Exclusive相关指令:Load-Exclusive/Store-Exclusive的具体使用。

一,Local Monitor 与 Global Monitor

ARMv8 同步和信号量(Synchronization and semaphores)简介_SOC罗三炮的博客-CSDN博客 一文中有对local monitor和global monitor做详细描述。

1,Local Monitor

如果内存被标记为Non-shareable内存,说明该内存不可共享的,只能被单个处理器访问。所以对这种内存的读写一致性问题,只需要Local monitor维护即可。Local monitor只在处理器内部维护exclusive状态,由于不涉及多处理器的exclusive状态共享,所以不需要对真正的内存进行标记。在硬件实现上,可以通过对内存地址进行标记exclusive状态,来实现单个处理器的读写一致性。也可以通过追踪exclusive 指令(Load-Exclusive/Store-Exclusive)来维护单处理器中多线程读写一致性。

2,Global Monitor

对于shareable 内存,既可共享内存而言,该类型内存可以被多个处理器同时访问,此时需要使用Global Monitor来维护读写一致性,通过对共享内存的物理内存标记为独占式访问:定义一个mutex信号量,来保证多处理器并发时的多读单写。

根据ARMv8手册描述:global monitor可以存在于处理器中,也可以作为一个二级的monitor存在于内存接口中。在具体的设计实现中,Local monitor和Global Monitor甚至可以合并成一个独立单元,但是既提供local  monitor的功能和global monitor的功能。

下图是Exclusive的一种实现架构:Local monitor位于各个处理器中(在cortex-A系列中,如A53,A55以及A73,local monitor都位于L1 cache中),多个处理器共用一个global monitor:

 二,Exclusive 指令的简单使用

在AArch32中,使用的Exclusive 指令是LDREX和STREX:

LDREX R1, [R0]
STREX R2, R1, [R0]

在AArch64中,使用的Exclusive 指令是LDXR & STXR:

ldxr w0, [x9]
stxr w8, w0, [x9] 

 下面以LDREX和STREX为例,介绍两个指令的简单使用:

LDREX R1, [R0] 指令将加载 R0所指向内存地址的一个word(4 bytes)数据,加载到R1中。同时会初始化Exclusive monitor的exclusive 状态,并将R0所指向的内存区域(一个granule 大小)标记为Exclusive access。

STREX R2, R1, [R0] 是一个有条件的 store指令,它是否成功执行,取决于Exclusive monitor,如果Exclusive monitor通过状态机发现存在Store-Exclusive指令成功执行的条件:

  • Store操作将执行:R1中的值将会被更新到R0所指向的内存位置
  • monitor中的Exclusive 状态将会被清除,之前被标记为Exclusive access的内存区域也会被清除标记
  • 状态寄存器R2中的值将会被设置为0,表明该STREX指令执行成功。

如果不具备Store-Exclusive指令成功执行的条件,Store操作将不会进行,状态寄存器R2中的值将会被设置为1,表明该STREX指令执行失败。

此外,LDREX和STREX是对内存中的一个字(Word,32 bit)进行独占访问的指令。如果想独占访问的内存区域不是一个字,还有其它的指令:

  1. LDREXB和STREXB:对内存中的一个字节(Byte,8 bit)进行独占访问;
  2. LDREXH和STREXH:中的一个半字(Half Word,16 bit)进行独占访问;
  3. LDREXD和STREXD:中的一个双字(Double Word,64 bit)进行独占访问。

它们必须配对使用,不能混用。

三,Exclusive 示例程序

1,原子自加1程序

下面的例子给出了使用 LDXR & STXR 实现原子加一的过程:

; extern int atom_add(int *val);
_atom_add:
mov x9, x0 ; 备份 x0,为了失败时恢复,x9=x0=*val
ldxr w0, [x9] ; 从val所在的内存中读取一个 int,并标记 Exclusive
add w0, w0, #1 ; w0=w0+1
stxr w8, w0, [x9] ; 尝试写回 val 位置,写入结果保存在 w8
cbz w8, atom_add_done ; 如果 w8 为 0 说明成功,跳到程序结束
mov x0, x9 ; 恢复备份的 x0,重新执行 atom_add
b _atom_add
atom_add_done:
ret

另一个自加程序 (存在于 libkern 提供的 OSAtomicAdd32 函数):

;int32_t OSAtomicAdd32(int32_t __theAmount, volatile int32_t *__theValue);
ldxr    w8, [x1]      ;将__theValue的值加载到w8,同时标记Exclusive access状态
add     w8, w8, w0     ; w8=w8+w0, w0=__theAmount
stxr    w9, w8, [x1]     ;将w8写回到*__theValue, 结果保存到w9
cbnz    w9, _OSAtomicAdd32 ;判断w9是否为0,不为0则跳到函数头,重新执行函数
mov     x0, x8        ;成功则将w8作为返回值返回
ret     lr

 2,原子锁程序

关于此原子锁程序的解析,参考原文:ARMv8之exclusive操作(二)exclusive操作例子 | 骏的世界

; void lock(lock_t *ptr)
lock:
    ; is it locked?
    LDXR   W1, [X0]        ; Load current value of lock
    CMP       W1, #LOCKED  ; Compare with "LOCKED"
    B.EQ   lock            ; If LOCKED, try again
 
    ; Attempt to lock
    MOV       W1, #LOCKDED
    STXR   W2, W1, [X0]    ; Attempt to lock
    CBNZ   W2, lock        ; If STXR failed, try again
    DMB    SY              ; Ensures acesses to the resource are not made
                           ; before the lock is acquired
    RET

四, 多处理器多线程中Exclusive指令执行解析

在文章ARMv8之exclusive操作(二)exclusive操作例子 | 骏的世界中,解析了两个线程进行抢锁的过程。扩展到多线程,多个CPU也是一样的原理:对同个地址进行读写操作时,同一时间只会有一个线程能成功完成读写操作。

下面将举一个多处理器多线程的例子,来分析ARMv8中独占式访问的原理:

如下图所示,某系统中有两个CPU,CPU0里有两个线程:Thread 1执行程序1,Thread 2执行程序2,。CPU1中有一个线程:线程3中执行程序2。三个线程中的程序都对同一个地址A进行访问。

它们的执行顺序如下:

  1. CPU1的thread 3最先执行LDREX,锁定地址A开始的内存区域为exclusive access,同时更新CPU1的local monitor和global monitor的状态为Exclusive 状态。
  2. 然后CPU0的Thread1也执行LDREX,它也会更新CPU0 的local monitor和global monitor的状态为Exclusive状态。此时,在global monitor的视角中,CPU0和CPU1都对以地址A开始的一段内存做了Exclusive acces的标记。
  3. 接着,CPU0的Thread2也执行到了LDREX,此时它会发现CPU0的local monitor已经对该段内存做了独占标记,同时global monitor上CPU0也对该内存做了独占标记。但这并不会影响该指令的执行。
  4. 接下来,CPU0的Thread1首先执行STREX指令,尝试往地址A写入新的值。此时发现CPU0的local monitor对该段内存进行了独占访问标记,并且global monitor中也有CPU 0对该内存的独占标记,所以STREX指令将成功执行。同时会清除CPU0的local monitor以及global monitor中所有处理器对该段内存的独占标记。
  5. 接下来CPU1的Thread3也执行到了STREX,但是此时只有CPU1的local monitor对该段内存有独占标记,global monitor中没有CPU1的独占标记。所以更新失败,STREX指令执行失败。
  6. 同理,CPU0的Thread2执行STREX时也将失败,它会发现不管是local monitor还是global monitor都没有对该段内存的独占标记。
  7. 如果程序2是上文提到的原子自加程序,在执行STREX指令失败后,将会重新进行LDREX,此时,三个线程执行完成的顺序为:Thread1 - Tread3 - Tread2。

ARM的exclusive独占式访问的机制的核心在于:

在同一时间内,允许多个观察者对同一段内存进行读取,标记为独占式访问,但是只允许其中一个观察者能够对该内存进行成功写入,按照先写先得原则,最先执行完LDREX/STREX指令对的观察者(最先完成对该内存的更新)可以成功,其他的都会失败

这样就可以维护多个观察者的读写一致性问题。实际的使用中,可以重新用LDREX读取该段内存中保存的最新值,再处理一次,再尝试保存,直到成功为止。

参考文章:

iOS汇编教程(七)ARM Exclusive - 互斥锁与读写一致性的底层实现原理 - 掘金在多线程编程中,我们常常使用互斥锁来保证全局变量的线程安全,例如 pthread 中的 pthread_mutex,mach 中的 semaphore。他们通过 lock & unlock 或是 up & down 的方式来维护资源的状态,保证只有特定个数的线程能获得特定个数的…https://juejin.cn/post/6844903970536685576ARM平台下独占访问指令LDREX和STREX的原理与使用详解_adaptiver的博客-CSDN博客_ldrexLDREX Rx, [Ry]读取寄存器Ry指向的4字节内存值,将其保存到Rx寄存器中,同时标记对Ry指向内存区域的独占访问。STREX Rx, Ry, [Rz]如果执行这条指令的时候发现已经被标记为独占访问了,则将寄存器Ry中的值更新到寄存器Rz指向的内存,并将寄存器Rx设置成0。指令执行成功后,会将独占访问标记位清除。而如果执行这条指令的时候发现没有设置独占标记,https://blog.csdn.net/adaptiver/article/details/72392825ARMv8之exclusive操作(二)exclusive操作例子 | 骏的世界http://www.lujun.org.cn/?p=4142

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SOC罗三炮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值