SWP和SWPB是ARM指令集中对存储单元的原子操作,即对存储单元的一次读和一次不可被分割。SWP和SWPB分别完成存储器和寄存器之间 一个字(32bit)和一个字节(8bit)的数据交换。
SWP指令主要是完成ARM体系架构处理器的同步操作,在Linux操作系统中实现信号量的操作。但是此指令在ARMv6架构后就没有采用了,而是通过扩展的LDREX和STREX实现。本片文章主要介绍SWP的功能,对于LDREX和STREX以后再介绍。
SWP的指令格式如下:
SWP {<cond>}{B} Rd, Rm, [Rn]
其中Rd 是目的寄存器,从存储器中读到的值存放于此寄存器中
Rm寄存器是操作数,会将此寄存器中的值存放于存储单元中
[Rn] 是寄存器间接寻址,Rn保存的是某个存储单元的地址
假设[Rn]中存放的是信号量,当某程序要修改信号量时,则会调用SWP指令完成对信号量的操作,即对这个存储单元的读和写是一个原子操作,不会被打断,命令的执行过程如下图所示:
当多个程序要访问他们共享的资源时,我们必须要做好同步机制以保证数据的安全。通常,共享的资源可以是一段共享内存或者是外部设备,访问这些资源的可以使CPU、进程或者是线程。为了完成同步机制,会采用一个原子变量来保存资源的状态。例如下图所示,用一个二元信号量(0或者1)来实现共享资源的同步,当进程A 和 进程B都要访问信号量Semaphore。
对于A进程, 先访问到信号量Semaphore发现状态可用,应该马上会修改Semaphore的状态,告诉其他进程此资源正在被使用。但是可能由于时间片恰好用完,系统调度到进程B。 进程B访问到信号量时发现状态也可用,于是 修改Semaphore告诉其他进程此资源正在被使用,等到系统再次调度到进程A时,进程A却不知道进程B已修改了Semaphore并且使用了公共资源,于是接着上次未完成的任务, 开始修改Semaphore并且开始使用公共资源。因此,遇到这种情况的话,信号量形同虚设并没有起到同步的作用。所以如果使用SWP指令,通过上面的介绍,SWP指令时完成对存储单元的一次读和写的原子操作,就可以避免这样的情况。
下面的汇编代码是通过SWP实现互斥的例子
EXPORT lock_mutex_swp
lock_mutex_swp PROC
LDR r2, =locked
SWP r1, r2, [r0] ; Swap R2 with location [R0], [R0] value placed in R1
CMP r1, r2 ; Check if memory value was ‘locked’
BEQ lock_mutex_swp ; If so, retry immediately
BX lr ; If not, lock successful, return
ENDP
EXPORT unlock_mutex_swp
unlock_mutex_swp
LDR r1, =unlocked
STR r1, [r0] ; Write value ‘unlocked’ to location [R0]
BX lr
ENDP
当然,除了上面的情况,还可能由于中断的产生导致读和写的操作被打断。在一些任务比较简单的系统中,可以在关键的代码中利用禁止中断的方式来保证对数据操作的原子性,然而对于现在复杂的多任务操作系统,禁止中断的做法显然不是有效的解决方法。所以SWP通过特殊的访问方式,不需要禁止中断,但是这样也会延长中断的响应时间。随着处理器的快速发展,多核处理器已经显示出了强大的优势,同步的问题显得更加明显。如下所示,一个系统由一个Cortex-A8和Cortex-M4组成,他们都会访问一同一段存储空间。
SWP指令在这种模式下,就显得很尴尬了,如果依然采用原来的特殊访问模式,可能会大大降低多核处理的性能。所以从ARMv6架构以后,不再使用SWP指令实现同步的功能,而是增加了LDREX和STREX指令完成相关的操作。具体使用情况,会在LDREX和STREX的文章中详细说明。 |