跳到主要内容

内存管理

RISC-V 提供了灵活的内存管理机制,支持虚拟内存和物理内存的管理。本章介绍 RISC-V 的内存管理单元(MMU)和页表机制。

内存管理概述

地址空间

RISC-V 支持两种地址空间:

  • 物理地址空间:实际的硬件内存地址
  • 虚拟地址空间:程序看到的逻辑地址

地址翻译

内存管理单元(MMU)负责将虚拟地址翻译为物理地址:

虚拟地址 -> MMU -> 物理地址

地址翻译模式

RISC-V 支持多种地址翻译模式,通过 satp 寄存器的 MODE 字段选择:

MODE名称虚拟地址位数物理地址位数
0Bare无翻译-
8Sv393956
9Sv484856
10Sv575756

裸机模式(Bare Mode)

MODE = 0 时,不进行地址翻译,虚拟地址等于物理地址:

# 设置裸机模式
csrw satp, zero

Sv39 模式

Sv39 使用 39 位虚拟地址,支持 512 GB 的虚拟地址空间:

虚拟地址格式(39位):
[38:30] VPN[2] - 二级页表索引(9位)
[29:21] VPN[1] - 一级页表索引(9位)
[20:12] VPN[0] - 零级页表索引(9位)
[11:0] Offset - 页内偏移(12位)

Sv48 模式

Sv48 使用 48 位虚拟地址,支持 256 TB 的虚拟地址空间:

虚拟地址格式(48位):
[47:39] VPN[3] - 三级页表索引(9位)
[38:30] VPN[2] - 二级页表索引(9位)
[29:21] VPN[1] - 一级页表索引(9位)
[20:12] VPN[0] - 零级页表索引(9位)
[11:0] Offset - 页内偏移(12位)

页表结构

页表项(PTE)

每个页表项为 64 位,包含物理页号和权限信息:

页表项格式(64位):
[63:54] 保留
[53:10] PPN - 物理页号(44位)
[9:8] RSW - 保留给软件使用
[7] D - 脏位(Dirty)
[6] A - 访问位(Accessed)
[5] G - 全局映射
[4] U - 用户模式可访问
[3] X - 可执行
[2] W - 可写
[1] R - 可读
[0] V - 有效位

权限位说明

名称描述
VValid页表项有效
RRead可读权限
WWrite可写权限
XExecute可执行权限
UUser用户模式可访问
GGlobal全局映射(ASID 无关)
AAccessed页面被访问过
DDirty页面被写过

权限组合

RWX描述
000指向下一级页表
001只执行
010只读(保留)
011读执行
100只读
101读执行
110读写
111读写执行

多级页表

Sv39 页表结构

Sv39 使用三级页表:

虚拟地址 -> VPN[2] -> 页表2 -> VPN[1] -> 页表1 -> VPN[0] -> 页表0 -> 物理页

地址翻译流程

  1. 从 satp 获取根页表地址
  2. 使用 VPN[2] 索引二级页表
  3. 获取一级页表地址
  4. 使用 VPN[1] 索引一级页表
  5. 获取零级页表地址
  6. 使用 VPN[0] 索引零级页表
  7. 获取物理页号
  8. 组合页内偏移得到物理地址

页表遍历示例

# Sv39 地址翻译示例
# 虚拟地址:0x0000000012345678

# 提取 VPN
# VPN[2] = (va >> 30) & 0x1FF = 0
# VPN[1] = (va >> 21) & 0x1FF = 0x91
# VPN[0] = (va >> 12) & 0x1FF = 0x345
# Offset = va & 0xFFF = 0x678

# 翻译过程
# 1. 读取 satp 获取根页表物理地址
csrr t0, satp
slli t0, t0, 12 # PPN << 12 = 物理地址

# 2. 索引二级页表
li t1, 0 # VPN[2] = 0
slli t1, t1, 3 # * 8(每个 PTE 8 字节)
add t0, t0, t1
ld t2, 0(t0) # 读取 PTE

# 3. 检查有效位
andi t3, t2, 1
beqz t3, page_fault

# 4. 获取一级页表地址
srli t0, t2, 10
slli t0, t0, 12

# 5. 索引一级页表
li t1, 0x91 # VPN[1]
slli t1, t1, 3
add t0, t0, t1
ld t2, 0(t0)

# 6. 获取零级页表地址
srli t0, t2, 10
slli t0, t0, 12

# 7. 索引零级页表
li t1, 0x345 # VPN[0]
slli t1, t1, 3
add t0, t0, t1
ld t2, 0(t0)

# 8. 获取物理页号
srli t0, t2, 10
slli t0, t0, 12

# 9. 组合偏移
ori t0, t0, 0x678 # 物理地址

TLB(Translation Lookaside Buffer)

TLB 是页表的硬件缓存,加速地址翻译。

TLB 结构

TLB 缓存虚拟页号到物理页号的映射:

+----------------+----------------+-------+
| 虚拟页号 (VPN) | 物理页号 (PPN) | 权限 |
+----------------+----------------+-------+

TLB 刷新

修改页表后需要刷新 TLB:

# 刷新所有 TLB 条目
sfence.vma zero, zero

# 刷新特定虚拟地址的 TLB 条目
sfence.vma a0, zero # a0 = 虚拟地址

# 刷新特定 ASID 的 TLB 条目
sfence.vma zero, a0 # a0 = ASID

# 刷新特定虚拟地址和 ASID 的 TLB 条目
sfence.vma a0, a1 # a0 = 虚拟地址, a1 = ASID

地址空间标识符(ASID)

ASID 用于区分不同进程的地址空间,避免切换进程时刷新整个 TLB。

ASID 配置

satp 寄存器包含 ASID 字段:

satp 寄存器:
[63:60] MODE - 地址翻译模式
[59:44] ASID - 地址空间标识符(16位)
[43:0] PPN - 根页表物理页号

设置 ASID

# 设置 ASID 和页表
li t0, (8 << 60) # Sv39 模式
ori t0, t0, (1 << 44) # ASID = 1
ori t0, t0, page_table_ppn
csrw satp, t0

内存属性

物理内存属性(PMA)

PMA 定义物理内存区域的属性:

  • 是否可缓存
  • 是否支持原子操作
  • 访问宽度限制

物理内存保护(PMP)

PMP 提供更细粒度的内存访问控制:

PMP 寄存器

  • pmpcfg0-pmpcfg15:配置寄存器
  • pmpaddr0-pmpaddr63:地址寄存器

PMP 配置

pmpcfg 格式:
[7] L - 锁定位
[5:4] A - 地址匹配模式
00 = OFF(禁用)
01 = TOR(上界)
10 = NA4(4字节对齐)
11 = NAPOT(幂等对齐区域)
[2] X - 可执行
[1] W - 可写
[0] R - 可读

PMP 配置示例

# 配置 PMP 区域 0:允许所有访问
li t0, 0x1F # L=0, A=NA4, R=W=X=1
csrw pmpcfg0, t0
li t0, 0xFFFFFFFF
csrw pmpaddr0, t0

# 配置 PMP 区域 1:只读区域
li t0, (1 << 7) | (2 << 4) | 1 # L=1, A=NAPOT, R=1
csrw pmpcfg0, t0

页错误处理

页错误类型

异常码名称描述
12Instruction page fault指令页错误
13Load page fault加载页错误
15Store/AMO page fault存储页错误

页错误信息

发生页错误时:

  • mepc(或 sepc):触发错误的指令地址
  • mcause(或 scause):异常原因
  • mtval(或 stval):触发错误的虚拟地址

页错误处理

page_fault_handler:
# 读取错误地址
csrr a0, mtval

# 读取错误原因
csrr a1, mcause

# 分配新页面
call allocate_page

# 更新页表
call update_page_table

# 刷新 TLB
sfence.vma a0, zero

# 返回
j trap_exit

内存管理示例

初始化页表

// C 代码
#define PAGE_SIZE 4096
#define PTE_V (1 << 0)
#define PTE_R (1 << 1)
#define PTE_W (1 << 2)
#define PTE_X (1 << 3)
#define PTE_U (1 << 4)

typedef unsigned long pte_t;

// 创建简单的恒等映射
void setup_page_table(pte_t* page_table) {
// 映射前 1MB 物理内存
for (int i = 0; i < 256; i++) {
pte_t pte = (i << 10) | PTE_V | PTE_R | PTE_W | PTE_X;
page_table[i] = pte;
}
}

启用虚拟内存

# 启用 Sv39 虚拟内存
enable_vm:
# 设置页表
la t0, page_table
srli t0, t0, 12 # 获取 PPN
li t1, (8 << 60) # Sv39 模式
or t0, t0, t1
csrw satp, t0

# 刷新 TLB
sfence.vma zero, zero

ret

小结

本章介绍了 RISC-V 内存管理:

  • 地址翻译模式:Bare、Sv39、Sv48、Sv57
  • 页表结构:页表项格式和权限位
  • 多级页表:Sv39 三级页表翻译流程
  • TLB:地址翻译缓存和刷新
  • ASID:地址空间标识符
  • 内存保护:PMP 物理内存保护
  • 页错误处理:缺页异常处理

内存管理是操作系统内核的核心功能,理解这些机制对于开发操作系统和系统软件至关重要。