SV39虚拟内存 — SV39 Virtual Memory

三级页表虚拟地址翻译

📖 SV39地址格式

SV39使用39位虚拟地址,通过三级页表翻译为56位物理地址:

SV39虚拟地址 (39位): ┌─────────┬─────────┬─────────┬───────────┐ │ VPN[2] │ VPN[1] │ VPN[0] │ Offset │ │ 9 bits │ 9 bits │ 9 bits │ 12 bits │ │ 38:30 │ 29:21 │ 20:12 │ 11:0 │ └─────────┴─────────┴─────────┴───────────┘ SV39物理地址 (56位): ┌──────────────┬───────────┐ │ PPN[2:0] │ Offset │ │ 44 bits │ 12 bits │ │ 55:12 │ 11:0 │ └──────────────┴───────────┘ 三级页表翻译: Level 1: VPN[2] → L1 PTE → L2表基址 Level 2: VPN[1] → L2 PTE → L3表基址 Level 3: VPN[0] → L3 PTE → 物理页号PPN Final: PPN + Offset = 物理地址
级别索引页大小表项数
Level 1VPN[2]512
Level 2VPN[1]512
Level 3VPN[0]4KB512
每个页表恰好占1页(4KB),包含512个8字节PTE。三级页表总共可映射512GB虚拟地址空间(2³⁹)。

🔬 页表项(PTE)格式

PTE格式 (64位): 63 62-54 53-10 9-8 7 6 5 4 3 2 1 0 ┌──┬─────┬───────┬────┬──┬──┬──┬──┬──┬──┬──┬──┐ │N │RSW │ PPN │RSW │D │A │G │U │X │W │R │V │ └──┴─────┴───────┴────┴──┴──┴──┴──┴──┴──┴──┴──┘ V: Valid — PTE有效 R: Read — 可读(与W一起判断leaf/non-leaf) W: Write — 可写 X: Execute — 可执行 U: User — 用户态可访问 G: Global — 全局映射(不刷新TLB) A: Accessed — 已访问(由硬件/软件设置) D: Dirty — 已修改(由硬件/软件设置) Leaf判断: R|W|X != 0 → Leaf PTE Non-leaf: R=W=X=0 → 指向下一级页表
R W X类型权限
0 0 0Non-leaf(指向下一级)
1 0 0Read-only只读
1 1 0Read-Write读写
1 0 1Read-Execute只读+执行(代码段)
1 1 1Read-Write-Execute全部权限
0 1 0Reserved非法

🖥️ Verilog实现:SV39 MMU

// Lesson 09: SV39 Virtual Memory — Three-Level Page Table
module sv39_mmu(
    input  wire        clk, rst_n,
    input  wire        enable,
    input  wire [38:0] vaddr,
    input  wire [55:0] ptbase,
    output reg  [55:0] paddr,
    output reg         valid,
    output reg         fault,
    output reg  [1:0]  pte_perm
);
    wire [8:0] vpn2 = vaddr[38:30], vpn1 = vaddr[29:21], vpn0 = vaddr[20:12];
    wire [11:0] offset = vaddr[11:0];
    reg [63:0] l1_pte[0:511], l2_pte[0:511], l3_pte[0:511];
    integer i;
    initial begin
        for(i=0;i<512;i=i+1) begin l1_pte[i]=0; l2_pte[i]=0; l3_pte[i]=0; end
        l1_pte[0] = {44'd1, 2'b00, 4'b0000, 1'b0, 1'b0, 1'b1, 11'd0};
        l2_pte[0] = {44'd2, 2'b00, 4'b0000, 1'b0, 1'b0, 1'b1, 11'd0};
        l3_pte[0] = {44'd3, 2'b00, 4'b0000, 1'b1, 1'b1, 1'b1, 11'd0};
    end
    reg [1:0] stage; reg [8:0] sv_vpn2,sv_vpn1,sv_vpn0; reg [11:0] sv_offset;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin paddr<=0; valid<=0; fault<=0; pte_perm<=0; stage<=0; end
        else if(!enable) begin paddr<={17'd0,vaddr}; valid<=1; fault<=0; stage<=0; end
        else begin
            case(stage)
            0: begin sv_vpn2<=vpn2; sv_vpn1<=vpn1; sv_vpn0<=vpn0; sv_offset<=offset;
                 if(!l1_pte[vpn2][0]) begin fault<=1; valid<=0; end
                 else if(l1_pte[vpn2][1]||l1_pte[vpn2][2]||l1_pte[vpn2][3]) begin
                   paddr<={l1_pte[vpn2][63:20],sv_vpn1,sv_vpn0,sv_offset};
                   pte_perm<={l1_pte[vpn2][1],l1_pte[vpn2][2]}; valid<=1; fault<=0; end
                 else stage<=1; end
            1: begin if(!l2_pte[sv_vpn1][0]) begin fault<=1; valid<=0; stage<=0; end
                 else if(l2_pte[sv_vpn1][1]||l2_pte[sv_vpn1][2]||l2_pte[sv_vpn1][3]) begin
                   paddr<={l2_pte[sv_vpn1][63:20],sv_vpn0,sv_offset};
                   pte_perm<={l2_pte[sv_vpn1][1],l2_pte[sv_vpn1][2]}; valid<=1; fault<=0; stage<=0; end
                 else stage<=2; end
            2: begin if(!l3_pte[sv_vpn0][0]) begin fault<=1; valid<=0; end
                 else begin paddr<={l3_pte[sv_vpn0][63:20],sv_offset};
                   pte_perm<={l3_pte[sv_vpn0][1],l3_pte[sv_vpn0][2]}; valid<=1; fault<=0; end
                 stage<=0; end
            endcase
        end
    end
endmodule
Verilator仿真验证通过 — 三级页表翻译正确

代码解析

🧪 实验练习

  1. 添加超级页支持:L1 leaf(2MB)和L2 leaf(1GB)映射
  2. 实现页表遍历:从内存读取PTE而非使用预初始化数组
  3. 添加权限检查:U-mode访问S-mode页面触发fault
  4. 实现sv48支持:4级页表,支持256TB虚拟地址
三级页表翻译正确
思考题:SV39的虚拟地址是39位,但寄存器是64位。高25位(63:39)必须全0或全1,否则会触发page fault。为什么?
参考资料:RISC-V Privileged Spec §4.3 | SV39设计原理 | Linux RISC-V页表实现

🔗 Linux RISC-V页表布局

Linux在RISC-V上的虚拟内存布局:

Linux RISC-V虚拟地址空间 (SV39): 0xFFFFFFFFFFFFFFFF ┌──────────────┐ │ 内核空间 │ │ (高256MB) │ 0xFFFFFFC000000000 ├──────────────┤ ← PAGE_OFFSET │ │ │ 空洞 │ │ (不可访问) │ │ │ 0x0000003FFFFFFFFF ├──────────────┤ │ 用户栈 │ ↓ 向下增长 │ │ │ 共享库 │ │ │ │ 堆 │ ↑ 向上增长 ├──────────────┤ │ BSS段 │ │ 数据段 │ │ 代码段 │ 0x0000000000000000 └──────────────┘ 页表项数: 每进程3级页表 = 1个L1 + N个L2 + M个L3 典型: ~2000个L3页表 = 8MB页表内存 大页优化: 2MB大页减少到~4个L2项

页表与TLB的交互

当TLB miss时,硬件页表遍历器(Page Table Walker)自动完成三级查找:

  1. 读取satp获取L1页表基址
  2. 用VPN[2]索引L1表,读取L1 PTE
  3. 如果是non-leaf,用PPN获取L2基址
  4. 用VPN[1]索引L2表,读取L2 PTE
  5. 如果是non-leaf,用PPN获取L3基址
  6. 用VPN[0]索引L3表,读取L3 PTE
  7. 检查权限(V/R/W/X/U),如果合法则填充TLB

整个遍历需要3次内存访问。如果L2/L3页表不在Cache中,延迟可能高达100+个时钟周期。

🎯 本课与整体课程的关系

SV39知识图谱: 08 CLINT → 09 SV39 (本课) → 10 TLB ↓ 虚拟内存是现代OS的基础 ↓ 11 直接映射Cache — 物理地址缓存 12 组相联Cache — 降低冲突失效

📚 延伸阅读与参考资料

资料内容链接
RISC-V特权规范CSR、Trap、中断完整定义riscv.org/specifications
RISC-V手册中文版免费教材crva.ict.ac.cn
OpenSBI源码M-mode固件参考实现github.com/riscv/opensbi
Linux RISC-V内核移植与驱动kernel.org
BOOM处理器UC Berkeley开源OoO核心github.com/riscv-boom/riscv-boom
香山处理器中科院开源高性能核心github.com/OpenXiangShan

相关课程

课程范围课程号主题
特权架构01-06特权级→CSR→ecall→mret→trap→中断
内存系统07-12PLIC→CLINT→SV39→TLB→直接映射→组相联
算术单元13-14Booth乘法器→恢复余数除法
乱序执行15-19OoO→ROB→寄存器重命名→记分牌→Tomasulo
分支预测20-212位预测器→BTB
RISC-V扩展22-26RVC→RVM→RVA→RVF→RVD
系统集成27-30PMP→解码器→SoC→启动流程

实验环境搭建

建议使用以下环境进行实验:

📊 SV39 vs SV48 vs SV57

模式虚拟地址位宽页表级数虚拟空间用途
Sv3232位2级4GBRV32嵌入式
Sv3939位3级512GBRV64标准
Sv4848位4级256TB大型服务器
Sv5757位5级128PB未来扩展

SV39覆盖512GB虚拟地址空间,对大多数应用足够。但大型数据库和AI训练框架可能需要更大的地址空间,这就是Sv48/Sv57存在的原因。

大页(Huge Page)优化

页面类型大小TLB覆盖范围用途
标准页4KB4KB/TLB项通用
大页(Mega)2MB2MB/TLB项数据库、DPDK
巨页(Giga)1GB1GB/TLB项大内存虚拟机

大页显著减少TLB缺失:64项TLB + 2MB大页 = 128MB覆盖范围(vs 256KB with 4KB页)。Linux通过mmap(MAP_HUGETLB)或透明大页(THP)支持。