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个应用程序,同时访问共享资源,锁的作用,被屏蔽了。
其执行过程,如下表所示:
程序A |
当前执行 |
程序B |
ldr w1,[x0] 获取锁状态为0 |
程序A |
|
程序B |
ldr w1,[x0] 获取锁状态为0 |
|
程序B |
str w1, [x0] 上锁,锁状态为1 |
|
程序B |
访问共享资源 |
|
b.ne get_lock 此时锁状态为1,但是A不知道 |
程序A |
|
str w1, [x0] 上锁 |
程序A |
|
访问共享资源 |
程序A |
为了解决这个问题,可以在获取锁的时候,屏蔽中断,那这个时候,操作系统就不能调度,也就避免上面出现的问题。伪代码如下所示:
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指令。
在A32和T32下,也加入LDREX和STREX指令来支持。
LDXR指令,将状态从open状态切换到exclusive状态,STXR指令,将状态从exclusive状态切换到open状态,这个就表示store exclusive操作成功。
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,表示此时其他程序获取到该锁,就不能访问共享资源,回到获取锁。