目录
一,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位于各个处理器中,多个处理器共用一个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)进行独占访问的指令。如果想独占访问的内存区域不是一个字,还有其它的指令:
- LDREXB和STREXB:对内存中的一个字节(Byte,8 bit)进行独占访问;
- LDREXH和STREXH:中的一个半字(Half Word,16 bit)进行独占访问;
- 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进行访问。
它们的执行顺序如下:
- CPU1的thread 3最先执行LDREX,锁定地址A开始的内存区域为exclusive access,同时更新CPU1的local monitor和global monitor的状态为Exclusive 状态。
- 然后CPU0的Thread1也执行LDREX,它也会更新CPU0 的local monitor和global monitor的状态为Exclusive状态。此时,在global monitor的视角中,CPU0和CPU1都对以地址A开始的一段内存做了Exclusive acces的标记。
- 接着,CPU0的Thread2也执行到了LDREX,此时它会发现CPU0的local monitor已经对该段内存做了独占标记,同时global monitor上CPU0也对该内存做了独占标记。但这并不会影响该指令的执行。
- 接下来,CPU0的Thread1首先执行STREX指令,尝试往地址A写入新的值。此时发现CPU0的local monitor对该段内存进行了独占访问标记,并且global monitor中也有CPU 0对该内存的独占标记,所以STREX指令将成功执行。同时会清除CPU0的local monitor以及global monitor中所有处理器对该段内存的独占标记。
- 接下来CPU1的Thread3也执行到了STREX,但是此时只有CPU1的local monitor对该段内存有独占标记,global monitor中没有CPU1的独占标记。所以更新失败,STREX指令执行失败。
- 同理,CPU0的Thread2执行STREX时也将失败,它会发现不管是local monitor还是global monitor都没有对该段内存的独占标记。
- 如果程序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