RV64 Sv39内核MMU启用失败:写入satp后内存访问异常求助
RV64 Sv39虚拟内存启用失败问题排查
问题背景
基于QEMU和OpenSBI实现RV64 Sv39内核虚拟内存,执行MMU启用代码后触发内存访问错误。
启用核心代码:
map_pages(&base_pgdir, 0, KERNBASE, KERNBASE, 0x0000000002000000, PTE_R | PTE_W | PTE_X); u_long satp = SATP_MODE_SV39 | (base_pgdir >> 12); asm volatile("csrw satp, %0" : : "r"(satp));
其中KERNBASE为0x80000000L,SATP_MODE_SV39为0x8000000000000000。
执行csrw satp的下一条指令时,GDB报错:
Cannot access memory at address 0x802024ec
通过get_pa(pgdir, va)和debug_page(pgdir)测试显示,0x802024ec已做恒等映射到0x802024ec:
pa=00000000802024ec --------------------page---------------------|0000000080000000->0000000080000000 RWX | |0000000080001000->0000000080001000 RWX | ...... |0000000081e7f000->0000000081e7f000 RWX | ---------------------------------------------
可能的问题原因
1. 页表地址转换宏定义错误
这是最可能的核心问题:RV64 Sv39的PTE格式中,PPN(物理页号)对应物理地址的位12-55,而你的转换宏逻辑完全错误:
PA2PTE(x)将物理地址右移2位后与PTE_PPN按位与,正确逻辑应为(x >> 12) << 10(把物理地址转为页号后放到PTE的PPN字段)PTE2PA(x)将PTE的PPN部分左移2位,正确逻辑应为(x >> 10) << 12(把PTE中的页号转回物理地址)
这种错误会导致PTE中存储的物理地址完全偏离实际值,MMU无法正确翻译地址。
2. 页表自身未被映射
启用MMU后,CPU会通过页表翻译所有地址,包括页表本身的物理内存:
- 检查
base_pgdir及各级页表所在的物理页是否被包含在KERNBASE到KERNBASE+0x2000000的映射范围内 - 若页表物理地址不在该区间,
map_page中的pa < KERNBASE检查会阻止映射,导致CPU访问页表时触发错误
3. SATP寄存器设置错误
- 确认
base_pgdir是物理地址而非虚拟地址:如果base_pgdir存储的是虚拟地址,右移12位后得到的PPN无效,CPU无法定位页表根目录 - 检查
SATP_MODE_SV39的位位置是否正确:Sv39模式对应SATP寄存器的第63-60位为0b100,你的定义0x8000000000000000是正确的,但需排查类型转换导致的符号扩展问题
4. 指令预取与TLB一致性问题
执行csrw satp后,CPU预取队列可能保留MMU启用前的翻译结果,导致下一条指令访问异常:
- 需要在
csrw satp后添加sfence.vma刷新TLB、fence.i刷新指令预取队列:asm volatile("csrw satp, %0" : : "r"(satp)); asm volatile("sfence.vma"); asm volatile("fence.i");
5. 中间页表项权限错误
RV64要求中间级页表项(PTE1、PTE2)必须设置R/W权限才能被CPU访问:
- 你的代码中已设置
PTE_R | PTE_W | PTE_V,但需确认是否存在权限位被意外覆盖或缺失的情况
调试方法
1. 直接校验页表物理地址
在GDB中设置断点在csrw satp前,打印页表相关地址:
p/x base_pgdir # 确认页表根目录是物理地址 p/x *base_pgdir # 查看根页表第一个PTE的内容 p/x (PTE2PA(*base_pgdir)) # 手动计算一级页表地址,验证是否正确
2. 使用QEMU监控查看物理内存
在QEMU监控中执行xp命令直接访问物理内存,对比软件生成的页表:
xp 8x base_pgdir # 查看根页表的8个PTE xp 8x 0xXXXXXXX # 替换为一级页表物理地址,查看其内容
3. 启用QEMU MMU调试日志
启动QEMU时添加-d mmu参数,查看MMU操作的详细日志,包括页错误的具体原因:
qemu-system-riscv64 -d mmu ...其他启动参数
4. 验证页表自身的映射
手动检查页表根目录的物理地址是否被正确映射:
u_long pgdir_pa = (u_long)base_pgdir; u_long mapped_pa = get_pa(base_pgdir, pgdir_pa); printk("pgdir_pa: %lx, mapped_pa: %lx\n", pgdir_pa, mapped_pa);
若mapped_pa != pgdir_pa,说明页表自身未被正确映射。
5. 单步检查寄存器状态
在GDB中执行csrw satp后,立即查看SATP寄存器的值:
info registers satp
确认MODE字段为Sv39,PPN部分与页表根目录物理地址匹配。
内容的提问来源于stack exchange,提问作者nyan
相关产品推荐
相关产品推荐

