AMRv8架构中对于A64,提供了如下的一些exclusive指令,用来支持exclusive操作。
那为什么,arm在加入exclusive指令呢?加入这个,主要是为了解决多核情况下,锁的竞争问题。
在软件层面,对于共享资源的访问,会设定一个锁,只有能拿到这个锁的程序,才能够访问共享资源,而没有拿到锁的程序,就不能访问该共享资源。
拿到锁的程序,在访问完毕后,要释放锁,这样,其他的程序,才可以竞争该锁,从而访问共享资源。
其伪代码如下:
//获取锁
get_lock:
ldr w1, [x0]
cmp w1, #1
b.ne get_lock
//上锁
mov w1, #1
str w1, [x0]
//开始访问共享资源
...
//结束访问共享资源
unlock:
mov w1, #0
str w1, [x0]
读取锁,如果为非0,表示当前锁被其他程序占有,因此获取锁失败,就一直要等别人释放锁。如果锁的值为0,表示锁没有被其他程序占有,那么将锁置为1,然后访问共享资源。访问完毕后,将锁置为0,释放锁。
这个获取锁的程序,在单核的时候,可以运行得比较良好,因为在同一个时刻,只能有一个程序在执行,不存在竞争的问题。
在使用操作系统后,这个程序也可能会出现潜在的问题,因为操作系统会调度应用程序。假设应用程序A在读取到锁状态后,值为0,表示当前锁没有被其他程序占有,正当自己要将锁锁上的时候,操作系统进行了调度,应用程序B执行。程序B也读取锁状态,发现值为0,表示当前锁没有被其他程序占有,然后将锁给锁上,访问共享资源。此时,操作系统又进行了调度,应用程序A执行,将锁给锁上,然后访问共享资源。那此时,就出现了2个应用程序,同时访问共享资源,锁的作用,被屏蔽了。
其执行过程,如下表所示:
为了解决这个问题,可以在获取锁的时候,屏蔽中断,那这个时候,操作系统就不能调度,也就避免上面出现的问题。伪代码如下所示:
get_lock:
//关中断
bl mask_int
ldr w1, [x0]
cmp w1, #1
b.ne get_lock
//上锁
mov w1, #1
str w1, [x0]
//开中断
bl open_int
//访问共享资源
...
unlock:
mov w1, #0
str w1, [x0]
这样,可以避免上述的问题,但是只要获取锁,就要关中断,开中断,这影响了系统响应中断的实时性。
在单核的时候,还可以通过关闭中断,来阻止操作系统调度。但是在多核的情况下,各个cpu是并行执行的,因此这个时候,就会出现,上面描述的2个程序,同时获取到锁的状态,而这个时候,通过禁止中断,是没有效果的。
为了解决多核情况下的锁竞争问题,arm引入了exclusive操作,并添加了相应的指令。
exclusive的操作的核心,就是会将锁,用一个状态机进行维护,该状态机有2种状态,open状态和exclusive状态。要想成功的对锁进行上锁,状态必须要从exclusive状态切换到open状态,其他状态,都是失败的。
为了支持exclusive操作,在A64,新增了LDXR和STXR指令。
STXR指令和普通的STR指令,不同的是,该指令有返回值,表示store exclusive是否成功。如果成功,ws为0,不成功,ws为1。
当有了exclusive操作指令后,之前的获取锁的程序,就变为以下:
//获取锁
get_lock:
ldxr w1, [x0]
cmp w1, #1
b.ne get_lock
//尝试上锁
mov w1, #1
stxr w2, w1, [x0]
cbnz w2, get_lock
//访问资源
...
unlock:
mov w1, #0
str w1, [x0]
通过stxr指令,尝试更改锁的状态,如果更改成功,那么w2为0,此时就表示自己获取到锁,然后就可以访问共享资源。如果更改失败,那么w2为1,表示此时其他程序获取到该锁,就不能访问共享资源,回到获取锁。
系列其他篇
原文首发于骏的世界博客
作者:卢骏.
更多Arm技术相关的文章请关注Arm技术博客极术专栏,每日更新。