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

文摘   科技   2024-10-26 11:16   上海  

在上篇文章中介绍了ARMv8中同步与信号量的基本原理:ARMv8内存属性与类型(Memory types and attributes)简介

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

一、Local Monitor 与 Global Monitor

ARMv8内存属性与类型(Memory types and attributes)简介一文中有对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=*valldxr w0, [x9] ; 从val所在的内存中读取一个 int,并标记 Exclusiveadd w0, w0, #1 ; w0=w0+1stxr w8, w0, [x9] ; 尝试写回 val 位置,写入结果保存在 w8cbz w8, atom_add_done ; 如果 w8 为 0 说明成功,跳到程序结束mov x0, x9 ; 恢复备份的 x0,重新执行 atom_addb _atom_addatom_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=__theAmountstxr    w9, w8, [x1]     ;将w8写回到*__theValue, 结果保存到w9cbnz    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读取该段内存中保存的最新值,再处理一次,再尝试保存,直到成功为止。

文章转自CSDN,作者:SOC罗三炮,文章已获得原创作者授

Arm精选
ARMv8/ARMv9架构、SOC架构、Trustzone/TEE安全、终端安全、SOC安全、ARM安全、ATF、OPTEE等
 最新文章