ARM平台下独占访问指令LDREX和STREX的原理与使用详解

时间:2021-09-23 12:33:54


英文原版指令介绍,可以看过下面中文介绍之后,回过头来看这部分。

ARM Synchronization Primitives Development Article

1.2.1. LDREX and STREX

The LDREX and STREX instructions split the operation of atomically updating memory into two separate steps. Together, they provide atomic updates in conjunction with exclusive monitors that track exclusive memory accesses, see Exclusive monitors. Load-Exclusive and Store-Exclusive must only access memory regions marked as Normal.

LDREX

The LDREX instruction loads a word from memory, initializing the state of the exclusive monitor(s) to track the synchronization operation. For example, LDREX
R1, [R0]
 performs a Load-Exclusive from the address in R0, places the value into R1 and updates the exclusive monitor(s).

STREX

The STREX instruction performs a conditional store of a word to memory. If the exclusive monitor(s) permit the store, the operation updates the memory location and returns the value 0 in the destination register, indicating that the operation succeeded. If the exclusive monitor(s) do not permit the store, the operation does not update the memory location and returns the value 1 in the destination register. This makes it possible to implement conditional execution paths based on the success or failure of the memory operation. For example, STREX
R2, R1, [R0]
 performs a Store-Exclusiveoperation to the address in R0, conditionally storing the value from R1 and indicating success or failure in R2.

Alternative exclusive access sizes

The ARMv6K architecture introduced byte, halfword and doubleword variants of LDREX and STREX:

  • LDREXB and STREXB

  • LDREXH and STREXH

  • LDREXD and STREXD.

The ARMv7 architecture added these to the Thumb instruction set in the A and R profiles. ARMv7-M supports the byte and halfword but not the doubleword variants. ARMv6-M does not support exclusive accesses.

The architecture requires that each Load-Exclusive instruction must be used only with the corresponding Store-Exclusive instruction, for example LDREXB must only be used with STREXB.



中文转载自http://blog.csdn.net/roland_sun/article/details/47670099


为了实现线程间同步,一般都要在执行关键代码段之前加互斥(Mutex)锁,且在执行完关键代码段之后解锁。为了实现所谓的互斥锁的概念,一般都需要所在平台提供支持。

本文主要用来说明ARM平台上特有的独占访问指令LDREX和STREX的工作原理,以及如何使用。而它们也是ARM平台上,实现互斥锁等线程同步工具的基础。

我们先来看看LDREX和STREX两条指令的语义。其实LDREX和STREX指令,是将单纯的更新内存的原子操作分成了两个独立的步骤。

1)LDREX用来读取内存中的值,并标记对该段内存的独占访问:

[plain] view plain copy
  1. LDREX Rx, [Ry]  

上面的指令意味着,读取寄存器Ry指向的4字节内存值,将其保存到Rx寄存器中,同时标记对Ry指向内存区域的独占访问。

如果执行LDREX指令的时候发现已经被标记为独占访问了,并不会对指令的执行产生影响。

2)而STREX在更新内存数值时,会检查该段内存是否已经被标记为独占访问,并以此来决定是否更新内存中的值:

[plain] view plain copy
  1. STREX Rx, Ry, [Rz]  

如果执行这条指令的时候发现已经被标记为独占访问了,则将寄存器Ry中的值更新到寄存器Rz指向的内存,并将寄存器Rx设置成0。指令执行成功后,会将独占访问标记位清除。

而如果执行这条指令的时候发现没有设置独占标记,则不会更新内存,且将寄存器Rx的值设置成1。

一旦某条STREX指令执行成功后,以后再对同一段内存尝试使用STREX指令更新的时候,会发现独占标记已经被清空了,就不能再更新了,从而实现独占访问的机制。

大致的流程就是这样,但是ARM内部为了实现这个功能,还有不少复杂的情况要处理。

在ARM系统中,内存有两种不同且对立的属性,即共享(Shareable)和非共享(Non-shareable)。共享意味着该段内存可以被系统中不同处理器访问到,这些处理器可以是同构的也可以是异构的。而非共享,则相反,意味着该段内存只能被系统中的一个处理器所访问到,对别的处理器来说不可见。

为了实现独占访问,ARM系统中还特别提供了所谓独占监视器(Exclusive Monitor)的东西,其结构大致如下:

ARM平台下独占访问指令LDREX和STREX的原理与使用详解

可以看出来,一共有两种类型的独占监视器。每一个处理器内部都有一个本地监视器(Local Monitor),且在整个系统范围内还有一个全局监视器(Global Monitor)。

如果要对非共享内存区中的值进行独占访问,只需要涉及本处理器内部的本地监视器就可以了;而如果要对共享内存区中的内存进行独占访问,除了要涉及到本处理器内部的本地监视器外,由于该内存区域可以被系统中所有处理器访问到,因此还必须要由全局监视器来协调。

对于本地监视器来说,它只标记了本处理器对某段内存的独占访问,在调用LDREX指令时设置独占访问标志,在调用STREX指令时清除独占访问标志。

而对于全局监视器来说,它可以标记每个处理器对某段内存的独占访问。也就是说,当一个处理器调用LDREX访问某段共享内存时,全局监视器只会设置针对该处理器的独占访问标记,不会影响到其它的处理器。当在以下两种情况下,会清除某个处理器的独占访问标记:

1)当该处理器调用LDREX指令,申请独占访问另一段内存时;

2)当别的处理器成功更新了该段独占访问内存值时。

对于第二种情况,也就是说,当独占内存访问内存的值在任何情况下,被任何一个处理器更改过之后,所有申请独占该段内存的处理器的独占标记都会被清空。

另外,更新内存的操作不一定非要是STREX指令,任何其它存储指令都可以。但如果不是STREX的话,则没法保证独占访问性。

现在的处理器基本上都是多核的,一个芯片上集成了多个处理器。而且对于一般的操作系统,系统内存基本上都被设置上了共享属性,也就是说对系统中所有处理器可见。因此,我们这里主要分析多核系统中对共享内存的独占访问的情况。

为了更加清楚的说明,我们可以举一个例子。假设系统中有两个处理器内核,而一个程序由三个线程组成,其中两个线程被分配到了第一个处理器上,另外一个线程被分配到了第二个处理器上。且他们的执行序列如下:

ARM平台下独占访问指令LDREX和STREX的原理与使用详解

大致经历的步骤如下:

1)CPU2上的线程3最早执行LDREX,锁定某段共享内存区域。它会相应更新本地监视器和全局监视器。

2)然后,CPU1上的线程1执行LDREX,它也会更新本地监视器和全局监视器。这时在全局监视器上,CPU1和CPU2都对该段内存做了独占标记。

3)接着,CPU1上的线程2执行LDREX指令,它会发现本处理器的本地监视器对该段内存有了独占标记,同时全局监视器上CPU1也对该段内存做了独占标记,但这并不会影响这条指令的操作。

4)再下来,CPU1上的线程1最先执行了STREX指令,尝试更新该段内存的值。它会发现本地监视器对该段内存是有独占标记的,而全局监视器上CPU1也有该段内存的独占标记,则更新内存值成功。同时,清除本地监视器对该段内存的独占标记,还有全局监视器所有处理器对该段内存的独占标记。

5)下面,CPU2上的线程3执行STREX指令,也想更新该段内存值。它会发现本地监视器拥有对该段内存的独占标记,但是在全局监视器上CPU1没有了该段内存的独占标记(前面一步清空了),则更新不成功。

6)最后,CPU1上的线程2执行STREX指令,试着更新该段内存值。它会发现本地监视器已经没有了对该段内存的独占标记(第4步清除了),则直接更新失败,不需要再查全局监视器了。

所以,可以看出来,这套机制的精髓就是,无论有多少个处理器,有多少个地方会申请对同一个内存段进行操作,保证只有最早的更新可以成功,这之后的更新都会失败。失败了就证明对该段内存有访问冲突了。实际的使用中,可以重新用LDREX读取该段内存中保存的最新值,再处理一次,再尝试保存,直到成功为止。

还有一点需要说明,LDREX和STREX是对内存中的一个字(Word,32 bit)进行独占访问的指令。如果想独占访问的内存区域不是一个字,还有其它的指令:

1)LDREXB和STREXB:对内存中的一个字节(Byte,8 bit)进行独占访问;

2)LDREXH和STREXH:中的一个半字(Half Word,16 bit)进行独占访问;

3)LDREXD和STREXD:中的一个双字(Double Word,64 bit)进行独占访问。

它们必须配对使用,不能混用。