ARMv8架构处理器实现了弱一致性内存模型,在某些情况下,处理器在执行指令时不一定完全按照程序员编写的指令顺序来执行,处理器为了提高指令执行效率会乱序执行指令和预测指令。现代处理器为了提高系统吞吐率都会做如下优化:
- 并发执行多条指令。处理器可以在一个时钟周期内发射和执行多条指令。
- 乱序执行。处理器可以乱序执行没有依赖关系的指令。
- 预测执行。处理器在遇到一个条件判断时会预测将来可能发生的情况,并且提前执行分支代码。
- 预测加载。若可以预测一个加载指令,那么高速缓存就可以提前把数据预取到高速缓存行中,从而提高效率。
- 加载和存储优化。读写外部内存是一个耗时的操作,处理器应该尽量减少读写次数,如处理器将多次访问内存的操作合并为一次传输,这样可以提高系统效率。
在一个单核处理器系统中,指令乱序和并发执行对于程序员来说是透明的,因为处理器会处理这些数据依赖关系。但是,在多核处理器系统中,多个处理器内核同时访问共享数据或内存时,与处理器相关的乱序和预测执行等优化手段就可能会对程序造成意想不到的麻烦。
内存属性
ARMv8架构处理器主要提供两种类型的内存属性,分别是普通(normal)内存和设备(device)内存。
1. 普通内存
普通内存是弱一致性的,没有额外的约束,可以提供最高的内存访问性能。通常代码段、数据段以及其他数据都会放在普通内存中。普通内存可以让处理器做很多的优化,如分支预测、数据预取、高速缓存行预取和填充、乱序加载等硬件优化。
2. 设备内存
处理器访问设备内存会有很多限制,如不能进行预测访问等。设备内存是严格按照指令顺序来执行的。通常设备内存留给设备来访问。若系统中所有内存都设置为设备内存,就会有很大的副作用。
ARMv8架构定义了多种设备内存的属性。a.聚合和不聚合。聚合表示同一个内存属性的区域中允许把多次访问内存的操作合并成一次总线传输。b.指令重排与不重排。c.提前写应答与不提前写应答。往外部设备写数据时,处理器先把数据写入写缓冲区中,若使能了提前写应答,则数据到达写缓冲区时会发送写应答;若没有使能提前写应答,则数据到达外设时才发送写应答。
内存属性并没有存放在页表项中,而是存放在 MAIR_ELn(Memory Attribute Indirection Register Eln)中。页表项中使用一个 3 位的索引值来查找 MAIR_ELn。
内存属性如此重要,那系统在什么时候配置 MAIR 和内存属性呢?系统在上电复位并经过 BIOS 或者 Bootloader 初始化后跳转到内核的汇编代码。而在汇编代码中会对内存属性进行初始化。
高速缓存共享属性
普通内存可以设置高速缓存为可缓存的和不可缓存的。进一步地,我们可以设置高速缓存为内部共享和外部共享的高速缓存,一个处理器系统中,除了处理器之外,还可以有其他的可以访问内存的硬件单元,这些硬件单元通常具有访问内存总线的能力,如 DMA 设备、GPU 等,这些硬件单元可以称为处理器之外的观察点。在一个多核系统中,DMA 设备和 GPU 通过系统总线连接到 DDR 内存,而处理器也通过系统总线连接到 DDR 内存,它们都能通过系统总线访问到内存。
- 如果一个内存区域被标记为不可共享的,表示它只能被一个处理器访问,其他处理器不能访问这个内存区域。
- 如果一个内存区域被标记为内部共享的,表示它可以被多个处理器访问和共享,但是系统中其他的访问内存的硬件单元就不能访问了,比如 DMA 设备、GPU 等。
- 如果一个内存区域被标记为外部共享的,表示系统中很多访问内存的单元都可以和处理器一样访问这个内存区域。
内存屏障
ARMv8 指令集提供了 3 条内存屏障指令,依次严格:
- 数据存储屏障(Data Memory Barrier,DMB)指令: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的访问指令。DMB 指令保证的是 DMB 指令之前的所有内存访问指令和 DMB 指令之后的所有内存访问指令的顺序。也就是说,DMB 指令之后的内存访问指令不会被处理器重排到 DMB 指令的前面。DMB 指令仅仅影响内存访问指令、数据高速缓存指令以及高速缓存管理指令等,并不会影响其他指令的顺序。
- 数据同步屏障(Data Synchronization Barrier,DSB)指令:仅当所有在它前面的访问指令都执行完毕后,才会执行在它后面的指令,即任何指令都要等待 DSB 指令前面的访问指令完成。位于此指令前的所有缓存,如分支预测和 TLB 维护操作需全部完成。
- 指令同步屏障(Instruction Synchronization Barrier,ISB)指令:刷新流水线与预取缓冲区后,才会从高速缓存或者内存中预取 ISB 指令之后的指令。ISB 指令通常用来保证上下文切换的效果,如 ASID 更改、TLB 维护操作和 C15 寄存器的修改等。