| 问题 | 虚拟内存的作用 |
|---|---|
| 程序访问的地址从 0 开始,但物理内存不一样大 | 将每个进程的虚拟地址映射到不同物理地址 |
| 程序之间相互隔离 | 不同进程拥有独立地址空间 |
| 操作系统管理更方便 | 可轻松调度与保护进程 |
| 可支持分页机制 | 按页分配、换页、缓存更高效 |
💡 简单说: 虚拟内存让程序“以为”自己独占整个内存。
RISC-V 定义了多种分页模式,用于不同 XLEN(位宽):
| 模式 | XLEN | 地址位数 | 页大小 | 层数 | 用途 |
|---|---|---|---|---|---|
| Sv32 | 32 位 | 32 位虚拟地址 | 4 KiB | 2 层页表 | RV32 平台 |
| Sv39 | 64 位 | 39 位虚拟地址 | 4 KiB | 3 层页表 | RV64 常用(Linux) |
| Sv48 | 64 位 | 48 位虚拟地址 | 4 KiB | 4 层页表 | 高端服务器 |
| Bare | 任意 | 无分页 | — | — | 裸机/嵌入式系统 |
💡 Linux 在 RV64 平台上默认使用 Sv39。
RISC-V 虚拟内存通过 多级页表(Multi-level Page Table) 实现。 每一级页表都是一个 4KB 的数组,包含 512 个条目(8 字节 / PTE)。
| 位段 | 名称 | 说明 |
|---|---|---|
| [63:39] | Sign Extension | 必须与 bit 38 相同(符号扩展) |
| [38:30] | VPN[2] | 页表第 1 级索引(L2) |
| [29:21] | VPN[1] | 页表第 2 级索引(L1) |
| [20:12] | VPN[0] | 页表第 3 级索引(L0) |
| [11:0] | Offset | 页内偏移(4KB 页) |
VPN = Virtual Page Number 每级索引 9 位 → 512 个条目。
每个页表项(8 字节)描述虚拟页到物理页的映射。
| 位 | 名称 | 说明 |
|---|---|---|
| 63–54 | PBMT | 页属性(如缓存策略) |
| 53–10 | PPN | 物理页号(Physical Page Number) |
| 9 | RSW | OS 自定义位 |
| 8 | D | Dirty(是否写过) |
| 7 | A | Accessed(是否访问过) |
| 6 | G | Global(跨地址空间共享) |
| 5 | U | User(U 模式可访问) |
| 4 | X | eXecutable(可执行) |
| 3 | W | Writable(可写) |
| 2 | R | Readable(可读) |
| 1 | V | Valid(条目有效) |
| 0 | — | 保留 |
一个 PTE 中只要 R/W/X 中有一位为 1,就表示这是一个叶子节点(Leaf Entry), 否则它是中间页表(指向下一级页表)。
假设要访问虚拟地址 VA:
从 satp 寄存器 取出页表根地址;
使用 VPN[2] 作为第一级索引 → 得到二级页表地址;
使用 VPN[1] 索引二级页表;
使用 VPN[0] 索引三级页表;
找到叶子项(R/W/X 有效);
从 PPN 拼接物理地址:
物理地址 = PPN * 4KB + Offset
如果某级页表项无效(V=0),则触发 Page Fault 异常。
satp — Supervisor Address Translation and Protection ​| 字段 | 位宽 | 说明 |
|---|---|---|
| MODE | 4 位 | 分页模式(0=Bare, 8=Sv39, 9=Sv48) |
| ASID | 16 位 | 地址空间 ID(区分进程) |
| PPN | XLEN-24 位 | 根页表物理页号(页表起始地址) |
例如(Sv39 模式下):
satp = (MODE << 60) | (ASID << 44) | PPN
设置页表: 1️⃣ 在内存中建立页表结构 2️⃣ 把页表根物理地址写入
satp3️⃣ 执行sfence.vma刷新 TLB
sfence.vma — 刷新 TLB 缓存 ​当修改页表后需执行:
sfence.vma
确保新的映射生效。
假设有:
0x1000_00000x0000_1234_5678计算:
VPN[2] = bits [38:30]
VPN[1] = bits [29:21]
VPN[0] = bits [20:12]
Offset = bits [11:0]
CPU 根据 VPN 分三次读取页表,最后合成物理地址。
| 模式 | 地址宽度 | 层数 | 每级索引位 | 可寻址空间 | 常见系统 |
|---|---|---|---|---|---|
| Sv32 | 32 位 | 2 层 | 10 位 | 4 GiB | RV32 |
| Sv39 | 64 位 | 3 层 | 9 位 | 512 GiB | Linux RV64 |
| Sv48 | 64 位 | 4 层 | 9 位 | 256 TiB | 服务器 |
| 页面大小 | 说明 | 实现 |
|---|---|---|
| 4 KiB | 普通页 | 三级页表最后一级 |
| 2 MiB | 大页 | 二级页表直接叶子项 |
| 1 GiB | 超大页 | 一级页表直接叶子项 |
嵌入式系统可禁用虚拟内存:
csrw satp, zero # MODE = 0 (Bare)
此时虚拟地址 == 物理地址 即为“裸机运行模式(Bare Metal)”。
| 项目 | 内容 |
|---|---|
| 分页机制 | 将虚拟地址 → 物理地址的映射 |
| 页大小 | 通常 4KB |
| 控制寄存器 | satp、mstatus、stvec |
| 刷新指令 | sfence.vma |
| 常用模式 | Sv32(RV32) / Sv39(RV64) |
| 操作系统 | Linux、RT-Thread、FreeBSD 等 |
🔑 一句话总结: RISC-V 的虚拟内存通过多级页表(Sv32 / Sv39 / Sv48)实现, 控制寄存器
satp决定页表根地址和模式, CPU 按层解析虚拟地址 → 得到物理地址 → 执行访问。