AbydOS开发日记 (4) - MMU 与内存布局

3/16/2024 OSC++MMU

# 关于内存

内存,即 Memory,是存储计算机运行过程数据的主要部件。不过在操作系统中,内存并不单单是主存,还有各种外设内存等。本文所说的内存布局,主要是指在可寻址空间中的布局,这关系到系统和后面应用程序的运行,是及其重要的。在有内存管理单元 (MMU) 的处理器上,寻址空间可以分为两种类型,即 物理地址空间 (Physical Memory Area, PMA) 和 虚拟地址空间 (Virtual Memory Area, VMA)。通过 MMU 的控制与转换,可以实现有效的隔离和保护,防止非法的内存访问。

本文仅浅读标准手册,未尽之处,敬请参阅标准手册。

# RISC-V 的地址空间

根据 The RISC-V Instruction Set Manual Volume II: Privileged Architecture (opens new window),目前 RV32 可用的物理地址空间为 32 位(或更多),而 RV64 可用的物理地址空间是 64 位。

而虚拟地址空间上,RV32 只有一种模式,即 SV32,对应了 32 位的虚拟地址空间,并且由于 MMU 配置上用的是 22位 的物理页号 (每页 4K, 即 12 位),能够支持到 34 位的物理地址空间。

但是,RV64 因为地址空间足够大,普通应用可能不需要完全的虚拟地址空间支持,截止 V20211203,一共定义了三种模式,即 SV39, SV48, SV57,分别对应 39 位、48 位、57 位虚拟地址空间。

# RISC-V 的 MMU

在标准文档中,MMU 被设计成一个带有 TLB 的硬件模块,实现可配置的从虚拟地址空间到物理地址空间的映射。由于原理相同,所以标准文档只对 SV32 作了详细描述,这里我们也同样先分析 SV32,然后推广到 SV39 等。

略语一览:

  • PPN: Physical Page Number, 物理页号
  • VPN: Virtual Page Number, 虚拟页号
  • PTE: Page Table Entry,页表项
  • TLB: Translation Lookaside Buffer, 页表缓存

# 控制

设计上,RV 的 MMU 由一个 CSR (控制与状态寄存器) 控制,名为 SATP (Supervisor Address Translation and Protection),并带有专用指令用于刷新 TLB。寄存器字段如图所示:

RV32 SATP 寄存器

RV64 SATP 寄存器

  • 注:这里的 WARL 指的是 CSR 的访问策略,即 Write Any Read Legal。

据标准描述,ASID 即 Address Space Identifier,用于标识不同的地址空间而无需频繁地进行刷新 TLB 操作。PPN 字段填入根页表的物理页号,而 Mode 字段用于标识 MMU 的工作模式,如表所示:

RV32:

名称 描述
0 Bare 无转译和保护
1 SV32 基于页的 32 位虚拟寻址

RV64:

名称 描述
0 Bare 无转译和保护
1-7 - 保留供标准使用
8 SV39 基于页的 39 位虚拟寻址
9 SV48 基于页的 48 位虚拟寻址
10 SV57 基于页的 57 位虚拟寻址
11 SV64 保留供基于页的 64 位虚拟寻址
12-13 - 保留供标准使用
14-15 - 设计为自定义用途

仅当处于 S-mode (监管者模式) 和 U-mode (用户模式) 时,SATP 寄存器被激活。写入 SATP 寄存器并不代表页表更新,也不会使页表缓存失效。需要通过 SFENCE.VMA 指令来完成这件事。

# 页表的构成

对于不同模式下的 MMU, 使用不同级数的页表。SV32 使用 2 级,SV39 有 3 级,SV48 有 4 级,而 SV57 则有 5 级。页表条目如下所示:

SV32 页表项

SV57 页表项

  • 注:对于较少的页表级数,其不存在的级数对应 PPN 置 0 。如 SV39, PPN[3] 和 PPN[4] 应置 0 。

各字段的含义及作用如下:

  • V: Valid,有效位,如果是 0 ,则该表项无效。
  • R: Read, 可读
  • W: Write, 可写
  • X: eXecute, 可执行
  • U: User Mode, 标记该页是否可以在用户模式下访问
  • G: Global, 全局页
  • A: Accessed, 已被访问,当 R/W/X 之后置1
  • D: Dirty,脏页,已经修改
  • RSW: Reserved for Software,可自定义
  • PBMT: 被 Svpbmt 扩展使用
  • N : 被 Svnapot 扩展使用

页表项作用以 X/W/R 三位编码,列表如下:

XWR 意义
000 指向下级页表 (其 PPN 即为下级页表对应的 PPN)
001 只读页
010 保留
011 可读写页
100 只执行页
101 可读可执行页
110 保留
111 可读写可执行页

注意:

  • 所有的页都为固定的 4KB
  • 所有的页表簇都是一页大 (对于SV32,一簇有 1024 项,其他是 512 项)
  • 只要 X/R/W 不为 000,就表示叶子 PTE,其可表示一个大页 (如 2M, 1G, 512G, 甚至 256T)
  • 根页表所在的物理页号应填入 STAP.PPN
  • 对于 RV64 下的虚拟地址访问,其高位要与模式支持的最高位值保持一致(可认为是符号扩展)

原文

(On SV39) Instruction fetch addresses and load and store effective addresses, which are 64 bits, must have bits 63–39 all equal to bit 38, or else a page-fault exception will occur. The 27-bit VPN is translated into a 44-bit PPN via a three-level page table, while the 12-bit page offset is untranslated.

When mapping between narrower and wider addresses, RISC-V zero-extends a narrower physical address to a wider size. The mapping between 64-bit virtual addresses and the 39-bit usable address space of Sv39 is not based on zero-extension but instead follows an entrenched convention that allows an OS to use one or a few of the most-significant bits of a full-size (64-bit) virtual address to quickly distinguish user and supervisor address regions.

# 地址转换过程

这部分简单说,就是树的搜索。所有的页表形成一颗以 SATP.PPN 所指示页号为根的树,MMU 根据 VPN 依次访问页表,如果遇到指针项,就继续向下查找。访问一个无效页 (V=0) ,或非法访问一个页,会产生 Page Fault。

这里给出原文:

See More

A virtual address va is translated into a physical address pa as follows:

  1. Let a be satp.ppn × PAGESIZE, and let i = LEVELS − 1. (For Sv32, PAGESIZE=212 and LEVELS=2.) The satp register must be active, i.e., the effective privilege mode must be S-mode or U-mode.

  2. Let pte be the value of the PTE at address a+va.vpn[i]×PTESIZE. (For Sv32, PTESIZE=4.) If accessing pte violates a PMA or PMP check, raise an access-fault exception corresponding to the original access type.

  3. If pte.v = 0, or if pte.r = 0 and pte.w = 1, or if any bits or encodings that are reserved for future standard use are set within pte, stop and raise a page-fault exception corresponding to the original access type.

  4. Otherwise, the PTE is valid. If pte.r = 1 or pte.x = 1, go to step 5. Otherwise, this PTE is a pointer to the next level of the page table. Let i = i − 1. If i < 0, stop and raise a page-fault exception corresponding to the original access type. Otherwise, let a = pte.ppn × PAGESIZE and go to step 2.

  5. A leaf PTE has been found. Determine if the requested memory access is allowed by the pte.r, pte.w, pte.x, and pte.u bits, given the current privilege mode and the value of the SUM and MXR fields of the mstatus register. If not, stop and raise a page-fault exception corresponding to the original access type.

  6. If i > 0 and pte.ppn[i − 1 : 0] ̸= 0, this is a misaligned superpage; stop and raise a page-fault exception corresponding to the original access type.

  7. If pte.a = 0, or if the original memory access is a store and pte.d = 0, either raise a page-fault exception corresponding to the original access type, or:

  • If a store to pte would violate a PMA or PMP check, raise an access-fault exception corresponding to the original access type.
  • Perform the following steps atomically: a) Compare pte to the value of the PTE at address a + va.vpn[i] × PTESIZE. b) If the values match, set pte.a to 1 and, if the original memory access is a store, also set pte.d to 1. c) If the comparison fails, return to step 2
  1. The translation is successful. The translated physical address is given as follows:
  • pa.pgoff = va.pgoff.
  • If i > 0, then this is a superpage translation and pa.ppn[i − 1 : 0] = va.vpn[i − 1 : 0].
  • pa.ppn[LEVELS − 1 : i] = pte.ppn[LEVELS − 1 : i]. All implicit accesses to the address-translation data structures in this algorithm are performed using width PTESIZE.

# SFENCE.VMA 指令

SFENCE.VMA (Supervisor Memory-Management Fence) 指令,是编码于 SYSTEM opcode 下的一条指令,用于同步当前执行环境下处于内存中的内存管理数据更新。指令接受两个参数,rs1 (虚地址) 和 rs2 (ASID)。

根据 [平头哥 C906 用户手册],该指令的作用可简单描述如下:

rs1 rs2 用途
x0 x0 无效 TLB 中所有的表项
非 x0 x0 无效 TLB 中所有命中 rs1 所示虚拟地址的表项
x0 非 x0 无效 TLB 中所有命中 rs2 所示 ASID 的表项
非 x0 非 x0 无效 TLB 中所有命中 rs1 所示虚拟地址 及 所有命中 rs2 所示 ASID 的表项
  • 注:x0 就是通用寄存器 0,其值固定为 0

在如下常见情况,系统需要执行该指令以使更改生效:

  • 当软件回收一个 ASID(即,将其与不同的页表重新关联)时,它应首先改变 satp 指向使用回收的 ASID 的新页表,然后执行 SFENCE.VMA,其中 rs1=x0,rs2 设置为回收的 ASID。或者,软件可以在 satp 加载了不同的 ASID 时执行相同的 SFENCE.VMA 指令,只要下次 satp 加载回收的 ASID 时,同时加载新的页表。

  • 如果实现没有提供 ASID,或者软件选择始终使用 ASID 0,那么在每次写入 satp 后,软件应执行 SFENCE.VMA,其中 rs1=x0。在常见的情况下,没有修改全局转换,rs2 应设置为除 x0 之外的寄存器,但该寄存器包含零值,这样就不会刷新全局转换。

  • 如果软件修改了非叶 PTE,它应执行 SFENCE.VMA,其中 rs1=x0。如果遍历路径中的任何 PTE 都设置了 G 位,rs2 必须是 x0;否则,rs2 应设置为正在修改转换的 ASID。

  • 如果软件修改了叶 PTE,它应执行 SFENCE.VMA,其中 rs1 设置为页面内的虚拟地址。如果遍历路径中的任何 PTE 都设置了 G 位,rs2 必须是 x0;否则,rs2 应设置为正在修改转换的 ASID。

  • 对于增加叶 PTE 权限和将无效 PTE 更改为有效叶的特殊情况,软件可以选择懒惰地执行 SFENCE.VMA。在修改 PTE 但在执行 SFENCE.VMA 之前,将使用新的或旧的权限。在后一种情况下,可能会发生页面错误异常,此时软件应根据前一条规则执行 SFENCE.VMA。

# 地址空间布局

好了,看完了标准手册,该考虑系统的地址空间布局了。由于使用RV64,简单起见,我们将系统空间定在低地址,而用户模式空间定在高地址。

将系统空间定在低地址,指的是以 MMU 支持的最高位为 0 的虚拟地址空间,如在 SV39 模式下,

起始 结束 长度 用途
0x0 0x3F FFFF FFFF 256 GB 系统(S)
0xFFFF FFC0 0000 0000 0xFFFF FFFF FFFF FFFF 256GB 用户(U)

这样做,可以避免系统内存的重映射,减少内存管理的开销,并且简化启动的流程(可以不用重定位)。坏处在于,用户空间的起始地址不确定,如果以 SV39 统一,会造成用户堆空间的破碎。

使用这样的内存布局,系统栈就可以在启动 MMU 之后放置于系统空间的顶部,得到一个最大的连续地址空间用于堆空间分配,还可以留出很大的高于 DRAM 的 IO 空间 (如 PCIE IO空间)。详细的内容将会在下一篇日记中记述。