跳转至

RISC-V 特权级 ISA(页表相关)

2890 个字 预计阅读时间 10 分钟

Abstract

RISC-V 指令集的特权级部分。这里只包含页表相关的内容,其他的内容在 RISC-V 特权级 ISA(基础 & 中断)里。

参考:

操作系统实现虚拟地址需要硬件的支持,需要操作系统向 CPU 通知设置页表,在访问时 CPU 再进行地址的翻译。而这些页表的设置操作都是在 Supervisor 模式下进行设置的,因为 User 模式不应该关心这些,Machine 模式直接访问物理地址也不关心。所以关于分页的 ISA 都在 Supervisor 里。

相关 CSR 寄存器

sstatus

  • 18 SUM:是否允许 Supervisor 模式访问用户态内存(permit Supervisor User Memory access)
  • 19 MXR:是否允许读取可执行页面的内存(Make eXecutable Readable)

satp

satp Supervisor Address Translation and Protection,即用于设置页表的寄存器。

satp 32 位 Supervisor
31 30 22 21 0
M ASID PPN
  • PPN:根页表物理页号(Physical Page Number)
  • ASID:地址空间 ID(Address Space ID)
  • M(MODE:分页模式
    • 0:Bare 不进行地址翻译或者保护
    • 1:Sv32 采用 Sv32 模式虚拟地址翻译
satp 64 位 Supervisor
63 60 59 44 43 0
M ASID PPN
  • PPN:根页表物理页号(Physical Page Number)
  • ASID:地址空间 ID(Address Space ID)
  • M(MODE:分页模式
    • 0:Bare 不进行地址翻译或者保护
  • 8:Sv39 
  • 9:Sv48
  • 10:Sv57
  • 11:Sv64(保留)

相关特权指令

sfence.vma R 型
31 25 24 20 19 15 14 12 11 7 6 0
0001001 rs2 rs1 000 00000 1110011
  • 指令格式:sfence.vma rs1, rs2
  • 指令作用:刷新 TLB(Translation Lookaside Buffer)中的虚拟地址到物理地址的映射缓存
    • rs1 rs2 均为 x0 时,刷新所有 TLB
    • rs1 指定要刷新的虚拟地址,不为 x0 则只刷新所在的叶页表项
    • rs2 指定 ASID,不为 x0 则只刷新指定 ASID TLB

Sv32 分页模式

Sv32 模式规定的虚拟地址有 32 位,物理地址有 34 位,结构分别为:

31 22 21 12 11 0
VPN[1] VPN[0] page offset
33 22 21 12 11 0
PPN[1] PPN[0] page offset

其中 VPN Virtual Page Number 虚拟页号,PPN Physical Page Number 物理页号。转换的目标就是把 20 位虚拟页号转为 22 位物理页号,offset 不变。

Sv32 使用二级页表,每一个页表包含 2^10=1024 个页表项,每个页表项占 4 个字节,所以一个页表正好 4KiB 占一页内存且必须页对齐。每一个页表项的内容如下:

31 20 19 10 9 8 7 6 5 4 3 2 1 0
PPN[1] PPN[0] RSW D A G U X W R V

其中 PPN[1] PPN[0] 为下一级页表的物理页号 / 物理地址的高 20 位。剩下的为 flag

  • RSW:保留位,必须为 0
  • A(Accessed、D(Dirty:叶页表项是否被访问过、是否被写过(没细看,反正都是 0 就行)
  • G(Global:全局位,如果为 1 则不会被 TLB 清除(也没细看)
  • U(User:用户位,如果为 1 则允许用户态访问
  • X(eXecute、W(Write、R(Read:可执行、可写、可读位
    • RWX 全为 0 时表示当前页表项指向下一级页表
    • W 1R 一定也为 1W 1R 0 是保留状态)
  • V(Valid:有效位,如果为 1 则表示该页表项有效,否则在访问时会触发异常

地址翻译的流程图为:

上图是使用二级页表的翻译过程。除此之外还有一种特殊的情况,即也可以使用一级页表(superpage,当第一级页表的 RWX 不全为 0 时,表示当前页表项是叶页表项,此时会判断 PPN[0] 的值,如果不全为 0 则抛出异常。然后在翻译时 PPN[1] 保留,VPN[0] 并入 page offset

文档中对于地址翻译的详细描述

记待翻译的虚拟地址为 va,结果的物理地址为 pa

  1. a = satp.PPN << 12,i = 1
  2. pte = *(a + (va.VPN[i] << 2))
    • 如果 pte 的访问过程中违反了 PMA PMP 的检查,抛出对应访问类型的 Access Fault 异常
  3. 如果 pte.V = 0 或者(pte.R = 0 pte.W = 1)或者其他保留位没有置 0,抛出对应访问类型的 Page Fault 异常
  4. 目前 pte 是合法的。如果 pte.R = 1 pte.X = 1(即不全为 0)则跳到第 6
  5. 目前 pte 指向下一级页表。令 i = i - 1,a = pte.PPN << 12,跳到第 2
    • 如果 i < 0,抛出对应访问类型的 Page Fault 异常
  6. 目前 pte 是一个叶页表项。根据 pte.R/W/X/U、当前特权级、SUM MXR 位判断当前访问权限是否合法,不合法则抛出对应访问类型的 Page Fault 异常
  7. 如果 i > 0 pte.PPN[i-1:0] != 0,说明当前是 superpage 且没对齐,抛出对应访问类型的 Page Fault 异常
  8. pte.A pte.D 有关的一些没细看
  9. 翻译成功,接下来填写转换后的物理地址
    • pa.pgoff = va.pgoff
    • 如果 i > 0,说明当前是 superpage translation,令 pa.PPN[i-1:0] = va.VPN[i-1:0]
      • 即扩充 offset 的范围
    • pa.PPN[1:i] = pte.PPN[1:i]

Sv39 分页模式

Sv39 模式规定虚拟地址为 39 位,物理地址为 56 位。且虚拟地址的高 [63:39] 位必须和 38 位保持一致,否则会触发 Page Fault。虚拟地址有 27 VPN,通过三级页表转换为 44 PPN,剩下的 12 位为 offset 不变。虚拟地址和物理地址的结构如下:

下面 index 后面有 ... 的表示这部分没有按照实际长度比例来

38 30 29 21 20 12 11 0
VPN[2] VPN[1] VPN[0] page offset
55... 30 29 21 20 12 11 0
PPN[2] PPN[1] PPN[0] page offset

Sv39 每个页表有 2^9=512 个页表项,每个页表项有 8 字节,因此每个页表同样为 4KiB 大小,且必须对齐。其中每个页表项的结构如下:

63 62 61 60... 54 53... 28 27... 19 18... 10 9 8 7 6 5 4 3 2 1 0
N PBMT PPN[2] PPN[1] PPN[0] RSW D A G U X W R V

其中:

  • 高位三部分保留,如果有不为 0 的则抛出 Page Fault 异常
    • N 保留给 Svnapot 扩展,没实现的话必须为 0
    • PBMT 保留给 Svpbmt 扩展,没实现的话必须为 0
    • [60:54] 位保留给后续标准使用,目前必须为 0
  • PPN[2] PPN[1] PPN[0] 为物理页号
  • 后面的标识位和 Sv32 一样

地址翻译过程和 Sv32 类似,只是多了一级页表。而且 Sv39 的每一级页表项也都可以是叶页表项,同样页表项后续的 PPN 必须是 0 否则视为未对齐抛出 Page Fault 异常。因此 Sv39 支持 4KiB 粒度的分页 pages2MiB 粒度的分页 megapages 1GiB 粒度的分页 gigapages。其中使用三级页表的地址翻译过程如下:

只使用二级页表和一级页表的 megapages gigapages 也不难理解,其实就相当于 page offset 向上扩充覆盖到了 VPN[0] VPN[1],这里就不画了。

文档中对于地址翻译的详细描述

记待翻译的虚拟地址为 va,结果的物理地址为 pa

  1. a = satp.PPN << 12,i = 2
  2. pte = *(a + (va.VPN[i] << 3))
    • 如果 pte 的访问过程中违反了 PMA PMP 的检查,抛出对应访问类型的 Access Fault 异常
  3. 如果 pte.V = 0 或者(pte.R = 0 pte.W = 1)或者其他保留位没有置 0,抛出对应访问类型的 Page Fault 异常
  4. 目前 pte 是合法的。如果 pte.R = 1 pte.X = 1(即不全为 0)则跳到第 6
  5. 目前 pte 指向下一级页表。令 i = i - 1,a = pte.PPN << 12,跳到第 2
    • 如果 i < 0,抛出对应访问类型的 Page Fault 异常
  6. 目前 pte 是一个叶页表项。根据 pte.R/W/X/U、当前特权级、SUM MXR 位判断当前访问权限是否合法,不合法则抛出对应访问类型的 Page Fault 异常
  7. 如果 i > 0 pte.PPN[i-1:0] != 0,说明当前是 superpage 且没对齐,抛出对应访问类型的 Page Fault 异常
  8. pte.A pte.D 有关的一些没细看
  9. 翻译成功,接下来填写转换后的物理地址
    • pa.pgoff = va.pgoff
    • 如果 i > 0,说明当前是 superpage translation,令 pa.PPN[i-1:0] = va.VPN[i-1:0]
      • 即扩充 offset 的范围
    • pa.PPN[2:i] = pte.PPN[2:i]

Sv48 分页模式

Sv39 提供的 39 位虚拟地址可能会不够用,所以扩展得到了 Sv48 模式,在 Sv39 基础上加了一级页表,为虚拟地址高位多加了 9 位的 VPN[3],将物理地址 26 位的 PPN[2] 分为了 17 位的 PPN[3] 9 位的 PPN[2]

47 39 38 30 29 21 20 12 11 0
VPN[3] VPN[2] VPN[1] VPN[0] page offset
55... 39 38 30 29 21 20 12 11 0
PPN[3] PPN[2] PPN[1] PPN[0] page offset

使用四级页表,每个页表内页表项数量和长度都没有变化,页表项内容也只是从 PPN[2] 拆分出了 PPN[3]

63 62 61 60... 54 53... 37 36... 28 27... 19 18... 10 9 8 7 6 5 4 3 2 1 0
PPN[3] PPN[2] PPN[1] PPN[0] RSW D A G U X W R V

地址的翻译过程也类似,只是可能会多一级查询,这里不再赘述。

同样每一级页表项都可以作为叶页表项,因此 Sv48 的分页支持四种粒度 4KiB pages、2MiB megapages、1GiB gigapages 512GiB terapages

文档中对于地址翻译的详细描述

记待翻译的虚拟地址为 va,结果的物理地址为 pa

  1. a = satp.PPN << 12,i = 3
  2. pte = *(a + (va.VPN[i] << 3))
    • 如果 pte 的访问过程中违反了 PMA PMP 的检查,抛出对应访问类型的 Access Fault 异常
  3. 如果 pte.V = 0 或者(pte.R = 0 pte.W = 1)或者其他保留位没有置 0,抛出对应访问类型的 Page Fault 异常
  4. 目前 pte 是合法的。如果 pte.R = 1 pte.X = 1(即不全为 0)则跳到第 6
  5. 目前 pte 指向下一级页表。令 i = i - 1,a = pte.PPN << 12,跳到第 2
    • 如果 i < 0,抛出对应访问类型的 Page Fault 异常
  6. 目前 pte 是一个叶页表项。根据 pte.R/W/X/U、当前特权级、SUM MXR 位判断当前访问权限是否合法,不合法则抛出对应访问类型的 Page Fault 异常
  7. 如果 i > 0 pte.PPN[i-1:0] != 0,说明当前是 superpage 且没对齐,抛出对应访问类型的 Page Fault 异常
  8. pte.A pte.D 有关的一些没细看
  9. 翻译成功,接下来填写转换后的物理地址
    • pa.pgoff = va.pgoff
    • 如果 i > 0,说明当前是 superpage translation,令 pa.PPN[i-1:0] = va.VPN[i-1:0]
      • 即扩充 offset 的范围
    • pa.PPN[3:i] = pte.PPN[3:i]

Sv57 分页模式

Sv48 基础上又加了一级页表,为虚拟地址高位多加了 9 位的 VPN[4],将物理地址 17 位的 PPN[3] 分为了 8 位的 PPN[4] 9 位的 PPN[3]

56 48 47 39 38 30 29 21 20 12 11 0
VPN[4] VPN[3] VPN[2] VPN[1] VPN[0] page offset
55 48 47 39 38 30 29 21 20 12 11 0
PPN[4] PPN[3] PPN[2] PPN[1] PPN[0] page offset

使用五级页表,每个页表内页表项数量和长度都没有变,内容差别和物理地址同样,从 PPN[3] 中拆出了 PPN[4],具体就不展示了。

翻译过程类似,同样每一级页表项都可以作为叶页表项,因此 Sv57 的分页支持五种粒度 4KiB pages、2MiB megapages、1GiB gigapages、512GiB terapages 256TiB petapages

文档中对于地址翻译的详细描述

记待翻译的虚拟地址为 va,结果的物理地址为 pa

  1. a = satp.PPN << 12,i = 4
  2. pte = *(a + (va.VPN[i] << 3))
    • 如果 pte 的访问过程中违反了 PMA PMP 的检查,抛出对应访问类型的 Access Fault 异常
  3. 如果 pte.V = 0 或者(pte.R = 0 pte.W = 1)或者其他保留位没有置 0,抛出对应访问类型的 Page Fault 异常
  4. 目前 pte 是合法的。如果 pte.R = 1 pte.X = 1(即不全为 0)则跳到第 6
  5. 目前 pte 指向下一级页表。令 i = i - 1,a = pte.PPN << 12,跳到第 2
    • 如果 i < 0,抛出对应访问类型的 Page Fault 异常
  6. 目前 pte 是一个叶页表项。根据 pte.R/W/X/U、当前特权级、SUM MXR 位判断当前访问权限是否合法,不合法则抛出对应访问类型的 Page Fault 异常
  7. 如果 i > 0 pte.PPN[i-1:0] != 0,说明当前是 superpage 且没对齐,抛出对应访问类型的 Page Fault 异常
  8. pte.A pte.D 有关的一些没细看
  9. 翻译成功,接下来填写转换后的物理地址
    • pa.pgoff = va.pgoff
    • 如果 i > 0,说明当前是 superpage translation,令 pa.PPN[i-1:0] = va.VPN[i-1:0]
      • 即扩充 offset 的范围
    • pa.PPN[4:i] = pte.PPN[4:i]

操作系统中开启分页

在操作系统中开启分页机制,大体上需要做以下事情:

  • 分配物理地址页,每页中写好需要映射部分的页表项
    • 注意 PTE 标识位的权限,以及 PTE.V
  • 将页表的物理地址末尾截断得到根页表物理页号
  • 同分页模式一起写入 satp 寄存器
    • 写入的同时就已经启用分页了,所以要注意 pc+4 是否仍然可以访问
  • 执行 sfence.vma x0, x0 指令,刷新 TLB 缓存

这里存在一个主要的问题,设置 satp 寄存器的同时就会开启分页机制,但同时不会修改 pc,所以就要考虑下一条指令读取的问题。大概有两种处理方法:

  • 提前设置 stvec 为设置 satp 之后的下一条指令的虚拟地址
    • 这样开启分页后继续执行会产生异常,跳转到 stvec 地址,在 S 态继续执行
  • 进行两次页表的设置
    • 第一次设置临时页表,将代码段同时映射到高位的虚拟地址,以及建立一个等值映射
    • 设置 satp 后仍可以通过等值映射根据物理地址访问后续指令
    • 后续通过将 ra 设置为高位虚拟地址再 ret,这时 pc 就开始在虚拟地址上执行了
    • 然后再布局完整的页表,设置 satp,此时内核态的虚拟地址映射不会发生变化,设置后 pc 仍可以正常执行

qemu 实际运行中,旧版 qemu(起码 7.0 之前)实测会有问题,使用第二种方法的话,即使不进行等值映射,qemu 也可以正常执行后续指令,但一旦使用 gdb 单步跟踪,就会在设置 satp 后立马挂掉(薛定谔的代码。尚不清楚是什么原因,姑且怀疑是 qemu 对于指令缓存处理的问题(ps:这里即使调用 fence.i 清除指令缓存也一样可以执行

相关问题以及一种 patch[PATCH] target/riscv: Exit current TB after an sfence.vma


最后更新: 2023年6月15日 22:13:10
创建日期: 2023年5月10日 23:50:08
回到页面顶部