快进到内存管理模块吧,其他的后面再补充~
目录
1.1 TLB (Translation Lookaside Buffer)
1.2 Kernel 和 Application 虚拟地址空间
1.4.3 缓存配置(Cache configuration)
1.5.1 虚拟地址标记(Virtual Address Tagging)
1.10 上下文转换 (Context switching)
内存管理单元(MMU)允许任务或应用程序以一种不需要了解系统物理内存映射,或者可能同时运行的其他程序的方式编写。这允许您为每个程序使用相同的虚拟内存地址空间。
它还允许您即使物理内存是碎片化的,也能使用一个连续的虚拟内存映射。这个虚拟地址空间与系统的实际物理内存映射是分开的。应用程序是在虚拟内存空间中编写、编译和链接的。
1.1 TLB (Translation Lookaside Buffer)
(Translation Lookaside Buffer,简称TLB)是内存管理单元(MMU)中最近访问过的页面转换的缓存。处理器执行的每次内存访问,MMU都会检查TLB中是否缓存了该转换。如果请求的地址转换在TLB中命中,那么地址的转换立即可用。
每个TLB条目通常不仅包含物理和虚拟地址,还包含诸如内存类型、缓存策略、访问权限、地址空间标识符(Address Space ID,简称ASID)和虚拟机标识符(Virtual Machine ID,简称VMID)等属性。如果TLB不包含处理器发出的虚拟地址的有效转换,这种情况被称为TLB未命中,那么将执行外部转换表遍历或查找。MMU内的专用硬件使其能够读取内存中的转换表。如果转换表遍历没有导致页面错误,那么新加载的转换可以缓存在TLB中,以便可能的重用。
TLB的确切结构在ARM处理器的不同实现之间有所不同。
如果操作系统修改了可能已缓存在TLB中的转换条目,那么操作系统就有责任使这些陈旧的TLB条目失效。
在执行A64代码时,有一个TLBI指令,即TLB失效指令。
这段描述解释了TLB的作用、它的条目包含的内容、当TLB未命中时会发生什么,以及操作系统如何管理TLB中的条目。
TLB可以容纳一个固定数量的条目。通过最小化由转换表遍历引起的外部内存访问次数,并获取高TLB命中率,可以实现最佳性能。ARMv8-A架构提供了一种称为连续块条目的特性,以高效使用TLB空间。转换表块条目每个都包含一个连续位。当设置此位时,它向TLB发出信号,表明它可以缓存一个条目,覆盖多个块的转换。查找可以索引到由连续块覆盖的地址范围内的任何位置。因此,TLB可以缓存一个条目,用于定义的地址范围,使得在TLB中存储的虚拟地址范围比通常可能的要大。
要使用连续位,连续块必须是相邻的,即它们必须对应于连续的虚拟地址范围。它们必须从对齐边界开始,具有一致的属性,并指向相同转换级别的连续输出地址范围。所需的对齐是虚拟地址的VA[20:16]对于4KB粒度或VA[28:21]对于64KB粒度,对于所有地址都是相同的。需要以下数量的连续块:
- 16个×4KB相邻块,给出一个64KB条目,粒度为4KB。
- 32个×32MB相邻块,给出一个1GB条目,用于L2描述符;128个×16KB给出一个2MB条目,用于L3描述符,当使用16KB粒度时。
- 32个×64KB相邻块,给出一个2MB条目,粒度为64KB。
如果这些条件没有得到满足,将发生编程错误,可能导致TLB中止或查找损坏。此类错误可能的例子包括:
- 一个或多个表条目没有设置连续位。
- 其中一个条目的输出指向对齐范围之外。
在ARMv8架构中,不正确的使用不允许权限检查超出EL0和EL1有效地址空间,或者错误地提供对EL3空间的访问。
1.2 Kernel 和 Application 虚拟地址空间
操作系统通常有多个应用程序或任务同时运行。每个任务都有其独特的转换表集合,内核在任务之间切换时,会从一个转换表切换到另一个。然而,内存系统的大部分只由内核使用,并且具有固定的虚拟到物理地址映射,转换表条目很少更改。ARMv8架构提供了一些特性来高效处理这一要求。
转换表的基地址在转换表基址寄存器(TTBR0_EL1)和(TTBR1_EL1)中指定。当虚拟地址(VA)的高位全部为0时,选择由TTBR0指向的转换表。当虚拟地址的高位全部设置为1时,选择TTBR1。您可以启用虚拟地址标记,以排除最高8位的检查。
处理器的指令获取或数据访问的虚拟地址是64位。然而,您必须在单个48位物理地址内存映射内映射上述两个区域。
EL2和EL3具有TTBR0,但没有TTBR1。这意味着:
- 如果EL2使用AArch64,它只能使用范围在0x0到0x0000FFFF_FFFFFFFF的虚拟地址。
- 如果EL3使用AArch64,它只能使用范围在0x0到0x0000FFFF_FFFFFFFF的虚拟地址。
显示了如何将内核空间映射到内存的最高有效区域,以及与每个应用程序关联的虚拟地址空间映射到内存的最低有效区域。然而,这两个都被映射到一个更小的物理地址空间。
控制寄存器TCR_EL1定义了要检查的最高有效位的确切数量。TCR_EL1包含大小字段T0SZ[5:0]和T1SZ[5:0]。字段中的整数给出了必须全部为0或全部为1的最高有效位的数量。这些字段有指定的最小值和最大值,这些值会随着粒度大小和起始表级别而变化。因此,您必须始终使用这两个空间,所有系统中至少需要两个转换表。即使是没有操作系统的简单裸机系统,仍然需要一个小的上层表,其中只包含错误条目。
TCR_EL1控制EL1和EL0的其他内存管理特性。
中间物理地址大小(IPS)字段控制最大输出地址大小。如果转换指定的输出地址超出此范围,则访问将出现故障,000=32位物理地址,101=48位。
两位转换粒度(TG)TG1和TG0字段分别给出内核或用户空间的粒度大小,00=4KB,01=16KB,11=64KB。
您可以配置用于第一次查找的转换表级别。完整的转换过程可能需要三个或四个级别的表。您不必实现所有级别。
第一次查找的第一级,实际上是由粒度大小和TCR_ELn.TxSZ字段决定的。可以分别为TTBR0_EL1和TTBR1_EL1进行配置。
1.3 转换虚拟地址到物理地址
当处理器发出一个64位虚拟地址用于指令获取或数据访问时,MMU硬件将虚拟地址转换为相应的物理地址。对于一个虚拟地址,最高16位[63:47]必须全部为0或1,否则地址将触发故障。
最低有效位随后用于在选定的节区内给出偏移量,因此MMU将块表条目中的物理地址位与原始地址的最低有效位结合起来,产生最终地址。
该架构还支持标记地址。这意味着地址的最高8位被忽略(被视为不是地址的一部分)。这意味着这些位可以用于其他目的,例如,记录有关指针的信息。
在实践中,这种简单的转换过程严重限制了您如何精细地划分地址空间。与其仅使用第一级转换表,第一级表条目也可以指向第二级页表。
通过这种方式,操作系统可以将一个较大的虚拟内存区进一步划分为更小的页面。对于第二级表,第一级描述符包含第二级页表的物理基地址。处理器请求的虚拟地址对应的物理地址是在第二级描述符中找到的。
一个具有两级查找的内存地址转换过程,假设使用的是64KB的粒度和42位虚拟地址空间。以下是详细步骤:
-
如果虚拟地址(VA)的[63:42]位全部为1,则使用TTBR1作为第一级页表的基地址。如果VA的[63:42]位全部为0,则使用TTBR0作为第一级页表的基地址。
-
页表包含8192个64位的页表条目,通过VA的[41:29]位进行索引。MMU从表中读取相关的第二级页表条目。
-
MMU检查第二级页表条目的有效性以及是否允许请求的内存访问。假设它是有效的,并且允许内存访问。
-
在第二级页表条目引用了第三级页表的地址(它是一个表描述符)。
-
从第二级页表条目中取出[47:16]位,形成第三级页表的基地址。
-
使用VA的[28:16]位来索引第三级页表条目。MMU从表中读取相关的第三级页表条目。
-
MMU检查第三级页表条目的有效性以及是否允许请求的内存访问。假设它是有效的,并且允许内存访问。
-
第三级页表条目引用了一个64KB的页面(它是一个页面描述符)。
-
从第三级页表条目中取出[47:16]位,并用来形成物理地址(PA)的[47:16]位。
-
因为我们有一个64KB的页面,所以取VA的[15:0]位来形成PA的[15:0]位。
-
返回完整的PA[47:0],同时附带页表条目中的其他信息。
1.3.1 安全和非安全地址空间
理论上,安全(Secure)和非安全(Non-secure)的物理地址空间是相互独立的,并行存在。系统可以设计成拥有两个完全独立的内存系统。然而,大多数实际系统将安全和非安全作为访问控制的属性来处理。普通(非安全)世界只能访问非安全物理地址空间。安全世界可以访问两个物理地址空间。同样,这是通过转换表来控制的。
从技术上讲,安全(Secure)的0x8000和非安全(Non-secure)的0x8000是不同的物理地址,它们可以同时存在于缓存中。
在一个安全和非安全内存位于不同位置的系统中,不会有问题。更有可能的是它们位于相同的位置。理想情况下,内存系统会阻止安全访问非安全内存和非安全访问安全内存。实际上,大多数系统只阻止非安全访问安全内存。这意味着你最终可能会在缓存中两次存储相同的物理内存,即安全和非安全的。这始终是一个编程错误。为了避免这种情况,安全世界必须始终使用非安全访问来访问非安全内存。
1.3.2 配置和使能MMU
对控制MMU的系统寄存器的写操作是上下文更改事件,它们之间没有排序要求。这些事件的结果在上下文同步事件之前不保证可见。
这与平坦映射(flat mapping)的要求是分开的,平坦映射是为了确保我们知道在写入SCTLR_EL1.M寄存器后直接执行的是哪条指令。如果我们看到了写入操作的结果,那么执行的指令是使用新的转换机制的VA+4处的指令。如果我们没有看到写入操作的结果,那么执行的指令仍然是VA+4处的指令,但此时VA等于PA(物理地址)。在这种情况下,ISB(指令同步屏障)指令在这里没有帮助,因为我们不能保证它是下一个执行的指令,除非我们进行平坦映射。
1.3.3 当MMU没有使能的时候
1.4 ARMv8-A的页表
ARMv8-A架构提供了三种不同的转换表格式支持:
-
ARMv8-A AArch64长描述符格式:这是ARMv8-A架构提供的长描述符格式,用于AArch64执行状态。它使用与ARMv7-A长描述符格式相同的64位长描述符格式,但进行了一些修改。它引入了一个新的级别0表索引,使用与级别1表相同的描述符格式,并增加了对高达48位输入和输出地址的支持。输入的虚拟地址现在来自一个64位寄存器。然而,由于架构不支持完整的64位寻址,地址的63:48位必须全部相同,即全部为0或全部为1,或者最高8位可以用于虚拟地址标记。
-
ARMv7-A长描述符格式:这种格式是ARMv7-A架构的扩展,例如在ARM Cortex-A15处理器中找到的Large Physical Address Extension (LPAE)。
-
ARMv7-A短描述符格式:这种格式仅用于EL0和EL1阶段的转换,因此不能被虚拟机监视器或安全监控代码使用。
在AArch32状态下,可以使用现有的ARMv7-A长和短描述符格式来运行现有的客户操作系统和现有应用程序代码,而无需修改。ARMv8-A长描述符格式在AArch64执行状态下使用,与ARMv7-A长描述符格式非常相似,但有一些变化。
AArch64支持三种不同的转换粒度:4KB、16KB和64KB,具体支持哪些由实现定义。创建页表的代码可以读取系统寄存器ID_AA64MMFR0_EL1,以找出支持哪些大小。例如,Cortex-A53处理器支持所有三种大小,但并非所有处理器都支持,例如早期版本的Cortex-A57不支持16KB粒度大小。每个转换表的大小可以在翻译控制寄存器(TCR_EL1)中配置。
1.4.1 AArch64描述符格式
您可以在从第0级到第3级的所有级别表中使用描述符格式。第0级描述符只能输出第1级表的地址。第3级描述符不能指向另一个表,只能输出块地址。因此,第3级的表格格式略有不同。
表描述符类型由条目的第1:0位识别,并且可以引用:
- 下一级表的地址,在这种情况下内存可以进一步细分为更小的块。
- 可变大小内存块的地址。
- 表条目,可以标记为故障或无效。
1.4.2 转换页表颗粒度大小的影响
三种不同的粒度大小可以影响所需的转换表的数量和大小。
注意:在所有情况下,如果虚拟地址(VA)输入范围限制在42位以内,您可以省略第一级表。根据可能的VA范围的大小,可能需要更少的级别。例如,使用4KB粒度,如果TTBCR设置为低地址仅跨越1GB,那么不需要第0级和第1级,转换从第2级开始,到第3级为4KB页面。
4KB
当您使用4KB粒度大小时,硬件可以使用4级查找过程。
48位地址有每级9位地址被转换,也就是每级512个条目,最后的12位直接从原始地址中选择4KB内的一个字节。
虚拟地址的47:39位索引到512个条目的L0表中。这些表条目每个跨越512GB的范围,并指向L1表。在这个512个条目的L1表中,38:30位被用作索引来选择一个条目,每个条目指向一个1GB的块或者L2表。29:21位索引到一个512个条目的L2表中,每个条目指向一个2MB的块或者下一个表级别。在最后一级,20:12位索引到一个512个条目的L2表中,每个条目指向一个4KB的块。
16KB
当您使用16KB粒度大小时,硬件可以采用4级查找过程。48位地址每级翻译有11位地址,即每级有2048个条目,最后的14位直接从原始地址中选择16KB内的一个字节。第0级表只包含两个条目。虚拟地址的第47位选择两个条目L0表中的一个描述符。这些表条目每个跨越128TB的范围,并指向L1表。在那个2048个条目的L1表中,使用35:25位作为索引来选择一个条目,每个条目指向L2表。35:25位索引到一个2048个条目的L2表中,每个条目指向一个32MB的块或者下一个表级别。在最后的翻译阶段,24:14位索引到一个2048个条目的L2表中,每个条目指向一个16KB的块。
64KB
当您使用64KB粒度大小时,硬件可以采用3级查找过程。第1级表只包含64个条目。 虚拟地址的47:42位选择64个条目L1表中的一个描述符。 这些表条目每个跨越4TB的范围,并指向L2表。在这个8192个条目的L2表中,使用41:29位作为索引来选择一个条目,每个条目指向一个512MB的块或者下一个表级别。在最后的翻译阶段,28:16位索引到一个8192个条目的L3表中,每个条目指向一个64KB的块。
1.4.3 缓存配置(Cache configuration)
MMU(内存管理单元)使用转换表和转换寄存器来控制哪些内存位置是可缓存的。MMU控制缓存策略、内存属性和访问权限,并提供虚拟地址到物理地址的转换。
1.4.4 缓存策略(Cache policies)
MMU的转换表还定义了内存系统中每个块的缓存策略。被定义为正常的内存区域可能被标记为可缓存或不可缓存。转换表条目中的[4:2]位引用了内存属性间接寄存器(MAIR)中的八种内存属性编码之一。然后,内存属性编码指定了访问该内存时使用的缓存策略。这些是对处理器的提示,并且是否支持所有缓存策略以及哪些缓存数据被视为一致的,这由具体实现定义(IMPLEMENTATION DEFINED)。可以根据其共享属性来定义内存区域。
1.5 页表配置
在ARMv8架构中,除了在TLB中存储单个映射项外,MMU可以配置为将转换表存储在可缓存的内存中,这通常会比总是从外部内存读取转换表提供更快的访问速度。TCR_EL1寄存器有额外的字段来控制这一功能。这些附加字段指定了TTBR0和TTBR1的转换表的可缓存性和可共享性。相关的字段被称为SH0/1 Shareability(共享性)、IRGN0/1 Inner Cacheable(内部可缓存性)和ORGN0/1 Outer Cacheable(外部可缓存性)。表12-2展示了可缓存性的允许设置。
- SH0/1 Shareability:决定了转换表本身的共享性属性。
- IRGN0/1 Inner Cacheable:决定了转换表本身的内部可缓存性属性。
- ORGN0/1 Outer Cacheable:决定了转换表本身的外部可缓存性属性。
这些属性必须与存储转换表的虚拟内存区域指定的属性相匹配。默认情况下,映射表是可缓存的。
在ARMv8架构中,内存的共享性(Shareability)是与内存属性(Memory Attributes)相关联的,并且与转换表的遍历(translation table walks)有关。对于设备(Device)或强有序(strongly-ordered)的内存区域,共享性属性的值会被忽略
在TCR_EL1中指定的属性必须与存储转换表的虚拟内存区域指定的属性相同。缓存转换表是默认的正常行为。
1.5.1 虚拟地址标记(Virtual Address Tagging)
TCR_ELn有一个额外的字段叫做最高字节忽略(TBI),它提供了标记寻址支持。通用寄存器是64位宽,但是地址的最高16位必须是全部0xFFFF或0x0000。任何尝试使用不同的位值都会触发一个故障。
当启用标记寻址支持时,处理器会忽略虚拟地址的最高的8位,也就是[63:56]。它在内部将位[55]设置为符号扩展地址到64位格式。虚拟地址的最高8位随后可以用来传递数据。这些位在寻址和转换故障中被忽略。TCR_EL1对EL0和EL1有单独的启用位。ARM没有规定或授权标记寻址的具体使用情况。
一个示例用例可能是支持面向对象的编程语言。除了有一个指向对象的指针外,可能还需要保留一个引用计数,以跟踪指向对象的引用或指针或句柄的数量,例如,这样自动垃圾收集代码就可以取消分配不再被引用的对象。这个引用计数可以作为标记地址的一部分来存储,而不是在一个单独的表中,这样可以加快创建或销毁对象的过程。
1.6 EL2和EL3的转换
ARMv8-A架构的虚拟化扩展引入了第二级地址翻译。当系统中存在 hypervisor 时,可能会有一个或多个客户操作系统。这些操作系统继续使用如前所述的TTBRn_EL1,并且MMU的操作看起来没有变化。
hypervisor 必须在两阶段过程中执行一些额外的翻译步骤,以便在不同的客户操作系统之间共享物理内存系统。在第一阶段,虚拟地址(VA)被翻译成中间物理地址(IPA)。这通常处于操作系统控制之下。第二阶段由 hypervisor 控制,然后执行将 IPA 翻译成最终的物理地址(PA)的操作。
hypervisor 和安全监控器也有自己的第一级翻译表集,用于它们自己的代码和数据,这些表直接从 VA 映射到 PA。架构参考手册使用“翻译机制”这个术语来指代这些不同的表。
在两级翻译过程中,第一阶段通常由客户操作系统完成,将虚拟地址翻译成中间物理地址。第二阶段由 hypervisor 完成,将中间物理地址翻译成最终的物理地址。这样可以确保不同的客户操作系统能够共享物理内存,同时保持它们各自的地址空间隔离。这种机制对于实现有效的虚拟化至关重要,它允许 hypervisor 管理和控制对物理内存的访问,同时为客户提供了一个一致和隔离的地址空间环境。
在ARMv8-A架构中,第二级翻译(stage 2 translations)用于将中间物理地址(Intermediate Physical Address, IPA)转换为最终的物理地址(Physical Address)。这一过程涉及由hypervisor控制的额外一组转换表。对于非安全(Non-secure)EL1/0访问,必须通过向Hypervisor Configuration Register(HCR_EL2)写入来显式启用这些转换表。
第二级翻译表的基地址在Virtualization Translation Table Base Register(VTTBR0_EL2)中指定。它在内存的底部指定了一个单一的连续地址空间。支持的地址空间大小在Virtualization Translation Control Register(VTCR_EL2)的T0SZ[5:0]字段中指定。
VTCR_EL2寄存器中的TG字段指定了颗粒大小,而SL0字段控制表查找的第一级。任何超出定义地址范围的访问都会导致翻译故障(translation fault)。
在实际应用中,操作系统(OS)需要确保在转换表被修改后,及时使相应的TLB项失效,以避免过时的TLB项导致错误。ARM架构提供了TLB invalidate指令(TLBI),用于在执行A64代码时刷新TLB中的条目。
在ARMv8-A架构中,hypervisor运行在EL2(Exception Level 2),而Secure monitor运行在EL3。它们都有自己的第一级翻译表,这些翻译表直接将虚拟地址映射到物理地址空间。这些翻译表的基地址分别在TTBR0_EL2和TTBR0_EL3中指定,允许在内存底部启用一个单一连续的可变大小的地址空间。
TG字段指定了颗粒大小,而SL0字段控制第一级表查找。任何超出定义地址范围的访问都会导致翻译故障(translation fault)。
这意味着hypervisor和Secure monitor可以有自己的独立地址空间,用于映射它们的代码和数据,而不受普通世界(EL0/EL1)的影响。这种设计允许在虚拟化环境中,hypervisor可以有效地管理和控制对物理内存的访问,同时为不同的客户操作系统提供隔离的地址空间。Secure monitor则可以访问和控制安全相关的内存区域。
在ARMv8-A架构中,Secure Monitor运行在EL3,它拥有自己专用的一级翻译表。这些翻译表的基地址通过TTBR0_EL3指定,并通过TCR_EL3进行配置。这些翻译表能够直接将虚拟地址映射到物理地址空间。TTBR0_EL3仅在Secure Monitor EL3模式下使用,而不是由受信的内核本身使用。当转换到安全世界完成后,受信的内核将使用EL1的翻译,即由TTBR0_EL1和TTBR1_EL1指向的翻译表。由于这些寄存器在AArch64中不是分页的,因此Secure Monitor代码必须为安全世界配置新的表,并保存和恢复TTBR0_EL1和TTBR1_EL1的副本。
在安全状态下,EL1翻译机制的行为与其在非安全状态下的正常操作不同。第二级翻译被禁用,EL1翻译机制现在能够指向安全或非安全的物理地址。在安全世界中没有虚拟化,因此中间物理地址(IPA)始终与最终的物理地址(PA)相同。TLB中的条目被标记为安全或非安全,因此在安全和普通世界之间转换时,永远不需要进行TLB维护。
1.7 访问权限
在ARMv8-A架构中,访问权限是通过转换表条目来控制的。这些访问权限决定了某个区域是否可以被读取、写入,或者两者都可以。权限可以分别为EL0(非特权级别)和EL1、EL2、EL3(特权级别)设置。
操作系统内核在执行级别EL1下运行。它定义了转换表映射,这些映射不仅被内核自身使用,也被在EL0下运行的应用程序使用。区分非特权和特权访问权限是必要的,因为内核为自己的代码和应用程序指定了不同的权限。运行在执行级别EL2的hypervisor和EL3的Secure Monitor仅拥有自己的转换方案,因此不需要在权限中进行特权和非特权的分割。
另一种访问权限是可执行属性。内存块可以被标记为可执行或不可执行(执行永不(XN))。你可以分别设置非特权执行永不(UXN)和特权执行永不(PXN)属性,并使用这些属性来阻止,例如,具有内核权限的应用程序代码的执行,或者在非特权状态下尝试执行内核代码。设置这些属性可以防止处理器对内存位置进行推测性的指令获取,并确保推测性的指令获取不会意外访问可能因此类访问而受到干扰的位置,例如,先进先出(FIFO)页面替换队列。因此,设备区域必须始终被标记为执行永不。
这些访问控制机制确保了系统的安全性和稳定性,防止了未授权的代码执行,同时也为操作系统提供了灵活的内存管理能力。通过这种方式,操作系统可以为不同的用户空间和内核空间的代码和数据分配适当的访问权限,同时也为虚拟化环境中的hypervisor和Secure Monitor提供了必要的控制。
在ARMv8-A架构中,你可以通过配置处理器将可写区域视为执行永不(Execute Never,XN),这是通过在SCTLR(System Control Register)寄存器中的以下位来实现的:
-
SCTLR_EL1.WXN:在EL0可写的区域在EL0和EL1被视为XN。在EL1可写的区域在EL1被视为XN。
-
SCTLR_EL2 和 SCTLR_EL3.WXN:在ELn可写的区域在ELn被视为XN。
-
SCTLR.UWXN:在EL0可写的区域在EL1被视为XN。这仅适用于AArch32执行状态。
SCTLR_ELn位可以在TLB(Translation Lookaside Buffer)条目中被缓存。因此,更改SCTLR中的位可能不会影响已经存在于TLB中的条目。在修改这些位时,需要执行TLB失效和ISB(Instruction Synchronization Barrier)序列。
1.8 操作系统使用转换表描述符
在ARMv8-A架构中,描述符中的另一个内存属性位是访问标志(Access Flag,AF),它指示块条目是否已被使用过。这个标志通常用于操作系统来跟踪哪些页面正在被使用。软件管理这个标志。当页面首次创建时,其条目的AF设置为0。当页面第一次被代码访问时,如果它的AF为0,这会触发一个MMU(内存管理单元)故障。页面故障处理程序记录该页面现在正在被使用,并手动在表条目中设置AF位。
例如,Linux内核在ARM64(Linux内核对AArch64的称呼)上使用PTE_AF来表示AF位,它用于检查页面是否曾经被访问过。这影响了内核的一些内存管理决策。例如,当一个页面需要被交换出内存时,不大会交换那些正在积极使用的页面。
描述符的[58:55]位被标记为软件保留用途,可以用来在转换表中记录操作系统特定的信息。例如,Linux内核使用这些位中的一个来标记条目为干净或脏。脏状态记录页面是否被写入过。如果页面后来被交换出内存,干净的页面可以被直接丢弃,但是脏页面必须先保存其内容。
这些机制允许操作系统更有效地管理内存,通过跟踪页面的使用情况和脏状态,操作系统可以做出更合理的内存置换决策,例如,优先保留那些经常被访问的页面,以及在页面被交换出内存前确保脏页面的数据被写回到持久存储中。
1.9 安全和MMU
ARMv8-A架构定义了两种安全状态:安全(Secure)和非安全(Non-secure)。它还定义了两种物理地址空间:安全和非安全,这样普通世界(Normal world)只能访问非安全物理地址空间。安全世界可以访问安全和非安全物理地址空间。
在非安全状态下,转换表中的NS位和NSTable位被忽略。只能访问非安全内存。在安全状态下,NS位和NSTable位控制虚拟地址是否转换为安全或非安全物理地址。您可以使用SCR_EL3.CIF来防止安全世界从任何转换为非安全物理地址的虚拟地址执行。此外,当处于安全世界时,您可以使用SCR.CIF位来控制是否允许安全指令获取访问非安全物理内存。
这些机制确保了系统的安全性,允许安全世界对内存访问进行更细粒度的控制,同时限制非安全世界对敏感内存区域的访问。SCR_EL3寄存器中的CIF位是一个重要的控制位,它影响安全世界对物理内存的访问策略。通过这些控制位,操作系统可以在保持系统安全性的同时,灵活地管理内存资源。
1.10 上下文转换 (Context switching)
在实现ARMv8-A架构的处理器中,通常用于运行复杂的操作系统,这些系统同时运行许多应用程序或任务。每个进程都有自己的一组唯一的转换表,存储在物理内存中。当应用程序启动时,操作系统会为其分配一组转换表条目,这些条目将应用程序使用的代码和数据映射到物理内存。这些表随后可以被内核修改,例如,映射额外的空间,当应用程序不再运行时,这些表会被移除。
因此,内存系统中可能存在多个任务。内核调度器定期将执行从一个任务转移到另一个任务。这称为上下文切换,需要内核保存与进程相关的所有执行状态,并恢复下一个要运行的进程的状态。内核还需要将转换表条目切换到下一个要运行的进程的条目。当前不运行的任务的内存完全受到运行中任务的保护。
需要保存和恢复的内容在不同的操作系统之间有所不同,但通常,进程上下文切换包括保存或恢复以下一些或全部元素:
- 通用寄存器X0-X30。
- 高级SIMD和浮点寄存器V0-V31。
- 一些状态寄存器。
- TTBR0_EL1和TTBR0。
- 线程进程ID(TPIDxxx)寄存器。
- 地址空间ID(ASID)。
对于EL0和EL1,有两个转换表。TTBR0_EL1提供虚拟地址空间底部的翻译,这通常是应用程序空间,而TTBR1_EL1覆盖虚拟地址空间的顶部,通常是内核空间。这种分割意味着操作系统映射不需要在每个任务的转换表中复制。
转换表条目包含一个非全局(nG)位。如果为特定页面设置了nG位,则它与特定任务或应用程序相关联。如果该位被标记为0,则条目是全局的,适用于所有任务。
对于非全局条目,当TLB更新且条目被标记为非全局时,除了正常的翻译信息外,还会在TLB条目中存储一个值。这个值称为地址空间ID(ASID),由操作系统为每个单独的任务分配一个号码。后续的TLB查找只有在当前ASID与存储在条目中的ASID匹配时才会匹配该条目。这允许对于标记为非全局的特定页面,可以存在多个有效的TLB条目,但具有不同的ASID值。换句话说,我们在上下文切换时,不一定需要刷新TLB。
在AArch64中,ASID值可以指定为8位或16位的值,由TCR_EL1.AS位控制。当前的ASID值在TTBR0_EL1或TTBR1_EL1中指定。TCR_EL1控制哪个TTBR保存ASID,但通常是TTBR0_EL1,因为这对应于应用程序空间。
注意
在转换表寄存器中存储当前的ASID值意味着你可以在单条指令中原子性地修改转换表和ASID。这简化了与ARMv7-A架构相比,修改表和ASID的过程。
此外,ARMv8-A架构提供了供操作系统软件使用的线程ID寄存器。这些寄存器没有硬件意义,通常被线程库用作每个线程数据的基指针。这通常被称为线程局部存储(Thread Local Storage,TLS)。例如,pthreads库使用此特性,并包括以下寄存器:
- 用户读写线程ID寄存器(TPIDR_EL0)。
- 用户只读线程ID寄存器(TPIDRRO_EL0)。
- 线程ID寄存器(TPIDR_EL1,仅限特权访问)。
1.11 Kernal访问使用用户权限
在ARMv8-A架构中,存在一些指令,允许在EL1(例如,操作系统)执行的代码以EL0或应用程序权限进行内存访问。这可以用于例如解引用系统调用提供的指针,并使操作系统能够检查仅访问对应用程序可访问的数据。这可以通过使用LDTR(Load Register)或STTR(Store Register)指令来实现。当在EL1执行这些指令时,它们执行的加载或存储操作就好像是在EL0执行一样。在所有其他异常级别,LDTR和STTR的行为类似于常规的LDR或STR指令。
这些指令具有常规加载和存储指令的通常大小、有符号和无符号变体,但偏移量较小,索引选项受限。这意味着LDTR和STTR指令可以用于在保持EL1权限的同时,以EL0的权限级别访问内存,这对于操作系统在执行系统调用或处理用户空间指针时非常有用。
这些指令的具体使用方式如下:
- LDTR:以EL0的权限级别从指定地址加载数据到寄存器。
- STTR:以EL0的权限级别将数据从寄存器存储到指定地址
可以参考我其他的SMMU2的文章:
【ARM】SMMU系统虚拟化(4)_concatenated translation tables-CSDN博客
【ARM】SMMU系统虚拟化(3)_ VMSAv8-64 address translation stages_smmu 地址转换过程-CSDN博客