2. RISC-V Sv39 虚拟地址格式:39位虚拟地址的划分与三级页表索引原理
在 xv6 内核中,RISC-V 架构采用 Sv39 分页方案管理 39 位虚拟地址空间。理解虚拟地址的位划分是掌握多级页表遍历与 TLB 刷新的基础。本章将详细解析 Sv39 虚拟地址的格式、三级页表索引的生成原理,以及硬件如何利用这些索引完成地址翻译。
2.1 Sv39 虚拟地址的位划分
Sv39 将 39 位虚拟地址(VA[38:0])划分为四个逻辑部分,用于三级页表遍历和页内寻址。高位 VA[63:39] 必须与 VA[38] 相同(符号扩展),否则硬件会触发页错误。
| 位域 | 位范围 | 宽度 | 名称 | 用途 |
|---|---|---|---|---|
| VPN[2] | VA[38:30] | 9 位 | 一级页号 | 索引顶级页表(L2 页表) |
| VPN[1] | VA[29:21] | 9 位 | 二级页号 | 索引二级页表(L1 页表) |
| VPN[0] | VA[20:12] | 9 位 | 三级页号 | 索引三级页表(L0 页表) |
| Offset | VA[11:0] | 12 位 | 页内偏移 | 在物理页框内寻址字节 |
关键点:每个 VPN 字段均为 9 位,意味着每级页表最多包含 29 = 512 个页表项(PTE)。每个 PTE 大小为 8 字节,因此每级页表恰好占用一个 4 KiB 物理页。
2.2 三级页表索引原理
硬件(MMU)在翻译虚拟地址时,从顶级页表(L2)开始,逐级向下索引,直到找到最终的物理页框号(PPN)。索引过程完全由 VPN 字段驱动:
- L2 索引: 从
satp寄存器读取根页表物理地址,加上VPN[2] × 8得到 L2 PTE 地址。读取 PTE,检查有效位(V)和读写权限。 - L1 索引: 从 L2 PTE 中提取下一级页表物理地址,加上
VPN[1] × 8得到 L1 PTE 地址。读取并检查。 - L0 索引: 从 L1 PTE 中提取下一级页表物理地址,加上
VPN[0] × 8得到 L0 PTE 地址。读取并检查。 - 物理地址合成: 从 L0 PTE 中提取物理页框号(PPN),拼接页内偏移(Offset)得到最终物理地址。
硬件遍历流程: 整个索引过程由 RISC-V 的 MMU 硬件自动完成,无需软件干预。软件(如 xv6 的
walk 函数)仅在建立或修改页表时模拟此过程。
2.3 页表项(PTE)格式与权限控制
每个页表项(PTE)为 64 位,包含物理页框号(PPN)和权限标志位。xv6 利用这些标志位实现用户/内核隔离、读写执行权限控制。
| 位域 | 位范围 | 标志 | 描述 |
|---|---|---|---|
| V | 0 | Valid | 页表项有效 |
| R | 1 | Readable | 页面可读 |
| W | 2 | Writable | 页面可写 |
| X | 3 | Executable | 页面可执行 |
| U | 4 | User | 用户模式可访问 |
| G | 5 | Global | 全局映射(不刷 TLB) |
| A | 6 | Accessed | 页面已被访问 |
| D | 7 | Dirty | 页面已被写入 |
| RSW | 8-9 | Reserved for SW | 操作系统保留位 |
| PPN | 10-53 | Physical Page Number | 物理页框号(44 位) |
| Reserved | 54-63 | — | 保留位,必须为 0 |
注意: 如果 PTE 的 R、W、X 均为 0,则该 PTE 指向下一级页表(非叶子节点)。xv6 的
walk 函数通过检查 (PTE_R | PTE_W | PTE_X) 判断当前 PTE 是否为叶子节点。
2.4 xv6 中的页表遍历实现
xv6 内核提供 walk 函数模拟硬件页表遍历过程,用于在软件中查找或创建虚拟地址对应的 PTE。以下是核心代码片段:
// 在页表 pagetable 中查找虚拟地址 va 对应的 PTE
// 如果 alloc 非零,则在缺失的中间页表层级分配新页
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");
for(int level = 2; level > 0; level--) {
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
if(!alloc || (pagetable = (pagetable_t)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}
代码解析:
PX(level, va)宏提取对应级别的 VPN 索引:PX(2, va)提取 VPN[2],PX(1, va)提取 VPN[1],PX(0, va)提取 VPN[0]。- 循环从 level=2 开始,逐级向下,直到 level=0 返回叶子 PTE 的指针。
- 如果中间页表缺失且
alloc=1,则调用kalloc()分配新物理页并设置 PTE。
2.5 地址翻译完整示例
假设虚拟地址 0x7F3A4B0C,其二进制位划分如下:
- VPN[2] =
0x1F8(VA[38:30]) - VPN[1] =
0x1D2(VA[29:21]) - VPN[0] =
0x0B0(VA[20:12]) - Offset =
0xC(VA[11:0])
硬件遍历过程:
- 从
satp读取根页表物理地址PA_root。 - 计算 L2 PTE 地址:
PA_root + 0x1F8 * 8,读取 PTE,得到 L1 页表物理地址PA_L1。 - 计算 L1 PTE 地址:
PA_L1 + 0x1D2 * 8,读取 PTE,得到 L0 页表物理地址PA_L0。 - 计算 L0 PTE 地址:
PA_L0 + 0x0B0 * 8,读取 PTE,得到物理页框号PPN。 - 最终物理地址 =
PPN << 12 | 0xC。