1 前言 

       如图1所示, Cortex-M7最大支持4GB的内存寻址,并对内存映射(memory map)做了初步的规定,将整个内存空间划分为了多个内存区域(region)。每个内存区域有着既定的内存类型(memory type)和内存属性(memory attribute),这两者决定了访存的具体行为。

图1  Cortex-M7 memory map 

2 内存区域的类型和属性

        Memory ordering(内存排序) 描述了CPU访问内存的前后顺序,既可以指编译器编译时生成的内存访问序列,也可以指在运行时(runtime)由CPU生成的内存访问序列。CPU 并不是完全按照我们写的程序的逻辑顺序来访问内存的,在保证原始语义的情况下,编译器会对代码指令进行重新排序, CPU也会对指令进行重新排序、延缓执行、各种缓存等等,即通过乱序执行(out-of-order execution)来提升程序执行效率。

        所以,内存排序描述了CPU在内存顺序重排方面的能力,这也直接决定了CPU是否支持乱序执行。乱序执行使得CPU可以在最大程度上利用各类型的内存的总线带宽(bus-bradwidth)以提高程序运行效率。例如,cache比普通的memory banks的访问带宽要大很多。在单线程模式下,编译器和CPU偷偷地做了优化,乱序执行的细节无须由应用层代码操心,彷佛程序确实在严格按照代码的逻辑顺序执行;但在多线程模式下,或者通过内存总线和其他硬件进行数据交互时,乱序执行很容易出问题,这时候就需要内存屏障(memory barriers)指令出场了。     

2.1  内存类型memory types

         Cortex-M7的内存区域的类型主要有以下几种:

        ① 正常(Normal)

        Cortex-M7采用了流水线机制,通过执行前预取指令和分支预测来优化程序执行效率。对于normal类型内存区域,CPU可以重新排序处理(指令)以提高效率,或者可以做一些乐观的猜测性读操作(ldr);

       设备内存和强排序内存区域Device and Strongly-Ordered)

        对于设备内存(device memory)或强排序内存区域(Strongly-ordered memory)来说,CPU会老老实实的严格按照程序定义的顺序进行指令执行。Device Memory类型用于描述那些映射到外设寄存器的内存区域。这类内存访问可能有副作用,比如读写某个寄存器可能会触发硬件操作比如,访问一个UART的数据寄存器,如果两次写操作被合并或重排,可能导致数据发送错误。 Device Memory(设备内存)的具体特点包括:

  • 不可缓存(Non-cacheable):避免缓存导致数据不一致。

  • 不可合并写操作(No write merging):处理器必须按代码顺序执行写操作。

  • 访问顺序要求:通常要求保持一定顺序,但允许部分优化(具体取决于子类型)

  • 可能需要显式屏障

        此外,设备内存强排序内存区域也有所不同,外部内存系统(external memory system)可以用带缓冲地写设备内存(device memory)进行写操作,但不可带缓冲地写强排序内存区域。                     Strongly-Ordered Memory是Device Memory的一个子集,它进一步限制了内存访问的顺序。所有对Strongly-Ordered内存的访问都必须严格按照程序中的顺序执行,没有任何优化或重排。这对于需要严格顺序的操作非常重要,比如配置硬件寄存器,必须按照特定顺序写入才能正确初始化设备,具体特点包括:

  • 严格顺序:读/写操作按代码顺序完成,无重排或合并。

  • 隐式内存屏障:无需显式插入内存屏障指令(如DMB)。

  • 低延迟响应:适用于需要即时硬件反馈的场景。

Gathering,   reordering,  early write acknowledgement, device memmory 的细分属性。M7中device属性,可以允许访问合并,即gathering;

  • Device Memory:适用于需要顺序访问的外设寄存器,支持不同严格程度的子类型。

  • Strongly-Ordered:最严格的Device类型,强制完全顺序执行,适合关键硬件初始化,约等于最严格的device memory。

2.2 内存属性memory attributes

         内存属性主要包括以下两种:

        ① 可共享(Shareable)

        对于可共享内存区域,如果系统中存在多个总线master(bus master,例如CPU配有DMA控制器),内存系统需要在总线master之间提供数据同步机制。所以,可共享即意味着拿来即可用,其内存区域内的数据由内存系统来保证同步性。

        此外,强排序内存区域总是可共享的,强排序的目的其实是为了防范语义风险;如果多个总线master可以访问一个不可共享的内存区域,则必须由软件来保证个总线master之间的数据一致性(data coherency)。

        ② 不可执行(Execute Never,XN)

        意为不可执行,即不可从该内存区域取值。谁胆敢从XN内存区域进行取值操作,立马错误异常(fault exception)伺候。

3 内存系统的内存访问顺序

        Memory system ordering of memory accesses,描述了内存系统对于内存访问顺序相关的规范。

        在不影响指令序列的行为的前提下,对于由显式内存访问指令引起的内存访问,内存系统不保证访问的实际顺序与指令的程序级顺序完全相同,即支持乱序执行。如果程序的正确执行依赖于两个内存访问指令的执行顺序,则软件必须在这两条指令之间加入内存屏障指令来保证其时序。

        但是,对于设备内存(device memory)或强排序内存区域(Strongly-ordered memory)来说,内存系统确实在一定程度上保证了访存的时序。例如,假设有由同一主接口(master interface)发起的A1和A2两条访存指令,如果A1在程序中执行的顺序在A2之前,则这两条访存指令的最终内存访问顺序如表1所示:

表1 内存访问顺序示例

        从中可见,若两者都为可共享的设备内存或强排序内存,亦或者两者都为不可共享的设备内存,则内存访问都是严格序列化的。

4 各内存区域的访存行为(Behavior of memory accesses)

        Cortex-M7对于不同内存区域的访存行为具有不同的规范。其中,Code,SRAM和 external RAM区域可用于加载程序代码,具体如表2所示。

表2 各内存区域的内存访问行为明细

        以上只是默认的内存访问行为,如果使能MPU,则可以对其进行重新定义。

        当系统拥有cache和可共享内存(shared memory)时,某些内存区域的约束有所增加,某些内存区域被划分成更小颗粒度的区域,具体如表3所示:

 表3 各内存区域的共享性和cache策略

        其中,WT表示的是write through,no allocate;WBWA表示write back,write allocate,具体含义如图2所示。        

图2 Cache读写策略示意图

5 软件程序定义的内存访问顺序

        软件程序直观地定义了一个程序执行流(program flow),其规则时基于一定的逻辑顺序。而当软件程序经编译器翻译成可执行的机器码并在CPU上执行时,内存系统通常不会严格按照软件程序定义的时序来进行内存访问,这也就是前文描述的乱序执行。

        CPU采用乱序执行的原因主要有以下几点:

        ① 在不改变软件程序定义的指令序列的预期行为的前提下,CPU可能会对一些访存操作重新排序;

        ② CPU拥有多个总线接口(bus interface);

        ③ 不同的内存区域或设备具有不同的等待状态;

        ④ 一些内存访问是带缓冲的,或者是带分支预测的;        

        举例来说,如果可以在运行时更改存储器的映射关系或者内存保护区的设置, (通过写MPU 的寄存器),就必须在更改之后立即补上一条 DSB 指令(数据同步指令)。因为对 MPU 的写操作很可能会被放到一个写缓冲中。

        写缓冲是为了提高存储器的总体访问效率而设的,但它也有副作用,其中之一,就是会导致写内存的指令被延迟几个周期执行,因此对存储器的设置不能即刻生效,这会导致紧临着的下一条指令仍然使用旧的存储器设置——但程序员的本意显然是使用新的存储器设置。这种紊乱危象是后患无穷的,常会破坏未知地址的数据,有时也会产生非法地址访问 fault。

       章节3中描述的一些由内存系统保驾护航的,严格按程序定义的顺序来访问内存的情况。除此之外,需要软件程序通过内存屏障指令(memory barrier instructions)来强制保证CPU的内存访问顺序,即严格按照软件程序定义的指令序列执行。

        这些相关的内存屏障指令指令有如下几种:

        ① DMB(Data Memory Barrier)             

        DMB指令可以保证其身前的内存指令全部处理完成后,其身后的指令才可以开始处理;DMB对于非访存指令的执行顺序没有影响;

        ② DSB(Data Synchronization Barrier)

       DSB架起了一道屏障,保证其身前所有的内存指令都执行完毕后,才会打开屏障,使得其身后的内存指令得以开始执行。在屏障打开之前,后续的所有内存指令都只能乖乖等待。此外,DSB指令不会更新xPSR的标志位。

        ③ ISB(Instruction Synchronization Barrier)

        ISB指令是指令级别的同步指令,在其身前的内存指令全部处理完毕后,对于其后的已预取指令通通抛掉,直接清洗流水线,重新从cache或内存中取指。这也就,处理结果对于后续的指令来说,ISB之前的所有指令都已执行完毕,且结果也已经落到实处;

        DMB 在双口 RAM 以及多核架构的操作中很有用。如果 RAM 的访问是带缓冲的,并且写完之后马上读,就必须让它“喘口气” ——用 DMB 指令来隔离,以保证缓冲中的数据已经落实到 RAM 中。
        DSB 比 DMB 更保险(当然也是有执行代价的),它是宁可错杀也不漏网——清空了写缓冲,使得任何它后面的指令,不管要不要使用先前的存储器访问结果,通通等待访问完成。
        同 DMB/DSB 相比, ISB 指令看起来似乎最强悍,对于高级底层技巧:“自我更新” (self-mofifying)代码,非常有用。举例来说,如果某个程序从下一条要执行的指令处更新了自己,但是先前的旧指令已经被预取到流水线中去了,此时就必须清洗流水线,把旧版本的指令洗出去,再预取新版本的指令。因此,必须在被更新代码段的前面使用 ISB,以保证旧的代码从流水线中被清洗出去,不再有机会执行。

5 总结        

        以上讨论的都是Cortex-M7默认的内存框架和内存访问行为,在功能安全的加注下,MPU的地位愈发重要,很多内容都还要和MPU结合起来一起考虑。不过,这都是后话了。

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐