MIT-6.S081 | Lab-cow(2021)

104 阅读2分钟

Lab COW

大部分思路在hint和plan。

先添加引用计数

struct {
  struct spinlock lock;
  struct run *freelist;
   //plane3 物理内存也就是KERBASE ~ PHYSTOP 除以4096也就是PGSIZE
  uint8 ref_count[(PHYSTOP - KERNBASE) / PGSIZE];
} kmem;

增加1,要添加到声明到defs.h

 /* kernel/kalloc.c */
 void increment_refcount(uint64 pa){
   acquire(&kmem.lock);
   kmem.ref_count[(pa - KERNBASE) / PGSIZE]++;
   release(&kmem.lock);
 }

减少1,每次取消对某个物理内存页的引用,最终都会调用到kfree(),因此ref_count减少1kfree()实现。同时,当ref_count减少到0时,即可释放这一物理页。

/* kernel/kalloc.c */
void
freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE){
    acquire(&kmem.lock);
    //目的是将ref_count设置为1,因为调用kfree会-1,要抵消这个-1
    kmem.ref_count[((uint64)p - KERNBASE) / PGSIZE] = 1; 
    release(&kmem.lock);
    kfree(p);    
  }
}

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

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

  acquire(&kmem.lock);
  if (--kmem.ref_count[((uint64)pa - KERNBASE) / PGSIZE] == 0)
  {
    release(&kmem.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);
  }
  else 
    release(&kmem.lock);
}

分配kalloc一次物理页,就将这个物理页ref_count设置为1

/* kernel/kalloc.c */
void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
  {
    kmem.ref_count[((uint64)r - KERNBASE) / PGSIZE] = 1;
    kmem.freelist = r->next;
  }
  release(&kmem.lock);

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

添加PTE_COW,标识这个PTE是copy on write(写时复制)的物理页

#define PTE_COW (1L << 8) // 1 -> COW page

修改uvmcopy

  • 调用uvmcopy时,如果当前页面可以写,那就将其置为不可写,同时将其标识为cowpage
  • 最终我们不再分配一个新的物理页,而是直接映射到旧的物理页
  • 当要写这个不可写但为cowpage的页面时,启动中断page fault ,此时我们才分配新的物理页
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);

    increment_refcount(PGROUNDDOWN(pa));  //引用计数
    if(*pte & PTE_W)
    {
      *pte &= ~(PTE_W); // 清除PTE_W
      *pte |= PTE_COW;  //设置PTE_COW
    }
    
    flags = PTE_FLAGS(*pte);
    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      goto err;
    }
  }
  return 0;

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

修改usertrap,r_scause() == 15的是我们要处理的store page fault,stval()的值此时是发生错误的虚拟地址>p->sz说明地址错误了,无法处理

  • is_cowpage用来判断该页面是不是cowpage
  • cow_page,为这个cowpage分配新的物理页
//trap.c
void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();
  
  // save user program counter.
  p->trapframe->epc = r_sepc();
  
  if(r_scause() == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } 
  else if(r_scause() == 15){
    uint64 fault_va = r_stval();
    if (fault_va > p->sz || // panic: init exiting
        is_cowpage(p->pagetable, fault_va) < 0 ||
        cow_alloc(p->pagetable, PGROUNDDOWN(fault_va)) == 0)
    {
      p->killed = 1;
    }
  } else if((which_dev = devintr()) != 0){
    // ok
  } else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
    p->killed = 1;
  }

  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

  usertrapret();
}
//是cowpage 0, else -1
int is_cowpage(pagetable_t pagetable, uint64 va)
{
  pte_t *pte = walk(pagetable, va, 0);
  return (*pte & PTE_COW ? 0:-1);
}
//分配物理地址为cow page
//成果返回mem pointer of void*, else 0
void *cow_alloc(pagetable_t pagetable, uint64 va)
{
  pte_t *pte = walk(pagetable, va, 0);
  uint64 pa = PTE2PA(*pte);
  //引用数为1
  if(get_refcount(pa) == 1)
  {
    *pte |= PTE_W;     //设置PTE_W
    *pte &= ~PTE_COW;  //清除PTE_COW
    return (void*) pa;
  }
  //引用数>1
  uint flags;
  char* new_mem;
  *pte |= PTE_W;
  flags = PTE_FLAGS(*pte);

  //pa = PTE2PA(*pte);
  new_mem = kalloc();

  // hint5:If a COW page fault occurs and there's no free memory, the process should be killed.
  if(new_mem==0) return 0;
  memmove(new_mem, (char*)pa, PGSIZE);
  *pte &= ~PTE_V; // 防止panic: remap
  if (mappages(pagetable, va, PGSIZE, (uint64)new_mem, flags) != 0)
  {
    *pte |= PTE_V;
    kfree(new_mem);
    return 0;
  }
  kfree((char *)PGROUNDDOWN(pa));
  return new_mem;
}

要在def.h中声明,以及walk函数