背景
原子指令顾名思义,就是这条指令的指令是一个整体,是“不可拆分的”,要么没做,要么已经完成了,不会出现只完成一半的场景。对于一些原子指令来说,可能需要分好几步来实现,比如“读-修改-写”。如果一个核在执行原子指令,那么在别的核的视角里,这条指令要么没做,要么这条原子指令的所有步骤都已经做完了。
这种特性在多处理器系统中尤为重要,可以用来实现同步锁,可以防止因多个处理器同时访问和修改同一数据而导致的竞争条件和数据不一致问题。因此,原子指令主要用于实现多核间的同步,确保在多个线程或进程之间正确地共享和访问资源,从而避免死锁、竞争条件等同步问题。
原子指令
RV A extension中介绍了AMO指令(即原子指令),AMO指令需要完成“读-修改-写”的操作,包括如下指令:
- AMOADD.W/D(原子加字):将内存中地址为x[rs1]中的字与x[rs2]相加,并将结果写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOXOR.W/D(原子异或字):将内存中地址为x[rs1]中的字与x[rs2]进行按位异或操作,并将结果写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOAND.W/D(原子与字):将内存中地址为x[rs1]中的字与x[rs2]进行按位与操作,并将结果写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOOR.W/D(原子或字):将内存中地址为x[rs1]中的字与x[rs2]进行按位或操作,并将结果写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOSWAP.W/D(原子字交换):将内存中地址为x[rs1]的字与x[rs2]的值进行交换,并将x[rs1]原值符号位扩展后存入rd寄存器。
- AMOMIN.W/D(原子最小字):比较内存中地址为x[rs1]的字与x[rs2]的值,将较小值(二进制补码比较)写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOMAX.W/D(原子最大字):比较内存中地址为x[rs1]的字与x[rs2]的值,将较大值(二进制补码比较)写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOMINU.W/D(原子无符号最小字):比较内存中地址为x[rs1]的字与x[rs2]的值,将无符号数比较后的较小值写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
- AMOMAXU.W/D(原子无符号最大字):比较内存中地址为x[rs1]的字与x[rs2]的值,将无符号数比较后的较大值写回该地址。同时,将原始值符号位扩展后存入rd寄存器。
用途
在背景中,我们介绍了原子指令可以实现线程之间同步锁,spec举了一个例子使用原子指令来实现经典的spinlock自旋锁。自旋锁保证了在任何时刻,只有一个线程(或CPU)能够获得锁,并且执行Critical section中的内容,其他线程(或CPU)则会忙等待,直到获得锁为止。
li t0, 1
again:
lw t1, (a0) # 将锁的值读回来
bnez t1, again # 检查锁是否被占据(不为0),如果被占,则再读一次
amoswap.w.aq t1, t0, (a0) # 如果锁未被占据,则将t0和锁的值互换
bnez t1, again # 如果锁在bnez和amoswap之间被占据了,则再尝试一次
#…
#Critical section.
#…
amoswap.w.rl x0, x0, (a0) # 将锁释放
硬件实现
在硬件实现上,CPU需要保证该原子指令的执行不被打断,并且在多核系统中,原子操作时,别的核不能访问该原子指令访问的地址。CPU需要做到但不限于以下要求:
- 原子指令准提交执行,避免该原子指令处于错误的分支上。
- 在原子指令执行期间,屏蔽中断,调试等需要可恢复的异步事件。
- 对于有cache的核,应当保证原子指令在执行期间,该数据应当一直唯一地存在于该核中(可以理解为MOSEI模型中的E态)。
- 如果需要总线来保证其原子指令的原子性,可以由lock传输等方式保证该地址不被其他master访问。但lock传输容易引发系统的死锁,AXI4协议中也取消了lock传输。因此在多核系统中,还是推荐使用cache将要访问地址缓存在本核的cache中。