基于ARMv7架构的同步和信号量

时间:2021-07-15 19:41:56

在ARMv6之前的架构版本中,支持对共享存储器的同步是依赖于SWP和SWPB指令。这些是交换寄存器内容和存储器的读-锁-写操作,并且在页A8-432上的SWPSWPB中描述。这些指令支持基本的繁忙/空闲信号量机制,但不支持要求在读和写阶段之间在信号量上执行计算的机制。

 

ARMv6架构引入了一个新的机制来支持更缜密的对共享存储器的非阻塞同步,通过使用对多处理器系统做延展的同步原语。ARMv6提供了一对同步原语,LDREX和STREX。ARMv7对此模型做了如下扩展:

1、添加了同步原语的字节、半字和双字版本(译者注:在ARM架构中,一个字的位宽是32位,那么半字的位宽为16位,字节仍然是8位)。

2、添加了一条排外清除指令,CLREX。

3、对Thumb指令集添加了同步原语。

 

在ARMv7中,在ARM和Thumb指令集中提供的同步原语如下:

1、排外加载——LDREX、LDREXB、LDREXD、LDREXH

2、排外存储——STREX、STREXB、STREXD、STREXH

3、排外清除——CLREX

[注:本小节描述的同步原语的一个排外加载/排外存储对(译者注:pair)的操作的描述,使用比如LDREX和STREX指令。同样的描述应用于其它同步原语对:

1、LDREXB与STREXB一起使用

2、LDREXD与STREXD一起使用

3、LDREXH与STREXH一起使用

每条排外加载指令必须只能与相应的排外存储指令一起使用。

 

对一个排外加载/排外存储指令对的使用,访问一个非中止的存储器地址x的模型为:

1、排外加载指令从存储器地址x读一个值

2、相应的排外存储指令成功写回到存储器地址x,只有当没有其它观察者、进程、或线程已经执行了更早的对地址x的存储时。排外存储操作返回一个状态位,指示存储器写是否成功。

 

一条排外加载指令对一小块排外访问的存储器打上标签。被打标签块的大小是由实现定义的。见,打标签和被打标签存储块的大小,在A3-20页。对同一地址的一条排外存储指令清除该标签。

 

[注:在本小节中,术语处理器包括可以生成一个排外加载或排外存储的任一观察者。]

 

A.3.4.1 排外访问指令与非可共享存储区域

 

对于没有可共享属性的存储区域,排外访问指令依赖一个本地监视器,该监视器对相应处理器执行一次排外加载所针对的任一地址打上标签。通过同一处理器的任一非中止的企图使用一次存储排外来修改任一地址,都能保证清除该标签。

(译者注:每个处理器都有自己的一个本地监视器)

 

一次排外加载执行从存储器的一次加载,并且:

1、执行处理器对排外访问的物理存储器地址打上标签

2、执行处理器的本地监视器变迁到其排外访问状态

 

一次排外存储执行一次对存储器的带条件的存储,依赖于本地监视器的状态:

1、如果本地监视器在其排外访问状态

    (1)如果排外存储的地址与在监视器中由先前的排外加载所打标签的是同一地址,那么存储发生;否则的话由实现定义存储是否发生。

    (2)一个状态值被返回给寄存器:

            ——如果存储发生,状态值为0

            ——否则,状态值为1

    (3)执行处理器的本地监视器变迁到其开放访问状态

 

2、如果本地监视器在其开放访问状态

    (1)没有存储发生

    (2)一个状态值1被返回给一个寄存器

    (3)本地监视器仍然处于开放访问状态

存储排外指令定义了状态值被返回给哪个寄存器。

 

当一个处理器使用其它写,而不是排外存储时:

1、如果是对一个物理地址的写,该地址没有被其本地监视器覆盖,那么该写不会影响本地监视器的状态。

2、如果对一个物理地址的写被本地监视器覆盖到,写是否影响本地监视器的状态是由实现定义的。

 

如果本地监视器在其排外访问状态,并且处理器对任一不是它最后所执行的排外加载的地址执行一次排外存储,那么存储是否更新存储器是由实现定义的,但在所有情况下,本地监视器被复位到开放访问状态。这个机制:

1、被用于一个上下文切换,见上下文切换支持,在A3-21页

2、在所有其它情况下必须被对待为软件编程错误。

 

[注:对一个打过标签的物理地址的存储是否引起本地监视器中的一个标签被清除,如果那个存储是通过一个不是引发物理地址被打标签的观察者,是由实现定义的。]

 

图A3-2展示了本地监视器的状态机。表A3-6展示了图中所展示的每个操作的效果。

 

A3.4.2 排外访问指令与可共享存储区域

 

对于具有可共享属性的存储器区域,排外访问指令依赖于:

1、在系统中,每个处理器的一个本地监视器,对任一地址打上标签,处理器在该地址上执行一次排外加载。本地监视器以A.3.4.1小节所描述的那样操作,而对于可共享存储区域,任一存储排外通过全局监视器进行检查它是否正如上一小节所描述的那样做至少以下一件事:

——更新存储器

——返回一个0状态值

本地监视器可以忽略来自系统中其它处理器的排外访问。

 

2、为一个特定的处理器对一个地址打上标签,作为排外访问的一个全局监视器。此标签稍候被使用以判定对那个地址的一次排外存储通过本地监视器是否可能失败。此存储器位置的可共享域中的任何其它观察者对打标签的地址进行成功地写可以保证清除了那个标签。对于系统中的每个处理器,全局监视器:

——保持一单个被打标签的地址

——维护一个状态机

 

全局监视器可以要么驻留在一个处理器块中,要么可以作为在存储器接口上的辅助监视器存在。

【注:一个实现可以将全局和本地监视器的功能结合在一单个单元中。】

 

全局监视器的操作

 

来自可共享存储区域的排外加载执行从存储器的一次加载,并且使得访问的物理地址为请求的处理器作为排外访问被打上标签。这个访问也使得排外访问标签从由请求处理器先前已打标签的其它任一物理地址被移除。全局监视器为每个处理器仅支持一单个显著的对可共享存储器的排外访问。

 

排外存储对存储器执行一个带条件的存储:

 

1、存储保证是成功的,只有当所访问的物理地址为请求处理器作为排外访问而被打标签,并且为请求处理器的本地监视器和全局监视器状态机处于排外访问状态时。在这种情况下:

    ——一个0状态值被返回给一个寄存器以确认成功存储

    ——为请求处理器的全局监视器状态机的最终状态是由实现定义的

    ——如果被访问的地址在全局监视器状态机中对任何其它处理器作为排外访问而被打标签,那么那个状态机变迁为开放访问状态

 

2、如果没有地址为请求处理器作为排外访问而被打标签,存储不会成功:

    ——一个1状态值被返回给一个寄存器以指示存储失败

    ——全局监视器不受影响并为请求处理器仍然处于开放访问状态

 

3、如果一个不同的物理地址作为排外访问为请求处理器被打上标签,存储是否成功是由实现定义的:

    ——如果存储成功,那么一个状态值0被返回给一个寄存器;否则的话,一个状态值1被返回

    ——如果为处理器的全局监视器状态机在排外存储之前处于排外访问状态,状态机是否变迁到开放访问状态是由实现定义的。

 

存储排外指令定义了状态值被返回给的那个寄存器。

 

在一个共享存储器系统中,全局监视器为系统中的每个处理器实现了一个独立的状态机。通过处理器(n)用于访问可共享存储器的状态机可以响应于对它可见的所有可共享存储器。这意味着它响应于:

1、由相关联的处理器(n)所生成的访问

2、由在存储器位置(!n)的可共享域中的其它观察者所生成的访问

 

在一个共享存储器系统中,全局监视器为每个可以在系统中生成一次排外加载或一次排外存储的观察者实现了一个独立的状态机。

 

A3.4.3 打标签与被打标签的存储块的大小

 

正如对在页A3-15的表A3-6和页A3-19上的表A3-7的脚注中所陈述的那样,当一个排外加载指令被执行时,结果标签地址忽略存储器地址的最低有效位。

 

Tagged_address = Memory_address[31:a]

在这个赋值中的a值是实现定义的,在最小值3和最大值11之间。例如,在一个实现中a == 4,那么对地址0x000341B4一次成功的LDREX给出该地址的[31:4]位的一个标签值,此标签值为0x000341B。这意味着从0x000341B0到0x000341BF的存储器的四个字被打标签为排外访问。

被打标签的存储块的大小被称为排外保留粒度(Exclusives Reservation Granule。排外保留粒度在下列条件之间由实现定义:

1、两个字,在一个实现中,a == 3

2、512个字,在一个实现中,a == 11

 

 

在某些实现中,CTR(Cache类型寄存器)标识了排外保留粒度。

 

A3.4.4 上下文切换支持

 

在一次上下文切换后,软件必须确保本地监视器处于开放访问状态。这要求它:

1、要么执行一条CLREX指令

2、要么对为此目的分配的存储器地址执行一条哑STREX指令。

 

【注:

1、为此目的使用一条STREX是与ARMv6架构所实现的排外操作向后兼容。CLREX指令在ARMv6K中被引入

2、上下文切换并不是一个应用程序级别操作。然后,这条信息在这里引入是为了对排外操作的完全描述

 

在一次上下文切换之后的STREX或CLREX指令可能会引发后面的一次存储排外操作失败,要求一个加载⋯⋯存储序列被重新执行。为了最小化这发生的可能性,ARM建议存储排外指令尽可能地保持在靠近排外加载指令的地方。

 

A3.4.5 排外加载和排外存储用法限制

 

排外加载和排外存储指令一般都是作为一个对一起工作的,比如,一个LDREX/STREX对或LDREXB/STREXB对。正如A.3.4.4小节所描述的,ARM建议存储排外指令尽可能地保持在靠近排外加载指令的地方。为了支持这些功能的不同实现,软件必须支持这里所给出的注记和限制。

这些注记描述了对LDREX/STREX对的使用,但与其它的排外加载/排外存储对用法一样:

1、排外支持一单个为每个被执行的处理器线程的显著的排外访问。架构不要求通过作为IsExclusiveLocal()函数一部分的一个地址或大小检查来利用这个。如果一个STREX的目标地址与先前在同一执行线程中的LDREX不同,行为可能是不可预测的。作为一个结果,只有当LDREX/STREX对以相同的地址被执行时,才能依靠它们最终成功。在上下文切换或异常可能导致执行线程改变的地方,一条CLREX或一条哑STREX指令必须被执行以避免不想要的效果。用这种方法使用一条STREX是软件可以编程一条带有与先前所执行的LDREX不同地址的STREX仅有的场合。

2、一次显式地对存储器的存储会导致与其它处理器相关联的排外监视器的清除。从而在LDREX和STREX之间执行一次存储会导致一个活锁情况。作为一个结果,在一单个代码序列中,代码必须避免在一条LDREX和一条STREX之间放一个显式的存储。

3、如果两个STREX执行,中间没有一条LDREX介入,那么第二条STREX返回状态1。这意味着:

    ——每个STREX在一个所给的执行线程中必须有一个与其相关联的LDREX

    ——对于每条LDREX,不需要后面跟着一条STREX。

4、排外加载和排外存储指令的一个实现可以要求,在任一执行线程中,一次排外存储的作用大小和在此线程中所执行的先前的排外加载的作用大小相同。如果一次排外存储的作用大小与先前的排外加载的作用大小不同,那么行为是不可预测的。作为一个结果,只有当LDREX/STREX对具有相同尺寸时,才能依靠它们最终成功。在上下文切换或异常可能导致执行线程改变的地方,一条CLREX或一条哑STREX指令必须被执行以避免不想要的效果,正如A.3.4.4小节所描述的那样。在这种方式下使用STREX,只有在软件可以以一个先前所执行的排外加载指令的不同作用大小使用一条排外存储指令的场合下。

5、一个实现可以在LDREX和STREX之间清除一个排外监视器,而不需要任一应用相关的起因。比如,这可能会因为Cache换出而发生。为这么一个实现所写的代码必须避免在LDREX和STREX之间有任何显式的存储器访问或Cache维护操作。

6、实现可以从在一单个代码序列中保持LDREX和STREX操作紧靠而获利。这最小化了在LDREX和STREX之间排外监视器状态被清除的可能性。从而,ARM强烈建议在一单个代码序列中,在LDREX和STREX之间有128个字节的限制,从而获得最佳性能。

7、实现相关协议,或仅有一单个主机的实现可以为一所给的处理器联结全局和本地监视器。在页B2-35上的排外监视器操作中定义的实现定义或不可预测部分被提供以覆盖此行为。

8、架构设置了一个区域大小最大2048字节的限制可以被标记为排外。从而,对于性能缘由,软件把将以排外访问方式要被访问的对象分割为至少2048字节。这是一个性能指引,而不是功能要求。
9、LDREX和STREX操作必须只能在带有正常存储器属性的存储器上执行。

10、在监视器的状态上的数据中止异常的效果是不可预测的。ARM建议中止处理代码执行一条CLREX指令或是一条哑STREX指令以清除监视器状态的 。

11、如果正被一个LDREX/STREX对所访问的存储器的存储器属性在LDREX和STREX之间被改变,那么行为是不可预测的。

 

A3.4.6 信号量

 

交换(SWP)和交换字节(SWPB)指令必须小心使用以确保可观察到所期望的行为。以下有两个例子:

1、带有多个总线master的一个系统使用交换指令以实现在不同总线master之间控制交互的信号量

    在这种情况下,信号量必须被放置在一个非可被cache存储区域,那里,使用此机制,对任一写缓存发生在对所有总线master都公共的点。交换指令随后引发一次上锁的读-写总线事务。

2、运行在一个统一处理器上带有多个线程的一个系统,使用交换指令以实现控制线程交互的信号量

    在这种情况下,信号量可以被放置在一个可被cache的存储区域,并且一次加锁的总线事务可能或可能不发生。交换和交换字节指令在这个系统上可能有更好的性能比起在带有多个总线master的系统。

【注:从ARMv6开始,对交换和交换字节指令的使用是被废弃的。所有新的软件应该使用排外加载和排外存储同步原语。】

 

A3.4.7 同步原语和存储器次序模型

 

同步原语遵循由指令所访问的存储器类型的存储器次序模型。对于这个理由:

1、要求使用一个旋锁的可移植的代码必须通过执行一条DMB指令来包含一个数据存储器屏障(DMB)操作,在使用旋锁和利用旋锁的访问之间。

2、要释放一个旋锁的可移植代码必须在写以清除旋锁之前包含一个DMB指令。

这个要求应用于使用以下指令的代码:

1、排外加载/排外存储指令对,比如LDREX/STREX

2、废弃的同步原语,SWP/SWPB

 

A3.4.8 通过旋锁使用WFE和SEV指令

 

ARMv7和ARMv6K提供了等待事件和发送事件指令,WFE和SEV,它们可以辅助减少由于重复尝试获得一个旋锁而导致的电源消耗和总线争夺。这些指令可以在应用层上使用。但是要对它们做了什么有一个完整的理解要依赖于对异常的系统级理解。它们在B1-44页被描述。