外观
虚拟内存:把地址翻译、隔离和延迟分配连起来
为什么地址不是物理位置
同一个虚拟地址在两个进程中可以指向不同物理页,两个不同虚拟地址也可以指向同一物理页。页表把这两种能力结合起来:既能隔离,也能受控共享。每次切换 satp,CPU 就得到新的翻译根;TLB 缓存翻译结果,因此修改内存中的 PTE 后还要考虑旧缓存是否仍可能被使用。
Sv39 把低 39 位虚拟地址分成三级各 9 位的索引和 12 位页内偏移。每张页表有 512 项,每项 8 字节,正好占 4096 字节。xv6 使用其支持范围中的低半区,MAXVA 为
设
CPU 依次取根表第 3 项、下一层第 5 项、末层第 7 项。若末项映射物理页基址 0x80002000,最终地址就是 0x80002abc。页内偏移不翻译;页号通过树查找。
flowchart LR VA[虚拟地址 VPN2/VPN1/VPN0/offset] --> L2[根页表 VPN2] L2 --> L1[第二层 VPN1] L1 --> L0[第三层 VPN0] L0 --> PA[物理页基址 + offset]
查看流程图文本
flowchart LR VA[虚拟地址 VPN2/VPN1/VPN0/offset] --> L2[根页表 VPN2] L2 --> L1[第二层 VPN1] L1 --> L0[第三层 VPN0] L0 --> PA[物理页基址 + offset]
PTE 不只是一个指针
PTE_V 表示有效,R/W/X 表示可读写执行,U 表示允许用户访问,A/D 表示访问和脏状态。对 RISC-V 叶项,W=1,R=0 是非法组合。有效且 R/W/X 全为零的项通常指向下一层页表;有读或执行权限的项是叶项。递归打印页表时,只看 V 就继续递归会把用户数据当作页表。
源码中的 walk(pagetable, va, alloc) 找到 PTE 槽位;mappages 建立映射;walkaddr 为用户地址提取物理地址;uvmunmap 删除映射;freewalk 回收页表树。删除一个映射和释放被映射物理页是两件事,所以 uvmunmap 有 do_free。共享页使这种区分成为正确性的核心。
假设进程仅用两个相距很远的 4KiB 页面。多级页表无需为中间所有虚拟页分配 PTE 数组,只分配实际路径上的页表页,节省空间。代价是缓存未命中时多次访存;TLB 和大页就是对这类代价的优化。
三种缺页要分清
第一种是非法访问:地址不属于进程许可区域,或在只读文本上写入,应该拒绝。第二种是惰性分配:地址范围合法,但暂时没有物理页,访问时才分配并清零。第三种是 COW:物理页已经存在,但写者必须拿到自己的私有副本。
2025 的 vmfault(pagetable, va, read) 原本已经处理惰性分配,且对“已经映射”的页返回失败。做 COW 时若保留这个早退而不先识别 COW,有效但只读的 COW PTE 永远进不了复制路径。正确的组织应先分类,再分别处理,不能把每个 fault 都当成缺一个页。
缺页处理完成后,普通 load/store 要重试原指令,因此保持故障 PC;系统调用的 ecall 已经被执行,其返回 PC 则前进 4 字节。这是内存与 traps 两章最关键的连接点。
大页节约什么,又使什么变难
2MiB 页在 level 1 就成为叶项,下方 9 位索引并入偏移,偏移长度变为 21 位。虚拟和物理起点都需要对齐到 2MiB。连续 2MiB 若用普通页,需要 512 个末级 PTE;改为大页后可以减少这部分页表存储及翻译压力。
代价是分配需要连续物理空间、权限粒度更粗,部分释放需要拆页。一个 2MiB 映射只想释放末尾 4KiB 时,直接释放整块会破坏前 511 页;继续保留整个大页又违反用户缩小地址空间的要求。2025 pgtbl 实验要求处理这种 demotion,详见大页实验。
资源不变量
无论实现 COW、大页还是 mmap,都可以检查以下关系:每个有效用户叶项只指向仍存活的物理内存;所有权限变化保留原来的隔离语义;最后一个引用消失后才释放资源;失败返回不能留下半建立映射;释放页表结构之前先解除叶映射。
例子:父子共享页 P,引用数是 2。子进程写后分配 Q,映射改到 Q,P 减为 1,Q 为 1。若先把 P 放回空闲链表再复制内容,另一 CPU 可能已拿走 P;若忘记递减,短测试看似正常,反复 fork 后却耗尽内存。
可运行模型与边界
在项目根目录运行:
sh
python3 labs/os/models.py
python3 -m unittest discover -s labs/os -p 'test_*.py' -vSv39 演示 4KiB、2MiB、1GiB 叶项、权限与重叠检测。Process 演示 COW 与引用计数。它不模拟 TLB、A/D 位、并行 hart、硬件异常入口,也不模拟页表页自身分配失败。因此它能验证概念例子,不能替代 xv6 的 pgtbltest/cowtest/usertests。
自测:页表里 PTE_W 已清零,内核直接写物理地址会自动触发 COW 吗?
不会。硬件检查的是本次访问使用的映射。内核通过自己的可写直接映射访问物理页时,不会经过用户只读 PTE。因此 copyout 必须主动识别并解除 COW。