xv6 Lab9 mmap - MIT 6.1810 Fall 2025 Operating System

本文最后更新于 2026年8月20日 晚上

感觉最难的 lab...起码是代码量最大的。

阅读

事实上这就是 Lab5 COW 里面提到的 8.1 Page Fault Basics | MIT6.S081 这一章节里面所涉及的内容(一个小节)。所以需要读的内容也不多,只是一种机制。

memory mapped files 的核心思想是将完整或部分文件加载到内存中,从而直接使用内存的 load store 来操控文件,减少了磁盘 I/O。

需要实现的接口(来自 man 2 mmap):

void *mmap(void *addr, size_t len, int prot, int flags,
           int fd, off_t offset);
           
int munmap(void *addr, size_t len);

这个 lab 不要求完全实现 POSIX 里面的规定,可以做出如下简化问题的假设:

对于 mmap()

  • addr 总是 0,表示由 kernel 决定映射到哪个虚拟地址。

  • mmap 成功时返回映射地址,失败时返回 0xffffffffffffffff

  • len 表示要映射的字节数,它可能和文件长度不同。

  • prot 表示这段内存是否可读、可写和/或可执行。这个 Lab 中只需要处理 PROT_READPROT_WRITE 或两者组合。

  • flags 只会是 MAP_SHAREDMAP_PRIVATE

  • MAP_SHARED 表示对映射内存的修改应该写回文件。

  • MAP_PRIVATE 表示修改不应该写回文件。

  • fd 是待映射文件的已打开 file descriptor。

  • offset 可以假设总是 0,即总是从文件开头开始映射。

  • 关于懒分配:这里采用的是懒分配模式。也就是说,mmap() 本身不应该分物理内存,也不应该读取文件。只有当 page fault 发生时,再在 usertrap() 或它调用的 page fault 处理代码里完成真正的分配和文件读取。

    回忆 cow lab

  • 如果两个进程映射同一个 MAP_SHARED 文件,这个 Lab 允许它们 不共享同一个 physical page

关于 munmap()

  • 删除指定范围内的 mmap 映射。
  • 如果进程已经修改过这段内存,并且对应的是 MAP_SHARED 映射,那么在解除映射之前,修改应该先写回文件。
  • 本来可能是只解除整个 mmap 映射中的一部分(从中间挖洞)(回忆拆 super page),但是这个 lab 是要么开头要么结尾要么整个,总之不会只在中间。

实现

依旧以 hints 为线索。

建立系统调用

还是老一套流程。

为每个进程记录 mmap 区域

建立每进程的 struct vma,即 Virtual Memory Area,虚拟内存区域,记录 mmap() 创建的虚拟地址范围信息。

// kernel/proc.h
struct vma {
  int valid;
  uint64 addr;
  uint64 len;
  uint64 offset;
  int prot;
  int flags;
  struct file *f;
};

offset len 不使用和原来签名一样的类型。

原因是 size_toff_t 属于 mmap 接口类型,目前定义在 defs.h;但很多源文件会先包含 proc.h、后包含 defs.h,导致解析 struct vma 时还不认识它们。

uint64 定义在更基础的 types.h 中,而且在 xv6 里:

  • size_t 实际就是 64 位无符号整数;
  • 本实验的 offset 固定为 0,不需要 off_t 的有符号语义;
  • 虚拟地址、长度和文件偏移本身也适合用 uint64 保存。

因此,用户接口和 sys_mmap() 参数仍按照 man page 使用:

size_t len;
off_t offset;

而 VMA 是内核内部数据结构,用 uint64 保存转换后的数值即可。两层不要求使用完全相同的类型名称,只需数值能正确表示。

然后,由于 xv6 kernel 中没有 variable-size kernel allocator,直接声明一个固定大小(16)的 VMA 数组,需要时从里面分配。

// kernel/def.h
#define NVMA 16
// kernel/proc.c
struct vma vma[NVMA];

实现 mmap()

首先要理解 mmap 的映射区域到底位于虚拟地址空间的哪些部分:

地址空间可以大致理解为:

低地址
┌──────────────────────┐
│ 程序代码、数据、堆      │
├──────────────────────┤ ← 原来的 p->sz┤
│ 尚未使用的地址空间      │
├──────────────────────┤ ← limit / TRAPFRAME
│ TRAPFRAME            │
├──────────────────────┤
│ TRAMPOLINE           │
└──────────────────────┘ ← MAXVA
高地址

mmap 区域不能覆盖最上面的两个特殊页面。

一开始(以及读到的知乎文章 MIT XV6 操作系统 实验全解 - 知乎 )里面的实现是这样的:

代码C · 69 行
uint64
sys_mmap(void)
{
  void *addr;
  size_t len;
  int prot, flags, fd;
  off_t offset;
  struct file *f;
  struct proc *p = myproc();

  argaddr(0, (uint64 *)&addr);
  argaddr(1, &len);
  argint(2, &prot);
  argint(3, &flags);
  argaddr(5, (uint64 *)&offset);

  // This lab only supports kernel-selected addresses and mappings that
  // start at the beginning of a regular file.
  if(addr != 0 || len == 0 || offset != 0) {
    return -1;
  }
  if((prot & ~(PROT_READ | PROT_WRITE)) != 0 ||
     (prot & (PROT_READ | PROT_WRITE)) == 0) {
      return -1;
  }
  if(flags != MAP_SHARED && flags != MAP_PRIVATE) {
    return -1;
  }
  // 将 fd 对应的文件对应到 struct file *f 上
  if(argfd(4, &fd, &f) < 0 || f->type != FD_INODE || !f->readable) {
    return -1;
  }
  if(flags == MAP_SHARED && (prot & PROT_WRITE) && !f->writable) {
    return -1;
  }

  // 为 mmap 选择一段虚拟地址进行映射,
  // p->sz 是目前的进程能用到的最高的地址空间,将新映射放在它后面
  addr = (void *)PGROUNDUP(p->sz);
  uint64 limit = MAXVA - 2 * PGSIZE;  // leave TRAPFRAME/TRAMPOLINE untouched
  if((uint64)addr >= limit || len > limit - (uint64)addr) {
    return -1;
  }
  // 长度round up到最近的页面字节数
  size_t maplen = PGROUNDUP(len);

  int idx;
  for(idx = 0; idx < NVMA; idx++){
    if(!p->vma[idx].valid) {
      break;
    }
  }
  if(idx == NVMA) {
    return -1;
  }
  struct vma *vma = &p->vma[idx];

  vma->valid = 1;
  vma->addr = (uint64)addr;
  vma->len = len;
  vma->permissions = prot;
  vma->offset = offset;
  vma->prot = prot;
  vma->flags = flags;
  vma->f = filedup(f);

  p->sz = (uint64)addr + maplen;
  return (uint64)addr;
}

vma 如果按照答主的实现,从 p->sz 之后一路往上分配,在 2025 版的 lab (这个答主是 Fall 2020 版本)里面测试会不通过,然后大致理由是:

  • 由于 mmapmunmap 的触发时机不同以及目前寻找 vma 都是下标分配,事实上每个 vmaaddr 及其下标会不存在正相关的关系,而且事实上难以管理 p->sz ,只能任凭其增长,无法缩小。
  • 那么,假设我们 munmap 了某个 vma,会产生类似内部碎片的东西;
  • 测试数据里面,这一块已经被 munmap 的部分,如果再触发 page fault,由于其满足 va < p->sz,并且并不处于 vma 的范围内,会被判定为由 sbrk() 产生的 lazy allocation 页,再度分配实际的物理页框;
  • 但是,测试数据是:解除某页的映射之后再度读取,预期触发非法访问杀死进程。这里显然不满足了

所以我们实际上应该换一种分配 vma 的方式,我想到了从 MAXVA - 2*PGSIZE 也就是蹦床页往下两页开始倒着分配

struct proc 里面添加一个 uint64 mmap_top;,表示下一次分配 vma 的起始处。因为虚拟地址近乎是无穷大的,这样不会对 OS 产生影响。

但是其实我也不好说。因为 vma 一直执行,p->mmaptop 只会单调增长。

Linux 的实现里面,OS 会去找空余的碎片。但是这里实现确实有点麻烦了。

所以大致的实现是这样的:

代码C · 76 行
uint64
sys_mmap(void)
{
  void *addr;
  size_t len;
  int prot, flags, fd;
  off_t offset;
  struct file *f;
  struct proc *p = myproc();

  argaddr(0, (uint64 *)&addr);
  argaddr(1, &len);
  argint(2, &prot);
  argint(3, &flags);
  argaddr(5, (uint64 *)&offset);

  // This lab only supports kernel-selected addresses and mappings that
  // start at the beginning of a regular file.
  if(addr != 0 || len == 0 || offset != 0) {
    return -1;
  }
  if((prot & ~(PROT_READ | PROT_WRITE)) != 0 ||
     (prot & (PROT_READ | PROT_WRITE)) == 0) {
      return -1;
  }
  if(flags != MAP_SHARED && flags != MAP_PRIVATE) {
    return -1;
  }
  // 将 fd 对应的文件对应到 struct file *f 上
  if(argfd(4, &fd, &f) < 0 || f->type != FD_INODE || !f->readable) {
    return -1;
  }
  if(flags == MAP_SHARED && (prot & PROT_WRITE) && !f->writable) {
    return -1;
  }

  // 为了防止从 p->sz 开始分配,结果因为创建/销毁 vma 的顺序不同,导致 p->sz 没有可能正确回收的问题
  // 不使用 p->sz 开始分配的方法,而是从顶上开始倒着分配
  // 因为虚拟地址近乎是无穷大的,这样不会对 OS 产生影响
  // #define MMAPTOP (MAXVA - 2 * PGSIZE)
  if(len > MMAPTOP) {
    return -1;
  }
  size_t maplen = PGROUNDUP(len);
  if(maplen > p->mmap_top) {
    return -1;
  }

  uint64 mapaddr = p->mmap_top - maplen;
  if(mapaddr < PGROUNDUP(p->sz)) {
    return -1;
  }
  addr = (void *)mapaddr;

  int idx;
  for(idx = 0; idx < NVMA; idx++){
    if(!p->vma[idx].valid) {
      break;
    }
  }
  if(idx == NVMA) {
    return -1;
  }
  struct vma *vma = &p->vma[idx];

  vma->valid = 1;
  vma->addr = (uint64)addr;
  vma->len = maplen;
  vma->offset = offset;
  vma->prot = prot;
  vma->flags = flags;
  vma->f = filedup(f);

  p->mmap_top = (uint64)addr;
  return (uint64)addr;
}

务必注意,mmap() 里面没有真的分配页,是要落实到 page fault 发生时才真的分配的。

这里就只是,记录下来这个文件(通过传入 fd)应该映射到这个特定的 VMA 这里。

处理 mmap page fault

添加代码,让 mmap region 中发生 page fault 时:

  1. 分配一个物理页;
  2. 从文件中读取相关的 PGSIZE 到该页;
  3. 把该页映射进用户地址空间。

使用 readi() 读取文件。

readi() 的作用是:从某个 inode 表示的文件中,从指定偏移开始读取若干字节,复制到指定内存地址。

int readi(struct inode *ip, int user_dst, uint64 dst, uint off, uint n);

参数含义:

  • ip:要读取的文件对应的 inode,例如 vma->f->ip。

  • user_dst:目标地址类型。

    • 1:dst 是用户虚拟地址。
    • 0:dst 是内核地址。
  • dst:数据复制到哪里。

  • off:从文件的哪个字节开始读。

  • n:最多读取多少字节。

  • 返回值:实际读到的字节数,失败可能返回 -1。

例如 readi(ip, 0, (uint64)mem, 4096, 4096);

表示从文件偏移 4096 处开始读取 4096 字节,写入内核地址 mem。

根据 cow lab 的经验,这里应该是修改 vmfault() 函数。

代码:

代码C · 65 行
uint64
vmfault(pagetable_t pagetable, uint64 va, int read)
{
  uint64 mem;
  struct proc *p = myproc();

  if(va >= MAXVA) {
    return 0;
  }
  va = PGROUNDDOWN(va);
  if(ismapped(pagetable, va)) {
    return 0;
  }

  int index = ismmaped(p, va);
  if(index != -1) {
    struct vma *vma = &p->vma[index];
    struct inode *ip = vma->f->ip;
    // mem 是 kalloc() 返回的内核地址
    if((mem = (uint64)kalloc()) == 0) {
      return 0;
    }
    memset((void *)mem, 0, PGSIZE);
    ilock(ip);
    // va - vma->addr = 当前产生 page fault 的页面与 vma 开始地址的距离
    // 看似没保证页对齐,但是 vma->offset 永远是 0,而前两者已经页对齐,所以无妨
    uint64 fileoff = (va - vma->addr) + vma->offset;
    if(readi(ip, 0, mem, fileoff, PGSIZE) == -1) {
      iunlock(ip);
      kfree((void *)mem);
      return 0;
    }
    int flags = PTE_U;
    if(vma->prot & PROT_READ) {
      flags |= PTE_R;
    }
    if(vma->prot & PROT_WRITE) {
      flags |= PTE_R | PTE_W;
    }
    if(mappages(pagetable, va, PGSIZE, mem, flags) != 0) {
      iunlock(ip);
      kfree((void *)mem);
      return 0;
    }
    iunlock(ip);
    return mem;
  }

  // Only addresses below p->sz belong to the ordinary lazy-allocation area.
  // A high address removed by munmap must remain invalid.
  if(va >= p->sz) {
    return 0;
  }

  mem = (uint64) kalloc();
  if(mem == 0) {
    return 0;
  }
  memset((void *) mem, 0, PGSIZE);
  if (mappages(p->pagetable, va, PGSIZE, mem, PTE_W|PTE_U|PTE_R) != 0) {
    kfree((void *)mem);
    return 0;
  }
  return mem;
}

这里面比较麻烦的点在于这个 fileoff,但其实就是计算偏移。

引用假设 vma->addr = 0x4000 vma->offset = 0 fault va = 0x6123 先把 fault 地址按页向下对...

假设

vma->addr   = 0x4000
vma->offset = 0
fault va    = 0x6123

先把 fault 地址按页向下对齐:

va = PGROUNDDOWN(0x6123) = 0x6000

虚拟地址区域:

VMA 虚拟地址空间

0x4000              0x5000              0x6000              0x7000
  │                   │                   │                   │
  ▼                   ▼                   ▼                   ▼
  ┌───────────────────┬───────────────────┬───────────────────┐
  │ VMA 第 0 页       │ VMA 第 1 页       │ VMA 第 2 页       │
  └───────────────────┴───────────────────┴───────────────────┘
                                            ▲
                                            │
                                    fault va = 0x6123
                                    所在页 = 0x6000

计算它距离 VMA 开头有多远:

va - vma->addr
= 0x6000 - 0x4000
= 0x2000

因此:

fileoff = (va - vma->addr) + vma->offset;

得到:

fileoff = 0x2000 + 0 = 0x2000

文件内容:

文件字节偏移

0x0000              0x1000              0x2000              0x3000
  │                   │                   │                   │
  ▼                   ▼                   ▼                   ▼
  ┌───────────────────┬───────────────────┬───────────────────┐
  │ 文件第 0 页       │ 文件第 1 页       │ 文件第 2 页       │
  └───────────────────┴───────────────────┴───────────────────┘
                                            ▲
                                            │
                                      fileoff = 0x2000

最终对应关系:

虚拟 mmap 区域                         文件

0x4000  VMA 第 0 页  ───────────────▶  offset 0x0000
0x5000  VMA 第 1 页  ───────────────▶  offset 0x1000
0x6000  VMA 第 2 页  ───────────────▶  offset 0x2000

因此下面这句:

readi(ip, 0, mem, fileoff, PGSIZE);

表示:

文件 [0x2000, 0x3000)
          │
          │ readi()
          ▼
新分配的物理页 mem
          │
          │ mappages()
          ▼
用户虚拟地址 [0x6000, 0x7000)

一句话总结:fileoff 用来确定“发生 page fault 的这个虚拟页面,对应文件中的哪一页数据”。

另外,注意 flags

另外:

在 vm.c 里面遇到的情况,需要#include "file.h"

实现 munmap()

最难的一个小题。

系统调用的函数签名:

int munmap(void *addr, size_t len);

addrlen 都是虚拟地址(虚拟地址空间的 VMA 部分)。

重点在于不需要处理部分挖洞的情况。也就是,只需要考虑这样的情况:

spec 说得不是很详细,但是我认为可以跨 VMA 处理,同时因为 VMA 是一直往前分配的,也就是这样:

这个图比较清晰,就是对于一个 vma 而言:

  • 最左端,是 vma->addr
  • 整段的长度,是 vma->len
  • 其余的不重要,offset 本题都默认是 0

这样的情况只是可能存在。

于是我就考虑,从 addr 开始,计算需要释放的字节范围窗口,然后一边释放一边缩小窗口,每次循环,都遍历所有的 vma 进行扫描,按照 addr 所在的地方来进行判断。(但是每轮循环只处理一个,扫到了就处理这个)。

得到了之后,通过不断的比较,确定是哪种情况(头 or 尾),然后再确定具体而言需要处理的范围。

之后,在这个范围内,一页一页地处理:

  • 如果 vma->flags == MAP_SHARED,那么就需要执行写回的操作

    • 计算 fileoff,也就是文件内的偏移量。因为是要写回文件,但是文件内部的调整是通过 inode 结构体的偏移量实现的,这里需要手动指定

      为什么不能完全处理,因为解映射可以是只解除一部分的。

    • 写回文件,具体写多少也是个问题。默认来说,我们一次是写一页(因为是一页一页地处理)。但是,有时候末尾可能有不足一页的部分,这个时候 write_len 就变成了最后剩下的长度。

      上面的部分需要获取 ip->size,就算是读取也需要上 inode 锁。

    • 最后,写回。 spec 里面说,要参考 filewrite(),但是这里有问题,因为 filewrite() 的文件内偏移量无法自己指定。具体而言见下面的部分,总之写一个 filewriteat() 辅助函数,可以指定偏移量。

    • 最后,如果正好是释放了一整个 vma,那么还需要减少文件的 ref(fileclose(f)),再清空掉这个 vma

  • 最后记得修改窗口。

最核心的逻辑,因为会发现后面的 kexit() 的修改,需要做和 munmap() 一样的事情,所以提取出来,作为一个传参的函数,放在 vm.c 里面。

// kernel/sysfile.c
uint64
sys_munmap(void)
{
  uint64 addr;
  size_t len;

  argaddr(0, &addr);
  argaddr(1, &len);
  return vmaunmap(myproc(), addr, len);
}
代码C · 80 行
// kernel/vm.c
int
vmaunmap(struct proc *p, uint64 addr, uint64 len)
{
  addr = PGROUNDDOWN(addr);
  len = PGROUNDUP(len);
  if(len == 0 || addr + len < addr) {
    return -1;
  }

  while(len > 0) {
    struct vma *vma = 0;

    // Re-scan because VMA slot order need not match virtual-address order.
    for(int idx = 0; idx < NVMA; idx++) {
      struct vma *cand = &p->vma[idx];
      if(cand->valid && addr >= cand->addr &&
         addr - cand->addr < cand->len) {
        vma = cand;
        break;
      }
    }

    if(vma == 0) {
      return -1;
    }

    struct file *f = vma->f;
    struct inode *ip = f->ip;
    uint64 vma_end = vma->addr + vma->len;
    uint64 unmap_end = addr + len;
    uint64 free_end = unmap_end < vma_end ? unmap_end : vma_end;
    uint64 free_len = free_end - addr;

    for(uint64 pageva = addr; pageva < free_end; pageva += PGSIZE) {
      pte_t *pte = walk(p->pagetable, pageva, 0);
      if(pte == 0 || (*pte & PTE_V) == 0) {
        continue;
      }

      if(vma->flags == MAP_SHARED && (vma->prot & PROT_WRITE)) {
        uint64 pa = PTE2PA(*pte);
        uint64 fileoff = vma->offset + (pageva - vma->addr);
        uint write_len = PGSIZE;

        ilock(ip);
        if(fileoff >= ip->size) {
          write_len = 0;
        } else if(write_len > ip->size - fileoff) {
          write_len = ip->size - fileoff;
        }
        iunlock(ip);

        if(write_len > 0 &&
           filewriteat(f, 0, pa, fileoff, write_len) != write_len) {
          return -1;
        }
      }
      uvmunmap(p->pagetable, pageva, 1, 1);
    }

    if(addr == vma->addr && free_end == vma_end) {
      fileclose(f);
      memset(vma, 0, sizeof(*vma));
    } else if(addr == vma->addr) {
      vma->addr += free_len;
      vma->len -= free_len;
      vma->offset += free_len;
    } else if(free_end == vma_end) {
      vma->len -= free_len;
    } else {
      panic("vmaunmap: hole");
    }

    addr = free_end;
    len -= free_len;
  }

  return 0;
}

为什么需要 filewriteat()

这里的问题主要有两个:文件内的偏移量源地址的类型

首先看 filewrite() 最关键的部分:

if ((r = writei(f->ip, 1, addr + i, f->off, n1)) > 0) {
  f->off += r;
}

filewrite() 没有接收文件偏移量的参数,它使用的是 struct file 内部的 f->off。每写入一段数据,还会自动把 f->off 往后推进。这对普通的 write() 是正确的,因为普通文件读写本来就需要维护一个当前读写位置。

但 mmap 写回文件时,文件位置不能由 f->off 决定,而是由这张页在 VMA 内的位置决定:

fileoff = vma->offset + (pageva - vma->addr);

例如,假设映射从文件偏移 0 开始,现在要解映射 VMA 的第 2 页:

VMA 第 0 页  ────▶  文件 offset 0
VMA 第 1 页  ────▶  文件 offset 4096
VMA 第 2 页  ────▶  文件 offset 8192

这时必须把该页写到文件的 8192 处。但 f->off 可能是 0,也可能已经被普通的 read() / write() 改到了其他位置。而且 filedup() 只是增加原来 struct file 的引用计数,VMA 和 fd 指向的仍然是同一个 struct file,所以不能假设 f->off 始终等于 mmap 需要的位置。

即使在 munmap() 时直接把整个 VMA 写回,也没有解决这个问题。filewrite() 仍然会从 f->off 开始写,而不是从 vma->offset 开始写。另外,munmap() 可能只解除 VMA 的一部分,映射长度也可能大于文件长度,所以仍然需要明确指定这次要写回的文件区间。

于是把 filewrite() 中写 inode 文件的逻辑提取成 filewriteat()

int
filewriteat(struct file *f, int user_src, uint64 src, uint64 off, int n);

最后的调用是:

filewriteat(f, 0, pa, fileoff, write_len);

其中:

  • 0 表示是 pa 不是 va

    其实这里(vmunmap())传 va 也可以(就是循环里面的 pageva),但是都一样。

  • pa 是当前需要写回的物理页;

  • fileoff 是这张页在文件内的正确位置;

  • write_len 是这次实际允许写入的长度。

filewriteat() 仍然保留 filewrite() 里面的分批写入、文件系统事务和 inode 加锁逻辑,但它使用显式传入的 off,并且不读取、不修改 f->off

修改 kexit()

就是释放所有 VMA,和 munmap() 一样。

// munmap all mmap regions
for(int i = 0; i < NVMA; i++) {
    struct vma *vma = &p->vma[i];
    if(vma->valid) {
      if(vmaunmap(p, vma->addr, vma->len) < 0) {
        panic("kexit: vmaunmap");
      }
    }
}

修改 kfork()

也就是复制一下,和复制 trapframe 没什么本质区别。hints 也提示了需要给文件引用 +1,照做就行了。

for (int i = 0; i < NVMA; ++i) {
    struct vma* vma = &p->vma[i];
    struct vma* nvma = &np->vma[i];
    *nvma = *vma;
    if (nvma->valid) {
      filedup(nvma->f);
    }
}

关于 challenges

其实我觉得这几个都挺有意思的,但是有点累了,在这里如果之后有兴趣,回来实现一下。

引用If two processes have the same file mmap-ed (as in the fork tests), shar...
  • If two processes have the same file mmap-ed (as in the fork tests), share their physical pages. You will need reference counts on physical pages.
  • Your solution probably allocates a new physical page for each page read from the mmap-ed file, even though the data is also in kernel memory in the buffer cache. Modify your implementation to use that physical memory, instead of allocating a new page. This requires that file blocks be the same size as pages (set BSIZE to 4096). You will need to pin mmap-ed blocks into the buffer cache. You will need worry about reference counts. One benefit of fixing this double-caching is that it also helps make read() and write() consistent with mmap. That is, if some mmaped file data is modified through the memory mapping, read should return those modifications, and likewise, if an application calls write, the write should appear in any active memory mappings of that file. You might find it interesting to read the paper on the unified buffer cache.
  • Remove redundancy between your implementation for lazy allocation and your implementation of mmap-ed files. (Hint: create a VMA for the lazy allocation area.)
  • Modify exec to use a VMA for different sections of the binary so that you get on-demand-paged executables. This will make starting programs faster, because exec will not have to read any data from the file system.
  • Implement page-out and page-in: have the kernel move some parts of processes to disk when physical memory is low. Then, page in the paged-out memory when the process references it.

写完会发现自己对于 xv6 的日志以及事务系统好像就没什么了解,只有在 file system 的 lab 里面简单操作了一下诸如 bread() brelse() 这样的简单 API,但是没有像 inode 这样深入了解,这也是个 TODO 吧。

不过似乎好好读读书(xv6 book)就行了。