Lab: Copy-on-Write Fork for xv6

Implement copy-on write

这个Lab的任务只有一个——将写入时复制这一功能实现。Lab对此给出了十分值得参考的步骤,翻译如下:

有了上面的步骤之后,我们基本上可以按照这个顺序来解决这个Lab。

修改uvmcopy

首先是修改 uvmcopy() ,我们将程序为子进程分配页面这一步及之后的指令注释掉,添加我们的代码。

在这里,我们的任务是将不需要立刻复制的页面从父进程处以只读的方式映射至子进程的页表中,复制后父进程同样不允许写入该页面。

那么,对于父进程的一个页面,一般有三种情况:

综上,我们对 uvmcopy() 作如下更改:

int 
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags, new_flags;

  for (i = 0; i < sz; i += PGSIZE) {
    if ((pte = walk(old, i, 0)) == 0)
      panic("uvmcopy: pte should exist");
    if ((*pte & PTE_V) == 0)
      panic("uvmcopy: page not present");
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);

    // if PTE initially doesn't have permission to write
    // then don't mark it as COW page
    if (flags & PTE_W) {
      new_flags = (flags & ~PTE_W) | PTE_C;
      *pte = (*pte & ~PTE_W) | PTE_C;
    }
    else {
      new_flags = flags;
    }

    if (mappages(new, i, PGSIZE, pa, new_flags) < 0) {
      goto err;
    }

    acquire(&ref.lock);
    ++ref_count[PAGE_INDEX(pa)];
    release(&ref.lock);
  }
  return 0;

err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}

修改 usertrap

完成对 uvmcopy() 的修改后,我们需要让操作系统在遇到写入时复制页面时可以正确地处理。

进程在尝试对写入时复制页面进行写入时,由于没有对该页面写入的权限,会使CPU向操作系统抛出一个缺页错误。抛出错误时,系统处于用户态,因此,我们需要在 usertrap() 中对相应的错误进行识别,最后进行处理。

RISC-V手册我们已经知道CPU在尝试写入时遇到错误的话,我们调用 r_scause() 将得到 15 的错误码。因此,我们在 usertrap() 中捕捉到错误码为 15 的异常后便直接调用错误处理函数并根据函数的返回值,决定进程是否遇到了合法的缺页异常,如果不是,则将进程杀死。对应的代码片段如下:

// kernel/user.trap.c
void
usertrap(void)
{
  // ...
  if(r_scause() == 8){
    // ...
  } else if((which_dev = devintr()) != 0){
    // ok
  } else if(r_scause() == 15) {
    uint64 va = r_stval();
    if (va >= p->sz)
      p->killed = 1;
    if (copy_on_write_page_fault_handler(p->pagetable, va) < 0) {
      p->killed = 1;
    }
  } else {
    // ...
  }
	// ...
}

在这里,我们提前加上了对进程访问的地址是否超出进程已分配的内存的判断,usertests中会针对这一项进行测试。