跳至主要內容
Lab6:Copy-on-Write Fork for xv6

虚拟内存提供了一层间接性:内核可以通过将页表项(PTE)标记为无效或只读来拦截内存引用,从而引发页错误,并通过修改 PTE 来改变地址的含义。计算机系统中有一种说法,任何系统问题都可以通过一层间接性来解决。惰性分配实验室提供了一个例子。本实验将探索另一个例子:写时复制(Copy-on-Write, COW)的 fork。

要开始实验,切换到 cow 分支:

$ git fetch 
$ git checkout cow 
$ make clean

Plus大约 8 分钟操作系统MITXV6riscvC
Lab5:xv6 lazy page allocation

操作系统可以通过页表硬件实现的众多巧妙技巧之一是对用户空间堆内存的延迟分配(lazy allocation)。Xv6应用程序通过sbrk()系统调用向内核请求堆内存。在我们提供的内核中,sbrk()会分配物理内存并将其映射到进程的虚拟地址空间。对于大型请求,内核分配和映射内存可能需要很长时间。例如,1GB的内存由262,144个4096字节的页面组成;即使每个分配操作都很廉价,但如此大量的分配操作仍然非常耗时。此外,一些程序分配的内存比实际使用的要多(例如,用于实现稀疏数组),或者提前分配内存但并未立即使用。为了在这些情况下让sbrk()更快地完成,复杂的内核会采用延迟分配用户内存的方式。也就是说,sbrk()不会立即分配物理内存,而是只记住哪些用户地址被分配,并在用户页表中将这些地址标记为无效。当进程首次尝试使用任何延迟分配的内存页面时,CPU会生成一个页错误(page fault),内核通过分配物理内存、将其清零并映射来处理这个错误。在这个实验中,你将向xv6添加这种延迟分配功能。


Plus大约 6 分钟操作系统MITXV6riscvC
Lab 2: System Calls

在上一个实验中,你使用系统调用编写了一些实用程序。在这个实验中,你将向 xv6 添加一些新的系统调用,这将帮助你理解它们的工作原理,并让你接触到 xv6 内核的一些内部机制。在后续的实验中,你将添加更多的系统调用。

注意

开始编码之前,请阅读 xv6 书籍的第 2 章,以及第 4 章的第 4.3 节和第 4.4 节,并阅读相关的源文件:

  • 用户空间的系统调用代码 在 user/user.h 和 user/usys.pl 中。
  • 内核空间的系统调用代码 在 kernel/syscall.h 和 kernel/syscall.c 中。
  • 与进程相关的代码 在 kernel/proc.h 和 kernel/proc.c 中。

Plus大约 7 分钟操作系统MITCriscvXV6
Lab 4:traps

本实验探索系统调用如何实现陷入。你将使用栈先进行一些热身训练,之后你将实现一个用户计陷入处理的例子。

注意

在你开始编码之前,请阅读 xv6 书的第四章以及相关文件:

  • kernel/trampoline.S: 涉及从用户空间切换到内核空间以及返回的汇编过程。
  • kernel/trap.c: 处理所有中断的代码。

Plus大约 12 分钟操作系统MITXV6riscvC
XV6源码精读 -- vm.c

uvmcreate()

在操作系统中,每个进程都有自己的页表,用于管理其虚拟地址空间。当操作系统创建新的进程时,需要为该进程分配一个新的页表。

// 创建一个空的用户页表
// 如果没有内存了就返回0
pagetable_t
uvmcreate()
{
  pagetable_t pagetable;
  pagetable = (pagetable_t) kalloc();
  if(pagetable == 0)
    return 0;
  //将此页表都填充成0
  memset(pagetable, 0, PGSIZE);
  return pagetable;
}

Plus大约 2 分钟操作系统MITXV6riscvC
Lab 3: page tables

实验原文地址:Lab: page tables (mit.edu)

在这个实验中,你将探索页面表并修改它们以加速特定的系统调用,并检测哪些页面已被访问。

注意

在你开始编码之前,请阅读 xv6 书的第三章以及相关文件:

  • kernel/memlayout.h,其中包含内存布局的信息。
  • kernel/vm.c,其中包含大部分虚拟内存(VM)代码。
  • kernel/kalloc.c,其中包含分配和释放物理内存的代码。 同时,参考 RISC-V 特权架构手册可能也会有所帮助。

Plus大约 11 分钟操作系统MITXV6riscvC
XV6源码精读 -- proc.c

proc_pagetable(struct proc *p)

此函数的作用是创建一个新的用户页表,并在其中映射跳板代码和陷阱帧页。这个函数通常用于操作系统内核中创建进程时初始化进程的页表。

// 创建给定进程的用户页表,没有用户内存,但有跳板和陷阱帧页。
pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;

  // 创建一个空白页表
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;

  //将跳板代码(用于系统调用返回)映射到最高的用户地址
  //只有内核使用该代码进出用户空间,所以不用设置PTE_U
  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
              (uint64)trampoline, PTE_R | PTE_X) < 0){
    uvmfree(pagetable, 0);
    return 0;
  }

  // 将陷阱帧页映射到跳板页的下方,用于 trampoline.S
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  return pagetable;
}

Plus大约 2 分钟操作系统MITXV6riscvC