MMU内存管理
1.MMU内存管理(armv8.6手册的D5章节),MMU包含快表TLB,TLB是对页表的部分缓存,页表是存放在内存里面的。
AArch64仅仅支持Long Descriptor的页表格式,AArch32支持两种页表格式Armv7-A Short Descriptor format和Armv7-A(LPAE)Long Descriptor format(默认是short)。AArch64支持3种不同的页大小:4KB,16KB,64KB(Linux使用的是4KB的页大小),大粒度的page size可以减少页表的体积。ARMv8的地址总线位宽通常为48位,最大空间支持256TB,对于实现了LVA扩展的ARMv8可以将位宽扩展到52位。ARMv8虚拟地址是64位的,虚拟地址VA被划分为两个空间,每个空间最大支持256TB,低位的虚拟地址空间位于0x0000_0000_0000_0000到0x0000_FFFF_FFFF_FFFF高位的虚拟地址空间位于0xFFFF_0000_0000_0000到0xFFFF_FFFF_FFFF_FFFF,低位地址空间用作App Space,使用页表TTBR0,高位地址空间用作Kernel Space,使用页表TTBR1,如下图所示,中间的FAULT部分的地址CPU是无法访问的,若访问会报错。
AArch64采用的是四级页表索引,如下图所示:
AArch64页表的L0~L2页表的页表项格式如下图所示,共有三种情况,bit0为0表示该页表项无效,bit0为1表示有效;bit1为1表示该页表项指示下一级页表的基地址,bit1为0表示该页表项指向一个大块内存空间。(可参考ARMv8手册第D5.3节)
AArch64页表的L3页表的页表项格式如下图所示,不同粒度的页面大小,会有不同位数的输出地址。
高位属性和低位属性的具体字段可以参考ARMv8手册的D5.3.3节,不涉及虚拟化只需参考stage1的部分,如下图:
各个字段具体含义如下:
通常一个TLB entry只能完成一个page的VA到PA的转换,Armv8上利用TLB进行的一个优化,可以利用一个TLB entry来完成多个连续的page的VA到PA的转换,这通过上图中的Contiguous位标示,使用Contiguous bit的条件:页面对应的VA和PA必须是连续的、对于4KB的页面,可以一次转换16个连续的page、对于16KB的页面,可以一次转换32或者128个连续的page、对于64KB的页面,可以一次转换32个连续的page、连续的页面必须有相同的属性、起始地址必须以页面对齐。对于不同粒度的页面大小,48位地址的划分情况如下图所示:
2.共享性与缓存性:缓存性(cacheability)指的是页面是否使能了高速缓存以及高速缓存的范围。通常只有普通内存可以使能高速缓存,通过页表项AttrIndx[2:0]来设置页面的内存属性。另外,还能指定高速缓存是内部共享属性还是外部共享属性。通常处理器内核集成的高速缓存属于内部共享的高速缓存,而通过系统总线集成的高速缓存属于外部共享的高速缓存。共享性指的是在多核处理器系统中某一个内存区域的高速缓存可以被哪些观察者观察到。没有共享性指的是只有本地CPU能观察到,内部共享性只能被具有内部共享属性的高速缓存的CPU观察到,外部共享性通常能被外部共享的观察者(例如系统中所有的CPU、GPU以及DMA等主接口控制器)观察到。页表项属性中使用SH[1:0]字段来表示页面的共享性与缓存性,对于使能了高速缓存的普通内存,可以通过SH[1:0]字段来设置共享属性。但是,对于设备内存和关闭高速缓存的普通内存,处理器会把它们当成外部共享属性来看待,尽管页表项中为SH[1:0]字段设置了共享属性(此时SH[1:0]字段不起作用)。在多核处理器系统中,内部共享性和外部共享性描述了缓存的访问范围。内部共享性指处理器内部多个核心之间共享的缓存(如L1、L2缓存),这些缓存只在同一处理器内部可见,通常访问速度较快,适用于核心间的缓存一致性。外部共享性指多个不同硬件单元(如多个CPU核心、GPU、DMA等)共享的缓存,通常位于处理器外部(如L3缓存),这些缓存通过系统总线对所有设备可见,保证了不同硬件之间的缓存一致性。内部共享性通常具有较低的延迟,而外部共享性涉及的硬件更多,延迟较高。两者的主要区别在于缓存的可见性和访问范围,分别适用于不同层级的硬件和内存访问需求。
3.AArch64执行状态的MMU支持单一阶段的页表转换,也支持虚拟化扩展中两阶段的页表转换。单一阶段的页表转换指把虚拟地址(VA)翻译成物理地址(PA)。两阶段的页表转换包括两个阶段。在阶段1,把虚拟地址翻译成中间物理地址(Intermediate Physical Address,IPA);在阶段2,把IPA翻译成最终PA。
4.在SMP(Symmetric Multi-Processor,对称多处理器)系统中,每个处理器内核内置了MMU和TLB硬件单元。如图110所示,CPU0和CPU1共享物理内存,而页表存储在物理内存中。CPU0和CPU1中的MMU与TLB硬件单元也共享同一份页表。当一个CPU修改了页表项时,需要使用BBM(Break-Before-Make)机制(该机制用于确保在SMP系统中,当一个CPU修改页表项时,其他CPU不会使用过时的TLB缓存数据,可参考ARMv8手册的第D5.11节和K11.5.3节)来保证其他CPU能访问正确和有效的TLB。
5.和页表相关的寄存器:
-
TCR_EL1(Translation Control Register):用来配置地址空间大小和页面粒度,其常用字段如下:IPS[34:32]:用来配置物理地址的大小,对于48位地址(物理空间256TB),可将该位设置为0b101。如果IPS字段定义的输出地址大于实际物理内存地址,那么CPU会使用实际物理内存,因为MMU输出地址不能大于实际物理地址。如果页表里的输出地址超过了物理内存地址,会触发地址大小缺页异常(address size fault)或者页表转换缺页异常(translation fault);TG0[15:14]和TG1[31:30]:分别用来配置TTBR0_EL1和TTBR1_EL1的页面粒度,可设置为4KB、16KB或64KB,例如将该位置为0b00表示4KB;T0SZ[5:0]和T1SZ[21:16]:分别用来配置TTBR0_EL1和TTBR1_EL1对应页表所能管辖的地址空间的大小,计算公式为2^(64-TxSZ),如果想将其设为48位寻址空间,则将该位的值设置为16即可;SH0、SH1、IRGN0、IRGN1、ORGN0、ORGN1:用来设置TTBR0_EL1和TTBR1_EL1对应内存的相关属性,如共享属性、高速缓存属性(具体参考ARM64体系结构编程与实践P203),上文提及的每个页表项中的SH[1:0]比当前的SH字段优先级更高,当两个字段都设置时,以页表项中的SH[1:0]位对应的共享属性为准,一般情况下两者需保证一致,否则可能出错
-
SCTLR_EL1(System Control Register):M[0]位用来设置MMU的打开与关闭,即控制第一阶段地址翻译的开关,C[2]位用来设置data cache的打开与关闭,I[12]位用来设置instruction cache的打开与关闭
-
TTBR0_EL1和TTBR1_EL1:分别指向TTBR0和页表TTBR1的基地址,通常用于EL1/EL0的页表映射
6.ARMv8架构处理器主要提供两种类型的内存属性:普通内存(Normal)和设备内存(Device),普通内存是弱一致性的(weakly ordered),没有其他额外的约束,提供最高的内存访问性能。处理器访问设备内存有很多限制,如不能使用高速缓存等。设备内存是严格按照指令顺序来执行的,是强一致性的。ARMv8架构定义了多种设备内存的属性:
- Device-nGnRnE:不支持聚合操作,不支持指令重排,不支持提前写应答;
- Device-nGnRE:不支持聚合操作,不支持指令重排,支持提前写应答;
- Device-nGRE:不支持聚合操作,支持指令重排,支持提前写应答;
- Device-GRE:支持聚合操作,支持指令重排,支持提前写应答。
设备内存相关操作:G表示支持聚合操作(聚合是指可以在同一个内存属性的区域里面把多次访问内存的操作合并成一次总线传输,如果一个设备内存标记为nG,那么就会严格按照访问的次数和大小来进行访问而不会进行合并优化);R表示支持指令重排,可以乱序执行;E表示支持提前写应答(即数据到达写缓存就可以进行写应答,如果一个设备内存标记为nG则数据到达外设才能进行写应答)。内存属性并没有存放在页表的页表项中,而是存放在MAIR_ELn寄存器(Memory Attribute Indirection Register)。MAIR_ELn寄存器结构如下,最多可以存储八类不同属性的内存:
页表项中有一个3位的索引值AttrIdx[0:2]来查找MAIR_ELn寄存器。
7.在操作系统启动过程中,从BootLoader/BIOS跳转到操作系统时,MMU(内存管理单元)通常是关闭的,因此处理器直接使用物理地址进行内存访问。关闭MMU意味着不能使用高速缓存优化性能,因为当MMU关闭时,系统无法使用虚拟地址进行内存访问,因此无法进行虚拟地址到物理地址的转换,也就无法正确维护缓存的内容。在系统初始化时,打开MMU并启用数据高速缓存是提高性能的关键,但这需要谨慎操作。因为当MMU打开后,处理器会开始使用虚拟地址进行内存访问,因此必须确保虚拟地址到物理地址的正确映射(如使用恒等映射,即VA=PA,一般映射前后2M空间就可以了),否则可能会导致访问错误或数据不一致,在关闭MMU情况下,处理器访问的地址都是物理地址。当MMU打开时,处理器访问的地址变成了虚拟地址。现代处理器都是多级流水线架构,处理器会提前预取多条指令到流水线中。当打开MMU时,处理器已经提前预取了多条指令,并且这些指令是以物理地址来进行预取的。当打开MMU指令执行完成,处理器的MMU功能生效,那么之前提前预取的指令以虚拟地址来访问,到MMU单元去查找对应的物理地址。因此在打开MMU前后应用isb()指令刷新处理器的指令流水线让CPU重新取指。页表的创建和填充是由操作系统来完成的,但是处理器遍历页表是由处理器的 MMU来完成的。第14章的实验一通过恒等映射创建了四级页表,主要创建过程如下:
-
在链接脚本中的数据段直接分配4KB大小作为L0级页表的缓存空间,剩下的L1级、L2级、L3级页表在初始化时动态创建,实验中所设计的页表只有第L2级页表(三级页表)可以分配2M的大块内存,所以在创建页表时用了一个小技巧,即在创建L2级页表项时如果虚拟区间的开始地址(addr)、结束地址(next)和物理地址(phys)都与SECTION_SIZE大小(2MB)对齐并且没有设置NO_BLOCK_MAPPINGS标志位,那么直接设置段映射(section mapping)的页表项,不需要映射下一级页表,因为这几个地址都与SECTION_SIZE对齐就说明所需要的内存大于SECTION,直接按照SECTION分配即可,不用再往下细分到4KB,如下图(具体参考ARM64体系结构编程与实践P215):
-
页表创建完之后设置所要用到的几种内存的内存属性,写入MAIR_EL1寄存器中,并设置页面粒度为4KB,ID_AA64MMFR0_EL1寄存器中含有当前支持的页面粒度信息
-
而后将L0页表基地址写入TTBR0_EL1中,打开MMU,注意在打开MMU前后使用isb()指令冲刷流水线
建立完页表并打开MMU后,可以正常访问已经建立了VA到PA映射的地址,但如果访问没有建立页表映射的地址则会导致同步异常。
8.ldxr和stxr可以进行原子访问,但是使用时有很多限制,参考ARM®Cortex®-A Series(Programmer’s Guide for ARMv8-A)手册的14.1.4节,如下图所示:
只有在所访问的存储器满足:可内部或外部共享、内部回写、外部回写、读写分配命中、非暂态等条件时才能确保这两个指令可正常使用。当没有打开MMU时,访问normal内存会被当成是访问device 内存,这两个指令无法使用。如果仅仅enable了MMU,而没有设置上述属性(具体设置为:通过SCTLR_EL1寄存器的C位打开data cache、通过TCR_EL1寄存器的SH0、SH1、IRGN0、IRGN1、ORGN0、ORGN1位打开外部共享、内部回写、外部回写等属性),这两个指令也无法正常使用。
9.除了通过MMU自动进行虚拟地址到物理地址的转换外,还可以通过AT指令实现两个地址间的转换。AT指令用法如下图所示:
语法格式为AT <operation>, <Xt>,例如上图中s1e1r表示第一阶段地址转换、EL1、读取操作,X0存放了待转换的虚拟地址值,具体可参考ARMv8手册第D5.2.11节,如果转换成功读取的物理地址和内存相关属性信息会被存放在PAR_EL1寄存器中,该寄存器结构如下图所示: