MIT 6.1810: Copy-on-Write Fork for xv6

警告
本文最后更新于 2023-04-15,文中内容可能已过时。

MIT 6.1810 中第五个 lab 的 solution

Copy-on-Write Fork for xv6

Your task is to implement copy-on-write fork in the xv6 kernel. You are done if your modified kernel executes both the cowtest and ‘usertests -q’ programs successfully.

kernel/vm.cuvmcopy()修改成下边这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
extern void refinc(void *pa);

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint 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);
    *pte &= ~PTE_W;
    *pte |= PTE_RSW;
    flags = PTE_FLAGS(*pte);
    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      goto err;
    }
    refinc((void *)pa);
  }
  return 0;

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

这里的refinc是之后加在别的源文件里的函数

把同文件的copyout()修改成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0, flags;
  pte_t *pte;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    if(va0 >= MAXVA)
      return -1;

    pte = walk(pagetable, va0, 0);
    if(pte == 0 || ((*pte) & PTE_V) == 0 || ((*pte) & PTE_U) == 0) {
      return -1;
    }
    flags = PTE_FLAGS(*pte);
    pa0 = PTE2PA(*pte);
    if ((flags & PTE_RSW) == PTE_RSW){
      char* mem = kalloc();
      memmove(mem, (char*)pa0, PGSIZE);
      flags |= PTE_W;
      flags &= ~PTE_RSW;
      *pte &= ~PTE_V; // avoid remap
      if (mappages(pagetable, va0, PGSIZE, (uint64)mem, flags) != 0) {
        kfree((void *)mem);
        return -1;
      }
      kfree((void*) pa0);
      
    }
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    n = PGSIZE - (dstva - va0);
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);

    len -= n;
    src += n;
    dstva = va0 + PGSIZE;
  }
  return 0;
}

kernel/riscv.h中对PTE权限设置那里加一个对RSW的定义

1
#define PTE_RSW (1L << 8)

kernel/trap.c添加一个page fault的识别逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  } else if(r_scause() == 15 || r_scause() == 13){
    // store page fault or load page fault
    pte_t *pte;
    uint64 pa;
    uint flags;
    char *mem;
    uint64 va = PGROUNDDOWN(r_stval());
    if(va >= MAXVA || va < PGSIZE){
      setkilled(p);
      goto kill;
    }
    pte = walk(p->pagetable, va, 0);
    if (pte == 0 || ((*pte) & PTE_V) == 0 || ((*pte) & PTE_U) == 0) {
      setkilled(p);
      goto kill;
    }
    pa = PTE2PA(*pte);
    if(!(*pte & PTE_RSW))
      goto unknown_trap;
    *pte |= PTE_W;
    *pte &= ~PTE_RSW;
    flags = PTE_FLAGS(*pte);
    if((mem = kalloc()) == 0){
      setkilled(p);
      goto kill;
    }
    memmove(mem, (char*)pa, PGSIZE);
    uvmunmap(p->pagetable, va, 1, 0);
    if(mappages(p->pagetable, va, PGSIZE, (uint64)mem, flags) != 0){
      kfree(mem);
      setkilled(p);
      goto kill;
    }
    kfree((void*)pa);
  }else {
    unknown_trap:
      printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
      printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
      setkilled(p);
  }

  kill:
  if(killed(p))
    exit(-1);

修改kernel/kalloc.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
struct {
  struct spinlock lock;
  int refcount[(PHYSTOP - KERNBASE) / PGSIZE];
} pginfo;


void
refinc(void *pa) {
  int index = ((uint64)pa - KERNBASE) / PGSIZE;
  acquire(&pginfo.lock);
  pginfo.refcount[index]++;
  release(&pginfo.lock);
}

void
kfree(void *pa)
{
  struct run *r;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  int index = ((uint64)pa - KERNBASE) / PGSIZE;
  acquire(&pginfo.lock);
  if (pginfo.refcount[index] > 1) {
    pginfo.refcount[index]--;
    release(&pginfo.lock);
  }
  else {
    pginfo.refcount[index] = 0;
    release(&pginfo.lock);

    // Fill with junk to catch dangling refs.
    memset(pa, 1, PGSIZE);

    r = (struct run*)pa;

    acquire(&kmem.lock);
    r->next = kmem.freelist;
    kmem.freelist = r;
    release(&kmem.lock);
  }
}

void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);

  if (r) {
    int index = ((uint64)r - KERNBASE) / PGSIZE;
    acquire(&pginfo.lock);
    pginfo.refcount[index] = 1;
    release(&pginfo.lock);
  }

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}