在之前的博文当中,笔者介绍了ARM中支持同步和信号量的硬件实现机制:Exclusive access(独占式访问)以及Load-Exclusive/Store-Exclusive指令的使用:
ARMv8 同步和信号量(Synchronization and semaphores)简介
ARMv8 同步和信号量(读写一致性问题):Load-Exclusive/Store-Exclusive指令详解
在Load-Exclusive/Store-Exclusive指令的使用过程中,存在着一些使用限制。如果未按照规范,错误地使用Load-Exclusive/Store-Exclusive指令,可能会造成Load-Exclusive/Store-Exclusive失效。对于Load-Exclusive指令,我们可以检查数据是否load成功;对于Store-Exclusive指令,会返回一个状态位信息,为0表示exclusive 访问成功,为 1 表示失败。
下面笔者将介绍Load-Exclusive/Store-Exclusive指令的一些使用限制、注意事项,以及当发生Load-Exclusive/Store-Exclusive访问内存失败时,我们应当如何分析和定位问题。
ARM架构中导致独占式内存访问Exclusive access 指令(LDXR/STXR)失败的原因分析
- 一,LoadExcl/StoreExcl 指令应当成对使用
- 1.1 LoadExcl 和 StoreExcl 应当使用同一个地址
- 1.2 LoadExcl 和 StoreExcl 的transaction size和访问的寄存器数量应当保持一致
- 1.3 LoadExcl 和 StoreExcl 访问地址空间的Tag Checked property和memory attribute应当一致
- 二,在LoadExcl 和 StoreExcl指令对之间不应该出现的操作
- 三,LoadExcl 和 StoreExcl指令注意事项
- 3.1 LoadExcl 和 StoreExcl指令执行的先后时间差应该尽可能短
- 3.2 Granule size建议使用保留值(Exclusives reservation granule)
- 3.3 VA到PA的映射关系不应发生改变
- 3.4 发生cache line eviction或者是cache 维护指令(clean、invalidate、clean&invalidate)
- 3.5 使用预加载指令预取数据 Prefetch memory
- 3.6 异常产生、异常返回ERET以及debug state切换
- 五,当Exclusive Access失败时应当如何分析
- 1 检查访问的内存属性:shareability 、cacheability
- 2 检查 local monitor 和 global monitor
- 2.1 local monitor
- 2.2 Global monitor 的具体实现
一,LoadExcl/StoreExcl 指令应当成对使用
如上表所示,为ARMv8架构提供Exclusive Access的A64相关指令。LoadExcl/StoreExcl 指令应当成对使用有两层含义,其中一个就是:Exclusive 访问时,load exclusive 和 store exclusive的指令应该按照上表所述,配对使用。比如当使用LDXR指令进行load exclusive access时,对应的,应当使用STXR执行store exclusive access。
不仅如此,LoadExcl/StoreExcl 指令访问的地址空间也应当相同,需遵循如下原则:
1.1 LoadExcl 和 StoreExcl 应当使用同一个地址
如果开了MMU,则LoadExcl 和 StoreExcl 指令应当访问同一个虚拟地址(VA)。如果没有使能MMU,则应当访问同一个物理地址(PA)。
1.2 LoadExcl 和 StoreExcl 的transaction size和访问的寄存器数量应当保持一致
如上表所示,不同的LoadExcl 和 StoreExcl访问内存的transaction size和访问的寄存器数量都不同,为了确保LoadExcl 和 StoreExcl 的transaction size和访问的寄存器数量保持一致,应当按照上表规则,配对使用。
1.3 LoadExcl 和 StoreExcl 访问地址空间的Tag Checked property和memory attribute应当一致
在LoadExcl 和 StoreExcl 访问同一个内存地址的时候,如果该内存空间的Tag Checked property或者内存属性发生了变化,将会导致不可预测的后果:exclusive 访问极大可能会失败。常见的内存属性比如有 shareability (inner shareable,outer shareable和non-shareable)和Cacheability (write-back和write-through)。
二,在LoadExcl 和 StoreExcl指令对之间不应该出现的操作
在LoadExcl 和 StoreExcl指令对之间,以下操作不应出现:
- 显式的内存影响
- 内存预加载
- 直接或间接的系统寄存器读写
- 地址转换指令
- cache 或者TLB 维护指令
- 异常产生指令
- Data abort 异常
- 异常返回
- ISB 内存屏障指令
- 间接的分支跳转指令
如下示例代码,为使用ldxr和stxr指令进行原子自加1的示例程序:
; 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
在多线程或者多核的系统中,复杂的环境无法保证每次的exclusive access都能成功,所以在使用exclusive access指令时,建议养成一个良好的编程习惯:在store exclusive 指令执行后,检查其返回的状态标志(为0表示exclusive访问成功,为1表示失败)。如果访问成功,程序继续往下执行,如果失败则跳转到load exclusive指令的地址,重新进行 load/store exclusive操作。
如果store-exclusive返回一个失败的结果,在返回结果并从新开始新的load-exclusive指令之间,不应当进行如下操作:
- 不允许有任何的load/ store指令或者PRFM指令访问当前exclusive 维护的granule中的地址,即使该地址与即将执行的load-exclusive指令访问的地址不同,但是处于同一个granule,也同样不行。
- 期间没有直接或者间接的系统寄存器写入,地址转换指令,cache或者TLB维护指令,异常产生,异常返回或者间接的分支跳转。
- 对一块连续的内存空间进行任何的load 或者store操作时,不能超过 512 bytes。
三,LoadExcl 和 StoreExcl指令注意事项
3.1 LoadExcl 和 StoreExcl指令执行的先后时间差应该尽可能短
在Exclusive access的实际应用中,应当尽量缩短Load exclusive 指令和 Store exclusive指令之间的距离,最好在单线程内将LoadExcl 和 StoreExcl指令都执行完成。换句话说,执行完load exclusive指令后,在尽可能短的时间内执行store exclusive指令,完成一对load exclusive和store exclusive指令。Load exclusive 指令和 Store exclusive指令之间,尽量减小Exclusive monitor的状态机被意外因素重置的可能性。此外,考虑到最佳的性能要求,ARM官方强烈建议,在一个单线程的执行过程中,LoadExclusive和Store Exclusive之间的指令数量最好不要超过128 bytes。
3.2 Granule size建议使用保留值(Exclusives reservation granule)
ARM架构支持最大2048 bytes的granule size,作为Exclusives reservation granule。
什么是Exclusives reservation granule?
当执行一个Load-Exclusive指令时,将会忽略64位地址的最低有效位,同时标记一个大小为 2^a byte大小的内存空间为Exclusive Access状态。被标记的地址可以是该内存块的任意一个地址。这个内存块称为 独占式保留颗粒(Exclusives reservation granule),granule的大小范围在4到512个words(1 word = 4 bytes)之间,其具体的大小由具体的架构实现定义。比如在A53处理器中,granule的大小为一个cache line的大小:16 words(16 words = 64 bytes = 512 bits)。
- 4 words in an implementation where a is 4.
- 512 words in an implementation where a is 11.
当a为4时,granule的大小为 4个words,比如对 0x341B4这个地址执行 LDXRB 指令,将会把 0x341B0 到 0x341BF这块内存空间标记为exclusive access。
关于Exclusives reservation granule size的默认值,可以在CTR(Cache Type Register)寄存中找到该字段,比如CTR_EL0[23:20] ==0b0100 = 4,则2^4 = 16 words = 64 bytes。
3.3 VA到PA的映射关系不应发生改变
如果在执行LoadExcl 和 StoreExcl指令期间,使用 break-before-make 方式更新了VA到PA的映射关系。在旧的页表(映射关系)失效后,如果有两个store exclusive指令先后对同一个PA进行写操作,后一个的store exclusive指令可能会失败。
3.4 发生cache line eviction或者是cache 维护指令(clean、invalidate、clean&invalidate)
无论是cache clean、invalidate还是clean&invalidate操作,都会对local monitor和global monitor的Exclusive Access状态造成不可预测的影响。cache 维护指令可能会重置monitor的状态机,或者也可能保持它的exclusive access状态。此外,对于基与地址的缓存维护指令,如果该地址是shreable的,那么位于该shareability domain的其他处理器,若也正在维护该地址的exclusive access,那么也会影响到其他处理器相关的monitor的状态。
如果在load exclusive指令和store exclusive 指令有普通的store指令,普通的store指令的操作地址无论是当前Load exclusive指令标记的地址还是其他地址,都不会对local monitor的状态机有直接的影响,如下图所示:
但是,如果访问的地址空间的内存属性为cacheable的,该普通的store操作造成了被标记exclusive access的地址所在的cache line 发生了eviction,cache line的eviction会导致monitor的状态机被重置。因此,ARM建议在load exclusive和store exclusive指令之间,最好不要有任何的load 或者store操作,因为这些额外的读写指令有可能导致被标记地址所在的cache line被evict。而任何缓存维护指令和cache line eviction操作,都会重置 exclusive monitor的状态机。
3.5 使用预加载指令预取数据 Prefetch memory
如下示例程序,在Load exclusive指令之前,使用了PREM 指令提前将要Load exclusive access的数据存到cache中:
; extern int atom_add(int *val);
_atom_add:
PRFM PSTL1KEEP, x0 ; 使用预加载指令将x0指向的地址上的数据预加载到cache中
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
这样做有两大风险:
- 不能确保PRFM指令已经将数据预加载到cache中后,才执行ldxr指令。可能导致ldxr指令执行失败,未进入 exclusive access状态,如果非要执行PRFM指令,建议在PRFM指令后加上DMB内存屏障,确保预加载完成后再执行ldxr。
- 如果多个core同时对同一个地址执行 PFRM PST* 指令,可能会导致Exclusive access状态的丢失。这意味着如果多个core在高频率地对同一个地址执行PFRM PST* 指令,可能会阻碍其他core的正常执行。
3.6 异常产生、异常返回ERET以及debug state切换
如果在LoadExcl 和 StoreExcl指令指令之间产生了异常或者是异常返回(ERET),不管是同步异常还是异步异常(中断或者SError),都有可能导致Exclusive monitor的状态丢失,导致store exclusive指令执行失败。
不仅如此,外部的一些debug 事件(external debug event)会导致处理器进入debug state,在debug state和正常状态之间切换,也会导致Exclusive monitor的状态丢失。常见的debug event有:
- External debug request debug event
- Halt instruction debug event
- Halting step debug event
- Exception catch debug event
- Reset catch debug event
- Software access debug event
- OS unlock catch debug event
- Breakpoint event
- Watchpoint event
五,当Exclusive Access失败时应当如何分析
如下图所示,为ARM架构下的exclusive access monitor的一般架构图,其中local monitor位于每个core的L1 内存系统中,并且如果实现了global monitor的话,它一般位于外部内存的内存接口中,针对不同的内存有着不同的global monitor。
Exclusive access失败(load exclusive失败或者store exclusive 失败)的根本原因就是 exclusive monitor中的状态机的状态切换(open状态转exclusive状态,或者exclusive状态转换到open状态)没有按照预期的效果进行。有可能是Exclusive monitor的状态机被清除了,或者是Exclusive monitor没有收到来自内存系统的正确的反馈(ACK),导致状态机切换到错误状态。
接下来,笔者将介绍当遇到exclusive access失败时的一般分析方法。
1 检查访问的内存属性:shareability 、cacheability
首先我们要确定是否使用到了local monitor和 global monitor,而内存属性中的shareability和cacheability 会影响到local monitor 和global monitor的使用。如果exclusive access访问的目标地址的shareability为non-shareable的,并且是单个处理器中的程序发起的,则使用local monitor。但是local monitor同样也会使用在inner shareable的地址空间,比如在多core多线程的环境中,local monitor和global monitor共同维护exclusive access的操作。如果是non-cacheable的地址空间,系统默认会把non-cacheable当做是outer-shareable,所以也需要local monitor和global monitor。
2 检查 local monitor 和 global monitor
其次,检查当前架构下的local monitor和global monitor的具体实现。
2.1 local monitor
如下图所示为ARMv8 Cortex-A系统下的local monitor的状态机转换图,其中打星号的操作由具体的架构实现来决定
而下图则是Cortex-A55处理器的local monitor的具体实现,在执行完LoadExcl(x)后,无论是对当前被标记地址进行普通的store指令还是对其他地址进行普通的store指令,都不会对当前的 Exclusive access产生任何影响。
2.2 Global monitor 的具体实现
其次,我们还要检查当前系统下的Global monitor的具体实现:
- 对不同memory访问时,是否实现了不同的Global monitor。
- Global monitor位于哪里,位于内存接口还是系统总线上。
- Global monitor最多执行维护多少个状态机,最多同时支持多少个不同地址的exclusive access 操作。
- 有何依赖,是否依赖于系统总线上的ack。比如CHI总线的Exclusive access,需要下级总线回应Exclusive Okay或者Normal Okay。如果处理器发出的transaction 为 Exclusive access,但是系统总线上返回的是Normal Okay,那么会导致该Exclusive access 失败。
- 总线上是否有针对exclusive access的特殊配置,或者能够使能或者禁止Exclusive access的配置。
- Global Monitor的具体实现,如下图所示,Global monitor的很多操作(如下打星号的操作)都依赖于具体的系统实现: