[A-16]ARMv8/ARMv9-Memory-内存共享属性和内存一致性(Shareability\Consistency)

文摘   2024-11-02 21:37   辽宁  
ver0.2
前言
前文我们详细的叙述了内存世界的无形之手是如何将整个ARM的系统的执行效率推升到极致,但是伴随着ARM架构的这些优化的策略执行在特定的场景下也会造成内存世界的重排序,而重排序会给应用层的编程带来一定的不确定性。从ARM架构的角度看过去,造成这种不确定的一个重要的原因就是ARM的内存模型中是支持内存共享的属性(Memory Shareability),PE-Core之间可以共享同一段内存,总线架构下各个Master之间也可以共享一段内存。当一个SOC在运行时,如果想让各个节点(Master)始终工作在正确的状态,那就需要使他们看到的内存内容是一样的状态,这就是内存的一致性(Memory Consistency)。这里声明一下,前文中说好的那只有形的手-内存屏障不会鸽的,只是在写屏障相关内容的时候,觉得内存的一致性课题挺有意思的,很多地方值得研究,所以独立一篇成文。
正文
在前面的文章中,我们已经研究过Cache的一致性: [A-06] ARMv8/ARMv9-Cache的一致性机制(Cache系列完结篇),相信度过那篇文章的伙伴理解内存一致性应该不会困难。Cache缓存的就是内存里面的内存,因此Cache的一致性其实是内存一致性的一个子集,我们今天研究的范围更加宏观一些那就是内存一致性。而内存一致性课题的由来就是内存的共享属性,因此有必要先花一点时间来介绍一下它。
1.1 内存的共享(Shareability)属性
读过前序文章内存多级页表架构和页表描述符:[A-12]ARMv8/ARMv9-Memory-页表描述符(Translation table descriptor) 的伙伴应该知道,ARM通过页表描述内存的属性,而这些属性中有一个属性就是Shareability,如图1-1所示。

图1-1 ARM Stage-1页表描述符

通过页表描述符我们可以看到ARM使用SH[1:0]两个位来描述这一块内存的共享属性,那么这两个bit能具体产生哪些配置呢,如图1-2所示。

图1-2 Stage 1 Shareability attributes

这里面需要注意的是内存的不同属性之间是有一定的关联性的,比如我们今天说到的共享属性就和内存类型(Normal or Device)和内存的Cache性就有关联,这里我们简单归纳一下:
(1) 对于属于Normal类型且可以缓存的内存来说,这段区域可以指定为图1-2中的任意一种类型。
(2) 而对于经过所有阶段合并转换后类型为设备类型或者是Normal的且不可Cache的内存,那么这段内存区域则会被系统赋予Outer Shareable的内存。
到这里内存的共享属性似乎就介绍完了,原来好简单啊,就两个标志位组合一下,最多也就四个选项。似乎是这样的,但是其实并非如此,我们来思考几个问题:
(1) 既然是共享属性,到底是谁和谁在共享;
(2) 共享的内存被更新之后,是如何通知到其他共享者的?
(3) 学习过前文页表架构的我们都知道,页表不光是分级的而且是分阶段的,图1-2中只列出了Stage-1阶段,那么Stage-2阶段又是什么情况?
本文我们先来回答问题(1)(2),问题(3)涉及到虚拟化课题,我们会单独成文来叙述。
1.2 Domains
本节先来回答第一个问题,到底都谁参与了共享。我们在前面文章中反复提到了总线架构,这里我们不展开讨论了,感兴趣的伙伴可以自行阅读。这里我们简单回顾一下,现代处理出货的时候早已经不是单打独自战斗了,而是一个小的功能单元称之为SOC(system on chip)(如图1-3所示)。

图1-3 总线架构框图

这个SOC上不急集成了CPU(PE-Cores),还要集成了GPU、DPU、NPU、SMMU等等具有独立功能的IP-Core,这些功能单元是通过总线链接在一起的。一个功能功能单元,只要按照总线架构要求的接口去实现,就能够顺利的对接到SOC内部,和其他功能单元融合为一个整体,这些功能单元就被称之为总线架构下的Master。芯片公司在拿到ARM的IP授权后,会基于总线架构的规范进一步设计SOC内部功能单元布局,这里面就要考虑一些实际的情况了,比如芯片的成本和芯片的性能了。功能越强大,集成的Master就要越多,芯片就会更加的复杂,自然就会推高成本。比如很多Master都需要使用SOC中的内存才能正常工作,而这个过程其实就是与CPU(PE-Cores)共享内存的过程,到底很简单CPU绝对是SOC中的老大地位,所有的资源都归它控制,任何Master想要使用内存都得和它商量。基于SOC的特点,那么自然降低成本的方法也很简单,就是降低总线架构层面的复杂度。这其中降低共享内存复杂度(当然肯定还有其他维度)就可以达到目的,因此ARM体系将SOC内部各个MASTER归类分成一个又一个的子系统,比如将图1-3中的GPU、VPU、DPU搞到一起形成一个MM SubSystem,这样GPU搞出一个Surface直接让VPU解码的视频数据铺装上去,然后GPU再把他们合成一个Hardware Layer给DPU直接推到外部显示器上显示出来,整个过程在一个子系统内部就搞定了。基于这样的思想,SOC内部根据功能关联度、安全要求等因素,一个又一个子系统被抽象了出来,而通过他们共享内存数据的范围,Domain的概念诞生了,如图1-4所示。

图1-4 Master和Domain的关系

我们来看一下手册中对Domain的介绍:
In the ARMv8-A Architecture, the term domain is used to refer to a set of primary bus masters. Domains determine which of the masters are snooped, for coherent transactions. Snooping is the checking of the master’s cache to see whether the requested location is stored there. There are four defined domain types:
• Non-shareable.
• Inner Shareable.
• Outer Shareable.

• System.

通过手册的描述,可以看出ARM体系下有4种类型的Domain。Master是否统一到一个Domain下最重要的考虑因素就是一致性。啥一致性啊?你看看那四种Domain类型,是不是似曾相识,晚上滑动一下屏幕看看图1-2,是不是呼应上了,感受到彼此的心跳了。是的,这里面的一致性就是内存一致性。现在我们清楚了参与的内存共享的各个角色了,他们就是SOC内部的各个子系统。
我们先不急展开内存一致性,先把这4个Domain的具体特征搞清楚,如图1-5所示。

图1-5 Domains Features

我们这里简单归纳一下帮助大家理解:
Non-shareable
这种Domain比较倔强,不和其他的Master分享任何东西。任性是需要一定实力的,比如人们常说有钱任性。SOC内部能这么任性的也只有CPU了,比如CPU中的一个PE-Core。这种类型内存的应用场景:
(1) 需要确保隔离性和安全性的场景下,比如用来存储该核心或处理器的私有数据,这些数据对其他核心或处理器不可见。
(2) 在多线程环境中,尽管Non-Shareable内存不是实现线程私有数据的必要条件,但在某些特定的硬件实现或优化中,它可能会被用来存储线程的私有数据。这有助于进一步提高数据的安全性和隔离性,防止其他线程或核心对数据的非法访问。
(3) 不需要关心数据一致性上下文中,Non-Shareable内存可能会被用于内存映射的外设或特殊设备。这些设备可能需要独立的内存空间来存储其状态或数据,并且不希望这些数据被其他处理器或核心访问,使用Non-Shareable内存可以满足这种需求。
Inner Shareable
这种Domain最大的特点,可以使内部的各个Master运行在同一个操作系统下,比如Linux。SOC中能运行操作系统就是CPU了,现代操作系统的架构,不论是Big.Little还是DSU.Big.little,本质上都是多核异构结构。这种类型内存的应用场景可以简答归纳成一句话:就是操作系统跑起来需要的所有内存都属于该Domain,当然也包括虚拟化场景和Secure执行状态。操作系统嘛,详细大家肯定很熟悉了,无非就是多线程共享数据、设备驱动和Kernel共享内存、等等吧,就不展开了。
Outer Shareable
这种Domain最大的特点就是CPU需要和CPU以外的设备共享数据,比如GPU、NPU、DMA控制器等等。这样的场景也非常的多,我们举例两个典型的上下文:
(1)这种场景特点就是CPU只会参与控制内存声明周期,只控制不使用,申请出来之后交给这些xPU自己去玩儿。比如Android通过ION申请了一块kernel中CMA类型内存,然后交给GPU去描画,等啥时候GPU用够了,交还给Android系统释放掉归还给BuddySystem就可以了。
(2) 就是CPU设备类型内存的交互,比如一个设备类型内存连着一个设备的寄存器,这类交互其实就是放在在Outer Shareable Domain下。
System
上面三种场景基本上可以覆盖所有的SOC中的共享内存的上下文了,因此它并不常见,我们这里不展开了,没有意义。
经过上面的总结,我们来具象一个Domain实例,帮助大家加深理解Domain的概念以及和内存共享的关系,如图1-6所示。

图1-6 Domains Instances

1.3 内存一致性
1.3.1 基本概念
我们来看一下ARM手册中对共享属性的介绍。

The shareable attribute is used to define whether a location is shared with multiple cores. Marking a region as Non-shareable means that it is only used by this core, whereas marking it as inner shareable or outer shareable, or both, means that the location is shared with other observers, for example, a GPU or DMA device might be considered another observer. In the same way, the division between inner and outer is IMPLEMENTATION DEFINED . The architectural definition of these attributes is that they enable us to define sets of observers for which the shareability attributes make the data or unified caches transparent for data accesses. This means that the system provides hardware coherency management so that two cores in the inner shareable domain must see a coherent copy of locations marked as inner shareable. If a processor or other master in the system does not support coherency, then it must treat the shareable regions as non-cacheable.

我们来解读一下手册中的描述:
(1) 因为内存有共享属性,所以可以操作和关心内存内容的角色就变得复杂了,比如两个PE-Core之间可以共享数据,那么他们就需要关注共享内存内容的一致性。
(2) 共享的内存有层次性,比如当一段内存被CPU和ADSP共享的时候,那么它们就都需要关注共享内容的一致性,比如CPU申请的一段存放音频数据的内存需要ADSP加一些音效进去,CPU再把这段内存通过DMA发送到BackEnd之前一定要确保ADSP已经渲染完了。
(3) 这个共享的过程中就衍生出了一个概念观察者(Observer),共享数据的双方或者几方,当一方对数据进行操作的时候,其他的方就自动变成观察者。
(3) 不管在什么情况下,当SOC内部共享数据的几方,要使用数据的时候,它看到和拿到的数据和其他几方是一样的时候,那么我们可以称为此时内存是处于一致性状态。
1.3.2 内存一致性的实现方式
由于SOC工作的时候上下文非常的负责,因此ARM架构下是通过软硬件结合的方式实现内存的一致性,这里我们也是举典型例子帮助大家理解:
(1)软件管理一致性:
• CPU内部由于一般都有Cache加持,可以通过操作Cache的指令是Cache和内存保持一致。
• Kernel的驱动程序在收到中断请求后,主动通知CPU失效Cache Line,讲DMA Buffer中的来自外部的最新数据同步到PE-Core中的Cache。
(2)硬件管理一致性:
由于软件管理一致性会增加处理器的负担,并可能影响性能和功耗。因此现代的CPU架构和总线架构都支持一致性规范,为硬件管理一致性提供了一种可简化软件的替代方式。使用这一解决方案时,任何标记为“共享”的缓存数据将始终自动保持最新。
• 场景一,PE-Core之间的Cache一致性操作就是由硬件完成。
• 场景二,PE-Core中Cache数据和GPU-Cache中断数据一致性操作就是由硬件完成。
1.3.3 内存一致性举例
我们通过一个例子来帮助大家理解内存一致性的工作上下文,先看图1-7。

图1-7 High-Level ARM Audio System

这里我们考虑一个播放音频数据的场景(这里为了说明问题,简化了音频播放模型、忽略了很多细节):
(1) 首先我们要将音频文件切成一段一段加载到内存,这个时候需要初始化一段Normal并允许Cache的内存。
(2) 假如我们要播放的文件格式是mp3,那么此时需要对mp3文件进行软解码,软解码需要CPU深度参与,此时内存中的Audio Data需要缓存到CPU的Cache中,这里就出现了第一次的数据共享,换句话说就要注意Cache中的音频数据和内存中的数据一致性问题了。在ARM体系下,这两个Master之间的内存一致性通过硬件实现,因为ARM支持通过特定的Cache策略将PE-Core更新的Cache数据回写到内存中去。
(3) 到这里还不算完,比如用户通过播放器想使用特殊的音效播放这个mp3文件,那么此时软件需要调用操作系统的Audio API通知ADSP加载相应的CPU解码完的数据到ADSP中的Cache进行音效处理,此时贡献这段数据就多了一个Master就是ADSP,此时PE-Core和DDR内存子系统就变成了Observer。此时的内存一致性也是通过硬件完成的,当ADSP Cache中的音频数据被修改,BUS上的一致性监控模块发现ADSP数据更新后,会把更新的数据同步到PE-Core和内存。
(4) 这个时候ADSP会通过中断通知PE-Core,它完成音效处理了,这个时候代码继续执行,通过DMA控制器将CPU和ADSP处理过数据拷贝到AFE的相应端口(TDM、I2S),此时External DSP就开始接收这些数据,完成数据接收之后,它会通过中断信号通知SOC中的DMA停止拷贝动作的同时进行DA转换然后播放。DMA控制器收到External消息后会通知PE-Core开始下一段音频数据的加载并重复解码、音效处理、DMA拷贝的动作。这里刚刚播放这段内存数据就会出现共享的第四个MASTERDMA,而DMA要和其他几个Master之间保持数据的一致性就要通过软件的方式,需要通过中断处理程序和驱动代码共同完成。
(5) ADSP介入后,内存的一致性就来到了Outer Shareable Domain了,但是这个时候控制内存一致性的动作还是在Cache一致性机制下,由硬件完成。而DMA介入后,内存的一致性控制就超越了Cache一致性机制能力范畴,需要软件管理进入了,这一块也需要大家仔细思考。
通过上面这个例子,同样一段音频数据,先后有两个Master参与更新,一个Master参与拷贝。整个播放的过程中为了维护内存的一致性,系统的软硬件机制都发挥了作用。要理解上面的例子需要对操作系统特别是BSP工作原理有一定了解才可以,大家能明白大致的过程就可以,不必太纠结细节(等后面有时间会专门开一个系列写写Android和Linux的Audio架构,写写ALSA)。
结语
本文我们详细叙述了,内存的共享属性,然后介绍了ARM体系中和内存共享属性强关联的Domain概念和内存一致性的概念,最后通过一个自己串联了系统通过软硬件机制维护内存一致性的大致过程。能够被控制说明是个好事儿,系统的工作不会出错,但是前一篇文章中讲过,由于ARM的弱排序内存架构,很多时候内存一致性状态不是那么100%稳定的,比如CPU的软解码还没完成,可能ADSP就开始加载共享的内存区域中的数据进行音效处理了,那么怎么避免这种情况发生,就需要通过更严格的同步机制发挥作用。这个机制就是我们下一篇文章要讲的内存屏障,请大家保持关注。
Reference
[00] <corelink_dmc520_technical_reference_manual_en.pdf>
[01] <corelink_dmc620_dynamic_memory_controller_trm.pdf>
[02] <IP-Controller/DDI0331G_dmc340_r4p0_trm.pdf>
[03] <80-ARM-IP-cs0001_ARMv8基础篇-400系列控制器IP.pdf>
[04] <arm_cortex_a725_core_trm_107652_0001_04_en.pdf>
[05] <DDI0487K_a_a-profile_architecture_reference_manual.pdf>
[06] <armv8_a_address_translation.pdf>
[07] <cortex_a55_trm_100442_0200_02_en.pdf>
[08] <learn_the_architecture_aarch64_memory_management_guide_en.pdf>
[09] <learn_the_architecture_armv8-a_memory_systems_en.pdf>
[10] <79-LX-LK-z0002_奔跑吧Linux内核-V-2-卷1_基础架构.pdf>
[11] <79-LX-LD-s003-Linux设备驱动开发详解4_0内核-3rd.pdf>
[12] <learn_the_architecture_memory_systems_ordering_and_barriers.pdf>
[13] <arm_dsu_120_trm_102547_0201_07_en.pdf>

[14] <80-ARM-MM-AL0001_内存学习(三):物理地址空间.pdf>

Glossary
MMU             - Memory Management Unit
TLB               - translation lookaside buffer
VIPT              - Virtual Index Physical Tag
VIVT              - Virtual Index Virtual Tag
PIPT               - Physical Index Physical Tag
VA                   -  Virtual Address
PA                   -  Physical Address
IPS                  - Intermediate Physical Space
IPA                  - Intermediate Physical Address
VMID               - virtual machine identifier
TLB                  - translation lookaside buffer(地址变换高速缓存)
VTTBR_EL2     - Virtualization Translation Table Base Registers
ASID                 - Address Space Identifier (ASID)
DMC                 - Dynamic Memory Controller
DDR SDRAM   - Double Data Rate Synchronous Dynamic Random Access Memory
TBI                   - Top Byte Ignore
DMB            - Data Memory Barrier
DSB            - Data Synchronization Barrier 
ISB             - Instruction Synchronization Barrier
DSU         - DynamIQ ™ Shared Unit
SOC          - System on Chip

浩瀚架构师
和大家一起探索这个神奇的世界。