内存管理描述了如何控制对系统中内存的访问。每次OS(操作系统)或应用程序访问内存时,硬件都会执行内存管理。内存管理是一种为应用程序动态分配内存区域的方法。
应用程序处理器被设计为运行一个丰富的操作系统,如Linux,并支持虚拟内存系统。在处理器上执行的软件只能看到虚拟地址,而处理器会将其转换为物理地址。这些物理地址被呈现给内存系统,并指向内存中的实际物理位置。
在AArch64架构中需要内存管理的几个主要原因:
虚拟内存: 内存管理支持虚拟内存技术,将逻辑地址空间映射到物理内存上。每个进程都有自己的虚拟地址空间,使得进程之间彼此隔离并且可以独立运行。内存管理负责将虚拟地址转换为对应的物理地址,以实现地址映射和访问控制。
内存分配与回收: 内存管理负责动态分配和回收内存资源。它跟踪哪些内存块已被分配给进程,并在进程不再需要时将其释放回可用内存池。这样可以避免内存浪费和耗尽,并确保内存资源的高效利用。
内存保护: 内存管理通过页表等机制,为每个进程或应用程序设置访问权限和保护机制。这样可以防止进程之间的互相干扰或访问其他进程的内存空间,从而提高系统的安全性和稳定性。
内存映射: 内存管理负责将外部设备(如显卡、网络接口等)的物理内存映射到系统内存空间中。这样,设备可以直接访问系统内存,实现高效的数据传输和交互。
页面置换: 内存管理通过页面置换算法,管理可用内存和磁盘之间的数据交换。当物理内存不足时,会使用页面置换机制将一些页从内存移到磁盘上,并将需要的页重新调入内存。这样可以扩大可用内存空间,满足更多进程的需求。
总之,AArch64架构中的内存管理在系统层面上负责有效地分配、保护和优化物理内存资源。它为每个进程提供独立的虚拟地址空间,管理内存分配和回收,确保内存保护和安全,支持设备内存映射,并通过页面置换机制增加系统的可用内存空间。这些功能都是为了提高系统的性能、稳定性和安全性。
使用虚拟地址的好处
在实际发开中,每个应用程序都可以使用自己的虚拟地址集,这些地址将映射到物理系统中的不同位置。当操作系统在不同的应用程序之间切换时,它会重新编程映射关系。这意味着当前应用程序的虚拟地址将映射到内存中真实物理位置。
虚拟地址通过映射被转换为物理地址。虚拟地址和物理地址之间的映射存储在转换表(有时也称为页表)中,如下图所示:
转换表(Translation Tables)在内存中,由软件管理,通常是操作系统或管理程序。转换表不是静态的,这些表是根据软件更改而更新。这将更改虚拟地址和物理地址之间的映射。
下图显示了在AArch64中的虚拟地址空间(以Armv8-A为例):
该表展示了三个虚拟地址空间
关于Secure,Non-secure. EL0~EL3将会放到Exception章节博客讲解
上述每个虚拟地址空间都是独立的,并且都有自己的设置和表。我们经常称这些设置和表为“translation regimes(转换机制)”。Secure EL0、Secure EL1和Secure EL2也有虚拟地址空间,但它们没有显示在图中。
在Armv8.4-A中添加了Secure EL2
因为存在多个虚拟地址空间,所以指定地址所在的地址空间很重要。例如,NS.EL2:0x8000是指在Non-secure EL2虚拟地址空间中的地址0x8000。
该图还显示了来自Non-secure EL0和Non-secure EL1的虚拟地址都经过了两组表(Guest OS Tables, Virtulization Tables)。这些表支持虚拟化,并允许hypervisor(管理程序?该单词翻译出来有点抽象,实际上就是一种模式,下面出现这个单词不在解释)虚拟化虚拟机(VM)所看到的物理内存。
Armv9-A支持上述针对Armv8-A的所有虚拟地址空间。Armv9-A引入了可选的领域管理扩展(Realm Management Extension,RME)。当实现RME时,还存在额外的转换机制:
关于RME将会放到RME章节博客讲解
在虚拟化中,我们将由操作系统(OS)控制的转换集(set of translations)称为阶段1(Stage 1)。阶段1的表将虚拟地址转换为中间物理地址(IPAs)。在阶段1中,操作系统(OS)认为IPAs是物理地址空间。然而,hypervisor控制着第二组转换,我们称之为阶段2(Stage 2)。这第二组将IPAs转换为物理地址。下图显示了这两组转换的工作原理:
虽然表格格式有一些细微的差异,但阶段1和阶段2的转换过程通常是相同的。
在Arm中,许多示例中都使用了地址0x8000。0x8000也是Arm链接器(armlink)链接的默认地址。这个地址来自一台早期的微型计算机,BBC Micro Model B,它有ROM(和sideways(横向?) RAM)在地址0x8000。BBC Micro Model B是由一家名为Acorn的公司制造的,该公司开发了Acorn RISC Machine(ARM),后来成为Arm。
除了多个虚拟地址空间外,AArch64还具有多个物理地址空间(physical address spaces PAS):
当处于具有多个物理地址空间可见的安全状态时,转换表入口处(translation table
entries)控制使用哪个输出物理地址空间。下图显示了多个物理地址空间的映射:
AArch64是一个64位的体系结构,但这并不意味着所有的地址都是64位的。
虚拟地址以64位的格式存储。因此,加载指令(LDR)和存储指令(STR)中的地址总是在X寄存器中指定。但是,并非X寄存器中的所有地址都有效。
下图显示了AArch64中的虚拟地址空间的布局:
EL0/EL1虚拟地址空间有两个区域:内核空间和应用程序空间。这两个区域显示在图的左侧,顶部是内核空间(Kernel Space),以及底部的应用程序空间,它被标记为“用户空间(User Space)”。内核空间和用户空间有单独的转换表,这意味着它们的映射可以保持独立。
对于所有其他异常级别而言,在地址空间的底部有一个单独的区域。这个区域显示在图表的右侧,是一个没有内容的box(方框?)。
如果设置HCR_EL2.E2H=1,则将使能运行在EL2中的host OS(主机操作系统?)配置,以及运行在EL0中的应用配置。在这种情况下,EL2也有一个上区域和一个下区域(region)。
每个地址空间的区域大小达52位。然而,每个区域都可以独立地缩小到一个更小的尺寸。TCR_ELx寄存器中的TnSZ位控制着虚拟地址空间的大小。例如,下图显示了TCR_EL1控制EL0/EL1虚拟地址空间:
虚拟地址的大小编码计算公式:
virtual address size in bytes = 264 -TCR_ELx.TnSZ
虚拟地址的大小也可以表示为地址位的数量:
Number of address bits = 64 - TnSZ
因此,如果将TCR_EL1.T1SZ设置为32,则EL0/EL1虚拟地址空间中的内核区域的大小为232字节(0xFFFF_FFFF_0000_0000到0xFFFF_FFFF_FFFF_FFFF)。任何超出配置范围或范围的地址在访问时将产生异常。这种配置的优点是,我们只需要描述尽可能多的地址空间,这就节省了时间和空间。例如,假设OS内核需要1GB的地址空间(30位的地址大小)来实现其内核空间。如果操作系统将T1SZ设置为34,则只创建1GB的转换表入口处(translation table
entries),如64 - 34 = 30。
所有的Armv8-A实现都支持48位的虚拟地址。支持52位虚拟地址是可选的,并由ID_AA64MMFR2_EL1报告。但是,没有一个Arm Cortex-A处理器支持52位虚拟地址。
物理地址的大小是IMPLEMENTATION DEFINED(实现定义)的,最多52位。ID_AA64MMFR0_EL1寄存器报告大小由处理器实现。对于Arm Cortex-A处理器,通常是40位或44位。
在Armv8.0-A中物理地址的最大是48位。在Armv8.2-A中被扩展到52位。
如果在转换表入口处(translation table entries)指定的输出地址大于实现的最大值,内存管理单元(MMU)将产生地址大小异常。
IPA空间的大小可以和虚拟地址空间相同的方式进行配置。VTCR_EL2.T0SZ控制大小。可以配置的最大大小与处理器支持的物理地址大小相同。这意味着你不能配置比所支持的物理地址空间更大的IPA空间。即中间物理地址大小必须小于等于物理地址大小。
许多现代操作系统(OS)的应用程序似乎都运行在同一个地址区域,这就是我们所描述的用户空间。在实践中,不同的应用程序需要不同的映射。这意味着,例如,VA 0x8000的翻译取决于当前正在运行的哪个应用程序。
理想情况下,我们希望不同应用程序的转换在转换查找缓冲区(Translation
Lookaside BuffersT, LBs)中共存,以防止在上下文切换上需要TLB失效。但是处理器如何知道要使用哪个版本的VA 0x8000翻译呢?在AArch64中,答案是地址空间标识符(Address Space Identifiers, ASIDs)。
对于EL0/EL1虚拟地址空间,可以使用转换表项的属性字段中的nG位将转换标记为全局(G)或非全局(nG)。例如,内核映射是全局转换,应用映射是非全局转换。全局转换应用于当前正在运行的任何应用程序。非全局转换仅适用于特定的应用程序。
非全局映射在TLBs中用ASID进行标记。在TLB查找中,将TLB入口中的ASID与当前选择的ASID进行比较。如果它们不匹配,则不使用TLB入口。下图显示了内核空间中没有ASID标记的全局映射和用户空间中带有ASID标记的非全局映射:
上图显示了多个应用程序的TLB入口允许在缓存中共存,并且ASID决定使用哪个入口。
ASID存储在两个TTBRn_EL1寄存器中的一个中。TTBR0_EL1通常用于用户空间。因此,单个寄存器更新可以同时更改ASID和它所指向的转换表。
在2023年,Arm引入了同时指定两个ASIDs的能力。从Armv9.5-A中,软件可以选择在两个TTBRn_EL1寄存器中使用ASID字段。TTBR0_EL1.ASID应用于虚拟地址空间的下部的地址和TTBR1_EL1.ASID到虚拟地址空间上部的地址。
ASID标记也可以在EL2中使用,通过设置HCR_EL2.E2H = 1
EL0/EL1的转换也可以用虚拟机标识符(VMID)进行标记。VMIDs允许来自不同VM的转换在缓存中共存。这类似于ASID转换来自不同应用程序的工作方式。在实际中,这意味着一些转换将同时被标记为VMID和ASID,并且两者都必须匹配才能使用TLB入口。
当安全状态支持虚拟化时,EL0/EL1转换总是用VMID标记,即使没有启用阶段2(Satge 2)转换也是如此。这意味着,如果您正在编写初始化代码而没有使用hypervisor(系统管理程序?),那么在设置Stage 1 MMU之前设置一个已知的VMID值是很重要的。
如果一个系统包含多个处理器,那么在一个处理器上使用的ASIDs和VMIDs在其他处理器上是否具有相同的含义?
对于Armv8.0-A,它们不必意味着相同的东西。这里没有要求软件在多个处理器上以相同的方式使用给定的ASID。例如,ASID 5可能被一个处理器上的计算器使用,可能被另一个处理器上的web浏览器使用。这意味着由一个处理器创建的TLB入口不能被另一个处理器使用。
在实践中,软件不太可能在不同的处理器之间使用不同的ASIDs。软件在给定系统中的所有处理器上以相同的方式使用ASIDs和VMIDs更为常见。因此,Armv8.2-A在转换表基寄存器(Translation Table
Base RegisterT,TBR)中引入了通用非私有(Common not Private,CnP)位。当设置了CnP位时,该软件允许在所有处理器上以相同的方式使用ASIDs和VMIDs,这允许由一个处理器创建的TLB入口被另一个处理器使用。
我们一直在谈论处理器,但是,从技术上讲,我们应该使用处理单元(Processing Element, PE)这个术语。PE是对实现Arm架构的任何机器的通用术语。这很重要,因为在处理器之间共享TLBs是困难的。但是在一个多线程处理器中,每个硬件线程都是一个PE,它更希望共享TLB入口。