Skip to content

mmap:先承诺一个地址范围,再按访问装入文件页 ​

版本:mmap,提交 5648f647958db15c43b90e2a03a9c01ac680f472。先读内存和文件系统。实验限定 addr=0、offset=0,支持 READ/WRITE 与 SHARED/PRIVATE,以及整段、前缀、后缀解除;不要求在中间打洞或完整 POSIX mmap 行为。

VMA 与页表分工 ​

VMA 记录“这段地址允许对应哪个文件的哪些字节,权限是什么”,页表记录“目前哪些地址已经有物理页”。刚 mmap 完可以只有 VMA,没有 PTE。第一次访问时缺页,内核根据 VMA 才知道该访问合法且数据应该来自哪里。

设计固定数组的 VMA:有效位、起点、长度、权限、标志、file 指针、文件偏移。保存原始有效字节长度和按页取整覆盖范围的关系,避免最后一页写回越界。选择虚拟地址区间时避开进程堆栈与高端特殊映射,并检查长度为零、取整溢出和区间相加溢出。

flowchart TD
  A[mmap 登记 VMA 并 filedup] --> B[返回地址,尚无物理页]
  B --> C[用户第一次 load/store]
  C --> D[缺页:查 VMA 与权限]
  D --> E[分配清零页并 readi]
  E --> F[安装 PTE 后重试指令]
  F --> G[munmap 或 exit]
  G --> H[按需写回、解除映射、fileclose]
查看流程图文本
flowchart TD
  A[mmap 登记 VMA 并 filedup] --> B[返回地址,尚无物理页]
  B --> C[用户第一次 load/store]
  C --> D[缺页:查 VMA 与权限]
  D --> E[分配清零页并 readi]
  E --> F[安装 PTE 后重试指令]
  F --> G[munmap 或 exit]
  G --> H[按需写回、解除映射、fileclose]

缺页处理与文件偏移 ​

沿 usertrap → vmfault 现有异常路径审查接入点。2025 已有处理 sbrk 惰性分配的 vmfault,但其 va>=p->sz 检查会拒绝另选高地址的文件映射。因此必须先区分 VMA 与原 heap 区域,不能把所有 mmap fault 原封不动送给只认 sz 的处理器。

对 fault 地址向下取整为页起点 pageva,文件偏移应为 vma.offset + pageva - vma.start。先检查本次读写是否符合 VMA 权限,再分配并清零物理页,锁定 inode,用 readi 读取对应文件内容,然后安装 PTE。若读取不足一页,未读部分保持零;若任一步失败,释放新页,不留下半成品映射。

不要使用 file 对象的共享 off 作为 mmap 缺页偏移。两个页可能乱序访问,fork 或重复映射还可能共享 file;每次读取必须基于 fault 地址计算显式偏移。先访问第二页再访问第一页应得到正确内容,而不是颠倒的数据。

munmap 同时改变两层状态 ​

解除映射时,先处理已存在的物理页。对于 MAP_SHARED 的修改,按正确文件偏移写回;MAP_PRIVATE 不写回。未访问的页没有 PTE,跳过即可,不能将合法惰性洞当 panic。之后解除实际映射,再缩小或删除 VMA。

若移除前缀,start 增加,length 减少,file offset 也增加相同字节数;只调整 start 会让以后缺页重新读文件开头。移除后缀通常只缩短长度。整段移除后释放 VMA 对 file 的引用。mmap 建立时使用 filedup,所以用户 close(fd) 后映射仍有效。

写回要考虑日志容量。参考 filewrite 的事务分块思路,但不要直接依赖它会推进共享 file offset 的行为。只有 VMA 实际对应的有效字节应写回;把最后页整整 4096 字节写回可能意外扩展文件。实验允许不依据 PTE_D 精确筛选脏页,但这只是性能宽松,不能放宽 SHARED/PRIVATE 语义。

fork 与 exit 不能留到最后猜 ​

kfork() 复制 VMA 元数据,增加每个 file 引用,并确保子进程拥有独立的 VMA 槽状态;允许子进程首次缺页时独立读页,不要求共享物理页。若父 VMA 部分修改但未写回,实验允许的简化与完整 MAP_SHARED 即时可见性并不等价,不能把测试范围描述成全部 Unix 语义。

kexit() 应像 munmap 一样处理所有映射,使 SHARED 修改写回并释放引用。还要确保失败的 fork、被杀进程和 exec 地址空间替换不会留下悬空引用;对非测试必需路径,应在设计说明中明确选择。单独扩展 exec 生命周期时,也应避免重复清理同一 VMA。

验收与诊断 ​

运行 mmaptest、usertests -q、make grade。测试非法写入或解除后的访问时,出现用户异常信息可能是预期行为,要看对应测试最终结果。只读映射写入应被拒绝;关闭 fd 后访问应仍可行;MAP_PRIVATE 修改不应改变文件;前缀 unmap 后剩余部分应保持正确偏移。

额外做“只访问中间一页后整段解除”“先访问后页”“长度不是页倍数”“fork 后双方分别退出”。文件引用泄漏往往表现为重复映射后资源耗尽,offset 错误则表现为所有页都显示第一块内容。

自测:为什么 mmap 成功不意味着 readi 已经执行?

实验要求惰性装页。mmap 的成功表示地址范围与文件引用已经建立;第一次真正访问页面时才付出物理页分配和文件读取成本。

来源:2025 mmap 官方任务,对应 trap.c/vm.c/proc.c/sysfile.c/file.c。本页描述的实现边界以实验子集为准。