鲲鹏开发重点-–扭转x86乾坤的挑战,ARM64内存模型

网友投稿 967 2022-05-29

鲲鹏开发重点1–扭转x86乾坤的挑战,ARM64内存模型

(老古、大何)

鲲鹏开发重点-–扭转x86乾坤的挑战,ARM64内存模型

一、前言:扭转x86乾坤的挑战

1、ARM:超越x86二选一思维

2、Apple:推出惊人速度的ARM笔记本电脑

3、鲲鹏:扭转x86乾坤的挑战

二、指令乱序执行:独特架构的先进特性

1、体系结构决定内存模型:X86强内存序,ARM64是弱内存序

2、X86强内存序: 写后读乱,读后读/写后写/读后写不乱

3、ARM64弱内存序: 精简指令集把访存指令和运算指令分开了

三、并行流水:10000米高空的观察

1、ARM64:多个并行流水部件,支持超标量

2、流水第一二阶段:串并转化、保乱皆有序

3、避免事故最关键设计:不是完美匝道口,关键在于驾驶员

4、安全距离是多少?如何预判?如何正确操作?

5、观察者PE/DMA:作为存放数据的内存位置,内部关系复杂

6、低级语言、高级语言:合作、边界

四、ARMv8内存模型:全面的定义

1、基本定义:Locations, Memory effects, and Observers

2、位置(Location)

3、内存影响(Memory effect):读/写/屏蔽

5、观察者(Observer):PE(Processing element)、代理

6、普通共享域(Comman Shareability Domain)

五、ARM64创新:排序及其可观察性

1、依赖约束、排序约束、APP序:OS和芯片协调边界

2、依赖:划分界限,寄存器R,数值V和指令I

3、基于位置的顺序和可观察性

4、排序关系

六、ARM64可控规则:系统中观察者之间的交互

1、使用共享变量来通信

2、所有内存完成规则的定义

3、外设

七、附-参考资源:ARM®  Architecture Reference Manual

ARM®  Architecture Reference Manual

八、侧记:像鲲鹏,去展翅高飞,共赢计算新时代

(图片来源于网络)

一、前言:扭转x86乾坤的挑战

1、ARM:超越x86二选一思维

ARM基础设施事业部高级副总裁兼总经理Chris Bergey称,ARM想改变行业对部署基础设施的思维,每一个创新者都不应该被要求在性能与能耗之间进行抉择,ARM平台提供了两者兼得的最佳解决方案。

法国芯片公司SiPearl和韩国电子通信研究所( ETRI)的高性能计算(SoC)均可展现这些设计元素的优势,这被ARM看作是高性能计算的发展方向

ARM系列芯片之一Neoverse N2在安全性、能耗以及性能方面都有全面提升,并能为用户减少TCO的每瓦性能表现。相比于Neoverse N1,N2在保持相同水平的功率和面积效率的基础上,单线程性能提升了40%。

ARM芯片具备良好的可扩展性,可以横跨从高吞吐量计算到功率与尺寸受限的边缘和5G应用场景,并在这些应用中带来优于Neoverse N1的表现。例如,在云端上提升1.3倍的NGINX,在5G边缘应用上提升1.2倍的DPDK数据包处理。

2、Apple:推出惊人速度的ARM笔记本电脑

今年还有一个里程碑式的事件,就是Apple以惊人的速度推出了基于ARM的笔记本电脑。Apple从来不是一个拘泥于现状的公司,在其历史上,就曾经在短时间内抛弃了摩托罗拉转向Intel。但当时的情况是Powerpc日渐式微,而Intel明显的处于快速上升期。如果告诉一个人说现在的iMac装不了Win10了,有很大一批人可能都会犹豫。因此Apple当下的选择好像是冒了很大的风险的。

Apple与之不同之处在哪里呢,是有自己独有的优势,就是软硬件一体的高度控制能力,这也是它敢于叫板Intel的底气。由于IOS在庞大的电脑市场占比较小,目前还不能对X86构成什么威胁。但这次改变却透露了X86未来可能的隐患,这就是基于Apple强有力的执行力和软硬件整合力的推动下,在Apple体系中成功建立起移动、桌面和云的一体化,从而证明了ARM架构在泛云支持上的可能性。

这个杀伤力就很大。

这表明现在桌面和移动的几乎所有应用都可以迁移到ARM上,同时对应的云端应用也可以用ARM支持,这样相当于直接切入了X86全部的生态圈。

3、鲲鹏:扭转x86乾坤的挑战

因为X86及其CISC架构生态的封闭性,中国市场对未来处理器的选择,将是更开放、更模块化的RISC架构。

鲲鹏处理器就是符合这个潮流的创新产品和生态,将直面一系列挑战,和Apple一样赢得这场挑战,来扭转X86的封闭性的乾坤,创造出中国的处理器新生态。

因为鲲鹏采用ARM64架构,其RISC架构和x86的CISC架构有很大的不同,原来在x86处理器上运行健壮的程序,在ARM64上不稳定,开发人员随口说,ARM64处理器有什么问题吗?这个帽子扣的有些大,后来发现是自己代码搞错了,有个代码分支没有加锁,这部分代码还是新写的,并没有在x86上运行多久,只是问题没有暴露而已。这是一个乌龙,让ARM64处理器背了锅。

其实,应用程序很少看到ARM64处理器的架构细节,也很少能碰到处理器的问题。中间隔着操作系统和编译器,处理器架构相关的工作都已经适配了,跨体系结构移植代码的难度也没有预想的那么大。不能因为对ARM64不了解,就谈虎色变,问题还是问题,还是需要从问题本身出发,从现象到本质。

鲲鹏处理器基于ARM64架构,追求极致能效比,与x86处理器相比的优势更在于多核和乱序执行,在处理程序并行上也会遇到一些挑战。

全新构建一个生态本身,不是一帆风顺的,鲲鹏产业必须举着”补洞”的大旗,再困难也要往前冲,有些困难的挑战却不仅仅是锦上添花的事情,在ARM64上干成了,就不怕和x86比了。

本文主要讲一讲扭转X86封闭性的乾坤重大挑战之一:ARM64的指令乱序。

二、指令乱序执行:独特架构的先进特性

ARM以及芯片架构师都有共识,ARM64就是这样的,是允许指令乱序执行的,这是出于性能的考虑,这是架构特性,不是漏洞。网传苹果最新的M1处理器的乱序窗口达到600条。

但是,指令乱序的影响却给系统可靠性带来了风险,驱动模块,基础软件和应用软件都要做排查、设计优化。所以,这是鲲鹏生态各产品线切换到ARM64平台上的软件也要谨慎面对。

1、体系结构决定内存模型:X86强内存序,ARM64是弱内存序

为什么会有指令乱序,这确实是体系结构的内存模型决定的,并不是有人在忽悠我们。

2、X86强内存序: 写后读乱,读后读/写后写/读后写不乱

X86是强内存序模型,复杂指令集允许其运算指令本身支持内存访问,并允许非依赖的写后读指令乱序,其它非依赖的读后读,写后写,和读后写的指令都不会发生乱序。

3、ARM64弱内存序: 精简指令集把访存指令和运算指令分开了

ARM64是弱内存序模型,因为精简指令集把访存指令和运算指令分开了,为了性能,必须允许几乎所有的指令乱序,但前提是必须遵守依赖约束和排序约束,不影响程序的逻辑和正确性。

所以,ARM64上没有依赖关系的读后读,写后写,读后写,和写后读都是可以乱序执行的。

这听起来很匪夷所思,那程序代码还能保证正确吗? 搞不定的问题都可以扣上CPU乱序的帽子吗?

这其实呢,在指令乱序前还得知道编译乱序,ARM64处理器并不是直接面对程序代码的,而是面对机器代码,或者说汇编指令。

由高级语言编写的程序,如C语言,首先通过编译器的编译转换为汇编指令,编译器会根据ARM64处理器的流水线特点,合理安排汇编指令的顺序,目的是最大限度的发挥CPU流水的并行能力。这也没有什么可怕的,编译器一定会在保证程序正确性的情况下,合理调整汇编指令的顺序。

(图片来源于网络)

特别的,在源代码中内联汇编指令时,这个外部额外插入的汇编指令,要告诉编译器不要优化指令顺序,需要使用“__asm__ __volatile__("": : :"memory") ”的编译提示。编译阶段的乱序不是我们关注的重点,而且这部分极少出错。我们仍然回到CPU的指令乱序特性上来。

三、并行流水:10000米高空的观察

生活中,特别是高速路的道路设计和CPU的流水设计有一点相似之处。高速路设计了并行的行车道和超车道,是允许超车的,这样可以保证高速路的高效通行和最大车流量。

经常开车的同学,肯定会为高速路上在行车道或者超车道上龟速行车的小汽车大为光火,也许你超车时还不忘瞟一眼,确认一下是不是女司机,其实多数是新手男司机才这么霸道。

1、ARM64:多个并行流水部件,支持超标量

ARM64设计了多个并行的流水部件,支持超标量,可以一个指令周期分发多条指令,实现最大程度的指令并行。

2、流水第一二阶段:串并转化、保乱皆有序

流水的第一阶段包括指令提取,译码和分发,这部分是保序的;

一旦到流水的第二阶段,执行指令流“串并转化”后,指令一旦分发到不同的并行流水部件中,它们的执行顺序就是乱序的了。有依赖的指令会放到同一个流水部件,先进先出方式排队,防止乱序执行。

关于流水线的设计是如何保证指令并行的正确性,详细的微架构分析,cache一致性原理等以后解释,此处直接跳过。

(图片来源于网络)

高速匝道口事故:A变道是否影响B正常行驶

高速路上经常发生事故的地方是匝道口。当左侧超车道上的车辆A往右侧匝道横向慢速变道,往往导致后方行车道上正常行驶的车辆B刹车不及,导致追尾碰撞。当然,如果此段高速路上只有车辆A,或者后面的车B根本看不到踪影,或者车辆A占用的车道和车辆B行驶的道路不相关,那么车辆A怎么慢吞吞的变道都没有关系。所以,发生事故的条件之一就是车辆A的变道是否影响到车辆B正常行驶。

(图片来源于网络)

3、避免事故最关键设计:不是完美匝道口,关键在于驾驶员

上图仅是一个匝道口示意,这样的设计是否足够完美,我们不是专业人员,不做判断,仅作参考。避免事故最关键的不是匝道口的完美设计,关键在于驾驶员。

4、安全距离是多少?如何预判?如何正确操作?

避险之道包括后车与前车保持足够安全的行车距离,后车对形势的正确预判,紧急情况下的正确操作。而安全距离是多少?如何预判?如何正确操作?这些都是车辆驾驶员的技能和安全意识所决定,所以,坐新手的车会让你提心吊胆,坐老司机的车就是放心。千万不要用发生在自己身上的交通事故来积累驾驶经验,这样的代价太大,网上有高速安全行驶教学视频,这里不再过多描述。

5、观察者PE/DMA:作为存放数据的内存位置,内部关系复杂

ARM64的指令乱序造成的软件问题与高速路上的匝道口事故有一点相似的地方,也需要了解交通参与者,交通设施,规则和应急措施。在一个CPU系统中,作为观察者的PE或者DMA等模块,作为存放数据的内存位置,他们之间的关系并不像C语言描述的那么简单,内部关系复杂。

ARM公司没有提供视频讲解,但是有架构参考手册,ARM® Architecture Reference Manual (ARMv8, for ARMv8-A architecture profile)的B2.3章节详细描述了内存模型,把指令乱序的相关细节作了比较细致的抽象描述。

6、低级语言、高级语言:合作、边界

ARM手册中定义了一些陌生的低级语言词汇,主要还是围绕处理核,寄存器,内存,并发,依赖,指令序,执行序,观察序等等。高级语言定义了逻辑关系,编译器定义了数据依赖,控制依赖和地址依赖等依赖关系,ARMv8架构定义了内存模型。逻辑依赖主要与应用程序有关,指令顺序主要与编译器有关,指令乱序和内存墙主要与CPU有关,最终都是系统相关。

手册中的描述有好多新词汇,并不通用,用中文翻译找不到类似“波音”这类比英文还美妙的词汇,就仍然保持了原来的英文单词。

ARMv8手册中的内存模型章节真不是一段“好剧本”,有些晦涩,“虽不明,但觉厉”。google翻译也不给力,难免错误,更不能搞的通俗易懂,只希望码农需要时,有这个“粗加工”的中文参考。如果觉得本文描述很绕,请回到下面这张图片,也许会感觉舒适一点。

(图片来源于网络)

此文档即不是编程规范,也不是编程军规,并不需要所有码农都了解,但是,驱动软件相关,无锁队列相关和并发软件相关的码农需要稍加留意。

四、ARMv8内存模型:全面的定义

本节描述ARMv8内存模型中有关顺序和观察的概念。包含以下子章节:

.位置

.顺序和可观察性

.顺序约束

.完成顺序和端点顺序

.内存屏障

.限定顺序域

有关内存访问端点顺序的更多信息,参考Reordering on page B2-129。

在ARMv8内存模型中,共享内存属性指硬件必须在一系列观察者之间保证内存一致性。参考Memory types and attributes on page B2-122.

ARMv8架构定义了另外的内存属性和相关的行为,这些定义在手册中的系统级内存模型和虚拟内存系统架构章节中。

Chapter D4 The AArch64 System Level Memory Model

Chapter D5 The AArch64 Virtual Memory System Architecture.

参考

Mismatched memory attributes on page B2-132.

这里贴一张CPU系统的框图,这不是最新的CPU描述,仅做参考:

(图片来源于网络)

1、基本定义:Locations, Memory effects, and Observers

ARMv8内存模型提供了一系列定义,用来约束内存访问的顺序。ARMv8内存模型定义:

. 观察序,不同观察者所看到的内存访问顺序

. 抵达序,内存访问抵达一个端点的顺序

. 机制,控制观察序和抵达序的机制

Locations, Memory effects, and Observers

2、位置(Location):

ARMv8内存模型对内存中的位置(Location)作如下定义:

一个位置指内存中的一个字节。

作为指令执行的一部分,指令可能对内存产生影响。系统中的观察者可以在内存位置上,看到那条指令产生的影响。ARMv8内存模型提供了如下关于内存影响和观察者的定义:

3、内存影响(Memory effect):读/写/屏蔽

一条指令产生的内存影响是指这条指令生成的读取影响,写入影响,或者屏障影响。针对内存访问指令:

. 当一个内存位置被指令读取时,将产生一个读取影响。

. 当一个内存位置被指令写入时,将产生一个写入影响。

有时,一条指令既能产生内存读取影响,也能产生内存写入影响。

*抒己见:比如原子加,或者比较交换CAS指令,会对内存产生读取和写入的双重影响。

当且仅当指令I1按指令序出现在指令I2之前,指令I1对内存产生的影响才按指令序,出现在指令I2对内存产生的影响之前。

ARMv8内存模型描述的目标,即所有的读取影响和写入影响,都是指普通共享域(Common Shareability Domain)。如果本节描述的读取,写入和内存屏障没有限定,那么都是指此对应的内存影响。

*抒己见:文中没有把“memory effect”翻译成内存效果或者其它词语,也许有比内存影响更好的词语。可能有些程序员(同事)认为对内存读不会对内存有任何影响,但是对CPU内部的cache影响还是有的,需要考虑cache一致性,读取内存数据时,也许有其它core在cache中保留了内存脏数据。

5、观察者(Observer):PE(Processing element)、代理

一个观察者即可以是一个处理核PE(Processing element),也可以是其它产生内存读写访问的代理。

6、普通共享域(Comman Shareability Domain)

程序的普通共享域,就是程序产生的内存访问影响能被域内所有观察者看的最小共享域。

五、ARM64创新:排序及其可观察性

1、依赖约束、排序约束、APP序:OS和芯片协调边界

ARMv8内存模型允许重新排序内存访问,即乱序执行。本节定义内存访问重排序有哪些限制:

. 依赖约束,这是执行单元上确立指令间顺序的重要约束。

. 排序约束,这是在一个内存位置上确立访问顺序的重要约束。

*抒己见:ARMv8内存模型将遵守依赖约束和排序约束,在这些约束的限制下,体系结构会保证这些指令的顺序不会在执行阶段重排。

有一种情况,对共享全局变量有逻辑依赖的程序,其编译后的关键指令对CPU来讲,并不存在CPU规定的指令依赖关系,这种逻辑依赖关系是程序员知晓的,CPU和编译器无法理解。要这些逻辑相关指令在ARM64上按指令序执行,就需要手工在应用程序中增加相应的内存屏障指令,所以,“自己的狗粮自己吃”,程序员需要重视单核中乱序执行对多核间全局变量访问的影响,多数情况抽象为,生产端的生产过程和完成标记的设置,消费端的完成标记的读取和消费过程。生成端的生成过程和完成标记的设置是可能乱序的,这是应用程序规定的角色,CPU是不知道这个逻辑关系的,消费端的逻辑也是CPU不知道的。所以应用程序和OS希望CPU理解类似这些逻辑也是困难的。CPU的乱序执行本质上是指令流的动态调度,在维护数据依赖和控制依赖的情况下尽可能快的执行每条指令。

“Program order”直译为程序顺序,本文按照指令序翻译。

2、依赖:划分界限,寄存器R,数值V和指令I

ARMv8内存模型定义了以下指令间的依赖:

*抒己见:ARMv8手册将“依赖”区分的非常细,我理解其目的就是划清界限,“我的地盘我做主”,CPU仅仅实现了这些相关的保序特性,依赖以外的乱序都是“未定义”的。以下描述中将出现几个字母,比如R1,V1,V2,V3,W2,I2等,这些都是寄存器R,数值V和指令I的代称。

本文对各种依赖的描述和手册中的实际顺序不一致。我们经常遇到的数据依赖,不管是寄存器访问指令还是内存访问指令,有三种类型,RAW(写后读),WAR(读后写)和WAW(写后写),其中RAW是真依赖,其它是假依赖,RAR没有依赖。

寄存器依赖

一个执行单元PE里,第一个数据V1与第二个数据V2存在寄存器依赖,当且仅当:

. 保存V1数据的寄存器用来计算V2数据 。

. 数据V1与数据V3有寄存器依赖,数据V3与数据V2也有一个寄存器依赖。

举例1:

ldr x1, [x29, #144]

add x2, x1, #0xc8

str x2, [x29, #168]

举例2:

ldr x1, [x29, #144]

ldr x3, [x29, #168]

add x3, x3, x1

str x3, [x29, #168]

add x2,x3, #0x10

str x2, [x29, #170]

*抒己见:保存V1数据的寄存器不包括零值寄存器XZR或者WZR。寄存器依赖主要描述前一个值是计算后一个值的基础,这是一种基础的依赖关系,并没有限定后值的用途。是值与值之间的依赖,通过寄存器搭桥而已。寄存器依赖有两种情况,一种是直接依赖,另外一种是间接依赖。寄存器依赖,必须从汇编语言层级来识别。描述寄存器依赖关系时没有提及指令,只提到了数据。

数据依赖

读操作R1与后续写操作W2存在数据依赖,当且仅当,读操作R1返回的数据与后续写操作W2写入的数据有一个寄存器依赖。

举例如下:

ldr x1, [x29, #144]     /*从堆栈中读取数据到寄存器x1中*/

add x2, x1, #0xc8       /*x1寄存器加0xc8后赋值给x2寄存器*/

str x2, [x29, #168]     /*把x2寄存器的值存放到堆栈中*/

*抒己见:这个例子和寄存器依赖的例子没有差别。 读后写被定义为数据依赖,即后面要写入内存的数据与前面从内存读出的数据相关。感觉数据依赖和寄存器依赖在相互解释。数据依赖和寄存器依赖的区别在于,寄存器依赖是后值依赖前值,而没有定义后值的用途,如果后值作为数据写回内存就是数据依赖,如果后值作为分支判断条件,就是控制器依赖,如果后值作为地址使用,就是地址依赖。而数据依赖只是寄存器依赖的一种形式而已。

有时,编译器会做指令优化,数据不存回堆栈或者内存中,暂时存放在寄存器中,所以数据依赖和实例展示的情况不一样。

控制依赖

读操作R1与后续指令I2存在控制依赖,当且仅当以下条件成立:

.当读操作R1读取的数据,与条件分支的判断值有一个寄存器依赖,同时,I2指令被包含在一个条件分支里。

.当读操作R1读取的数据,与决定指令I3上的同步异常的数据值有一个寄存器依赖,同时,指令I2按指令序排在指令I3后面。

*抒己见:第一个条件好理解,即R1读取的数据直接控制了I2指令的执行,但是需要注意条件分支的理解,如果是通过子函数调用,即“bl”分支过来执行I2指令,这个情况应该不是控制依赖,因为这是一个必然走到的分支。

第二个条件有些隐晦,引入了同步异常,描述为被动的控制依赖,我理解R1的值可能触发I3指令异常,也可能不触发I3指令异常,I3后面的I2指令被包含在一个隐藏的条件分支中。这个控制依赖不好理解,实际应用中还没有遇到过。

寄存器数据依赖

一个执行单元PE里,第一个数据V1与第二个数据V2存在寄存器数据依赖,当且仅当如下条件成立:

. 保存V1数据的寄存器用来计算V2数据,但是V1和V2之间的计算不包含以下情况:

_ 条件分支的条件由V1来决定

_ 条件选择,搬移或者比较的条件由V1决定

. 数据V1与数据V3有寄存器数据依赖,数据V3与数据V2也有寄存器数据依赖关系。

*抒己见:保存V1数据的寄存器不包括零值寄存器XZR或者WZR。前面有寄存器依赖,再定义一个寄存器数据依赖的用意是什么?我理解寄存器数据依赖排除了寄存器依赖作为控制依赖的情况,这为投机读开了绿灯,因为投机读是可以不受控制依赖限制的。

地址依赖

读操作R1与后续的读操作R2存在地址依赖,当且仅当R1读操作返回的数据与R2使用的地址有寄存器数据依赖关系。

读操作R1到后续的写操作W2存在地址依赖,当且仅当R1读操作返回的数据与W2使用的地址有寄存器依赖关系。

*抒己见: 如果后续动作是读,地址依赖仅仅包括寄存器数据依赖,允许地址受控制依赖,即允许投机读发生。如果后续动作是写,地址依赖需要包括寄存器依赖中的控制依赖,即不能发生投机写,因为当分支确定后,如果预测错误,写操作的影响是不能撤销的。

3、基于位置的顺序和可观察性

一个内存位置上的内存影响有下列关系描述:

Reads-from

“读出”关系,把写入的数据读取出来。针对同一个内存位置,一个读取操作配对一个写入操作,W1写入的数据被R2读出来。

------Note------

Reads-from关系表达了读取由写入来满足,然后返回写入的数据

————————

*抒己见:由于CPU内部的架构实现差异,数据可能并没有写入到内存中,而是在内部的cache中,或者本core要读的数据在另外一个core的cache中。但是reads-from关系仍然按照对实际CPU外部内存的影响来理解。

Coherence order

“一致性顺序”关系,针对程序中的每个内存位置,规定了所有一致性观察者的所有写入的总顺序,开始于一个初始值的抽象写操作。

------Note------

一个内存位置的Coherence order关系,表示写入这个位置时到达内存时的顺序

————————

*抒己见:这里描述的一致性顺序,只和写操作相关,联想到CPU内部的cache,在MESI协议规定下,有时一个观察者把数据写脏后,并不立刻写到内存,只更新本core的cache,在其它观察者需要再次写本位置时,才会发生数据同步。所以,这里隐含的描述了MESI协议的一部分定义。

Coherence-after

针对同一个内存位置,写入W2和另外一个写入W1,是Coherence-after关系,当且仅当,W2在W1后面,是Coherence order关系。

针对同一个内存位置,写入W2和另外一个读取R1,是Coherence-after关系,当且仅当,R1 Reads-from读出W3的值,W2与W3是Coherence-after关系。

*抒己见:这里描述的一致性顺序,无法翻译成中文,保留英文描述“Coherence-after”。一前一后的一致性写操作定义为“Coherence-after”,一前一后的一致性写,读出后一个写的值是“Coherence-after”。虽然比较难懂,重点就是每一步操作都是一致性可观察的。

Overlapping accesses

“重叠访问”关系。两个内存影响重叠,当且仅当它们访问相同的位置时。两条指令重叠,当且仅当它们生成的一个或多个内存影响重叠时。

*抒己见:有时编译器就会优化多余的重复访问,添加volatile定义时才会避免优化,处理器对重叠访问的处理细节被隐藏了,除非芯片设计人员告诉细节。多个PE的重叠访问和一个PE的重叠访问,在处理器中实现会不一致。比如MESI协议中,一个PE读到本cache的脏数据是没有状态变化和内存影响的。

Observed-by

一个观察者的读取或者写入RW1被另外一个观察者的写入W2观察到,当且仅当W2与RW2是“coherence-after”关系。

一个观察者的写入W1被另外一个观察者的读取R2观察到,当且仅当R2与W1是“Reads-from”读出关系。

DMB FULL

DMB FULL是包含LD或ST的DMB全集。

如果本节涉及的DMB没有任何限定,那么它指的是所有类型的DMB。除非定义了特定的可共享域,否则DMB适用于通用共享域。

适用于DMB的所有属性也适用于相应的DSB指令

4、排序关系

除了单个内存位置的排序关系之外,ARMv8内存模型还提供了发生在多个内存位置的内存影响的排序关系。这些关系描述如下:

Dependency-ordered-before

一个观察者生成的一个内存读取影响,和这个观察者生成另外一个内存影响之间的外部可见的顺序,是由依赖关系创建的。

一个内存读取R1依赖排序靠前Dependency-ordered-before来自同一观察者的内存读写RW2,当且仅当 R1在指令序上排在RW2之前,而且满足下列任一条件:

. R1与RW2有地址依赖或者数据依赖关系

. RW2是一个写入W2,在R1与W2之间有控制依赖

. RW2是一个读取R2,指令序上排在指令I3后面,I3将产生上下文同步事件,在R1与I3之间有控制依赖。

. RW2是一个写入W2,指令序上排在一个读或者写RW3后面,R1与RW3之间有地址依赖。

. RW2是一个写入W2,与写入W3是Coherence-after关系,R1与W3之间有控制依赖或者数据依赖。

. RW2是一个读取R2,与写入W3是读出关系,在R1与W3之间有一个控制依赖或者数据依赖。

*抒己见:这里描述了以依赖为中心的多个内存位置访问的指令执行排序关系,终于把前面描述的关系用上了。有这些依赖关系时,读R1需要排在前面执行,其它情况是不受约束的。所以,没有上述指定的依赖关系时,不同位置的读写操作会被乱序执行。

Atomic-ordered-before

即使没有依赖关系,互斥读Load-Exclusive和互斥写Store-Exclusive也有保序能力。

一个内存读写RW1原子排序靠前Atomic-ordered-before来自同一观察者的内存读写RW2,当且仅当指令序上,RW1排在RW2前面,并且满足以下任一情况:

. RW1是读取R1,RW2是写入W2,当R1和W2都由原子指令生成的,或者正好是访问同一位置的互斥读/互斥写指令对。

. RW1是原子指令生成的写入W1,或者一个成功的互斥写指令生成的写入W1。RW2是一个Acquire语义或者AcquirePC语义生成的读取R2,并且R2 Reads-from读出W1。

*抒己见:原子指令和互斥指令是自带排序属性的,有没有依赖关系不重要,这个也是ARM64架构的指令集特点。

Barrier-ordered-before

屏障指令排序优先,相比同一观察者在屏障指令后的其它内存影响,屏障指令的内存影响会早生效。一个读取或者写入RW1和另外一个读取或者写入RW2的关系是屏障排序靠前关系,当且仅当RW1在指令序上排在RW2前,而且满足以下任何一种情况:

.RW1在指令序上排在一个DMB FULL指令之前,或者包含Acquire语义和Release语义的原子指令之前,屏障指令和原子指令按指令序也排在RW2前面。

.RW1是由Release语义的指令生成的写入W1,而RW2是有Acquire语义的指令生成的读取R2。

.RW1是读取R1,并且:

-    R1在指令序上排在DMB LD前,指令序上也在RW2前。

-    R1由Acquire语义或者AcquirePC语义的指令生成。

.RW2是写入W2,并且:

-    RW1是写入W1,指令序上排在DMB ST前,指令序上也排在W2前。

-    W2由Release语义的指令生成

-    RW1在指令序上排在一个具有Release语义的指令生成的W3前,W2与W3是Coherence-after关系。

*抒己见:内存墙阻止了后续指令翻墙提前执行,不管是读取和写入指令,在CPU微架构上可能是通过阻止流水线第一阶段的后续指令分发来实现。同时,包括Acquire语义和Release语义的原子指令自带内存墙属性,将读写指令与墙指令合并为一条指令。

需参考互斥和原子指令的详细介绍,本文暂时不展开。

Ordered-before

如果说任意一对内存影响是顺序的,只有它链成的顺序访问从外部观察也是一致的。

一个读取或者写入RW1 Ordered-before排序靠前另外一个读取或者写入RW2,当且仅当以下任何一种情况适用时:

RW1 is Observed-by RW2.

RW1 is Dependency-ordered-before RW2.

RW1 is Atomic-ordered-before RW2.

RW1 is Barrier-ordered-before RW2.

RW1 is Ordered-before 一个读或者写,而这个读或者写又is Ordered-before RW2

六、ARM64可控规则:系统中观察者之间的交互

1、使用共享变量来通信

系统中观察者之间的交互不局限于一致性内存中使用共享变量来通信。比如,一个观察者可以配置中断控制器触发一个中断到另外一个观察者,作为一种消息通信方式。这些交互典型的特点是涉及一个额外的代理,它定义了两个不同观察者间建立通信链路的指令顺序。当交互形式结合到共享变量的使用时,就必须使用DSB指令来确保它们之间的顺序。

2、所有内存完成规则的定义

对所有的内存,完成规则的定义如下:

.一个共享域内,对一个位置的读取R1是完成的,满足以下所有条件:

-    共享域内,观察者对相同位置的任何写入W1与读取R1是Coherence-after关系

-    共享域内,任何R1相关的地址转换表都walk完成。

.一个共享域内,对一个位置的写入W1被认为是完成的,需满足以下所有条件:

-    共享域内,一个观察者对相同位置的任何写与W1是Coherence-after关系。

-    共享域内,一个观察者对相同位置的任何读与W1是Reads-from关系,或者与W1是Coherence-after关系的另外一个写是Reads-from关系。

-    共享域内,与写相关的所有地址转换表都walk完成。

.共享域内地址转换表完成,包括转换表条目的刷新,共享域内相关的转换表walk完成,TLB刷新。

.cache维护指令完成,在共享域内,当指令的访存效果在共享域内完成,这条指令触发的地址转换表同步在共享域内完成。

.TLB无效指令完成,当所有内存访问使用的TLB条目都无效完成。

任何cache和TLB维护指令的完成,包括它在所有受指令和DSB操作影响的PE上完成。而DSB是保证维护指令被外部可视所必需的。

此外,对于Device-nGnRnE内存,一个内存映射外设上的位置,读取和写入将出现副作用,当读取和写入:

.可以开始影响内存映射外设的状态

.可以触发所有相关的副作用,无论他们是否影响到其它的外围设备,PEs,或者内存。

*抒己见:cache、TLB、MMU等等都太专业了,主要get的点是内存影响涉及到cpu内部这些模块,包括缓存和地址管理,不仅仅是内存那个位置的值发生了改变。

Device内存的副作用,主要表现在写一个控制地址A,device的另外一个状态地址B的值会有变化。这是外设的一个显著特征。不像内存,写A地址时,B地址的内容不会变化。

3、外设

本节定义内存映射外设和读取和写入外设的总顺序,外设一致性顺序定义如下:

一个内存映射外设占用一段内存区域(大小和实现相关),通过load和store指令访问。对内存映射外设的内存访问可能产生副作用,比如一个写入将导致外设执行一个额外的动作。从内存映射外设的地址中读取的值可能与最后一个写入此地址的数据值并不一致。因此,内存映射外设的访存效果可能不会出现Reads-from和Coherence order的关系。

*抒己见:驱动人员对此应该深有体会,对于写清除的寄存器,再回读就不再是写入的值了。对一个控制和状态集合的寄存器,写和读操作可能访问的完全是两个寄存器。所以,内存的一致性关系在这里不适用。

内存映射外设的Peripheral coherence order是对该外设的所有读写操作的总顺序。

对于一个读取或者写入RW1,与针对此外设的另外一个读取或者写入RW2,当出现下列情况,RW1在RW2之前呈现Peripheral coherence order:

.RW1和RW2使用Non-cacheable或者Device属性,RW1和RW2是Ordered-before关系

.RW1和RW2使用 Device-nGnRE或者Device-nGnRnE属性,RW1按指令序排在RW2之前。

*抒己见:Device地址属性包括GRE控制,Gathering or non Gathering (G or nG):多个访问合并为一个总线访问,Re-ordering (R or nR):访问同一设备是否可乱序 ,Early Write Acknowledgement (E or nE):外设访问时,是允许互联逻辑的buffer提前返回ACK,而不等待真正的外设访问完成信号。

一个读取或者写入RW1与另外一个读取或者写入RW2是Out-of-band-ordered-before关系,当且仅当以下任一种情况:

.RW1在指令序上排在DSB指令前,而DSB指令开始于一个实现定义的指令序,它间接导致RW2的生成。

.RW1与一个读取或者写入RW3是Ordered-before关系,而RW3与RW2是Out-of-band-ordered-before关系。

如果一个访存效果M1与另外一个读或者写M2是Out-of-band-ordered-before,那么所有观察者都将看到M1在M2之前发生。

*抒己见:鸡生蛋,蛋生鸡。搞懂乱序原理解决乱序问题,解决乱序问题搞懂乱序原理。

七、附-参考资源:ARM®  Architecture Reference Manual

ARM®  Architecture Reference Manual

ARMv8, for ARMv8-A architecture profile

DDI0487D_b_armv8 (ID042519)

B2.3  Definition of the ARMv8 memory model

侧记:像鲲鹏,去展翅高飞,共赢计算新时代

有一个朋友说,他的技术博客有一年多没有更新。他觉得自己主要是太懒,因为他的工作很安逸、没有996的压力,但也没有新技术的激励,过于平淡的工作,也让他忘了自己的初衷。

很多朋友认为,还是要有目标才有动力,对于我们普通人来说,生活中需要有个trigger让自己努力起来。

也许是生活压力,也许是某人给的刺激,也许是自己的兴趣爱好,也许就是为了爱的人、爱的家、爱的国!!

他说,好吧,最近看华为被美国制裁刷了屏,内心很不平静,骂人没用,不骂又憋内伤,我辈俗人只有相信自强不息以立志!

向朋友们致敬!

ARM 鲲鹏

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【安全算法之概述】一文带你简要了解常见常用的安全算法
下一篇:sql server 数据修改的内部原理
相关文章