xv6-lab6-cow

Lab: Copy-on-Write Fork for xv6

实验目标

本实验你的目标是实现xv6上的 copy-on-write

实验实现

copy-on-write介绍

copy-on-write 是指当你创建子进程时,并不实际复制父进程的空间地址的内容到新的物理内存,而是将页表项改为只读,并在第一次父/子进程要改变页面内容时才进行复制,并将原来的页面解锁为可读写。这种做法节省了大量的物理内存,尤其是现在大部分程序在 fork() 和通常立马会接上 exce() 载入新的程序 。总体来说本实验的思路跟lazy alloc是比较类似的

hints

  • 修改 uvmcopy() ,使子进程的页表映射到父进程的物理内存,并清除写权限
  • 修改usertrap() , 识别cow 的页面错误, 为引起cow 页面错误的进程分配复制页表以及物理内存,将原页面写权限解锁
  • 确保物理内存在没有引用的情况下才被释放。可以用一个数组保存引用计数,用 物理地址/PGSIZTE 来区分不同的物理页
  • 修改 copyout() 来处里再内核中遇到 cow 页面错误的情况
  • 使用标志位来记录PTE 是否为cow 页面,可使用RSW区域

请添加图片描述

  • risc.h 底部有一些关于页表标识的宏定义

实现思路

  • 新增数据结构/标志位保存必要信息

    • 使用第pte第8位标记 cow 页面
    /*	riscv.h	*/
    #define PTE_V (1L << 0) // valid
    #define PTE_R (1L << 1)
    #define PTE_W (1L << 2)
    #define PTE_X (1L << 3)
    #define PTE_U (1L << 4) // 1 -> user can access
    
    #define PTE_COW (1L << 8)
    
    • 新增 mem_ref 记录某块物理内存引用计数,以及引用相关操作
    /*	kalloc.c	*/
    
    struct mem_ref
    {
      struct spinlock lock;
      int cnt;
    };
    
    struct mem_ref mem_ref[PHYSTOP/PGSIZE];
    
    //初始化引用计数的锁
    void
    kinit()
    {
    
      for(int i = 0; i < PHYSTOP/PGSIZE; ++i)
        initlock(&(mem_ref[i].lock), "kmem_ref");
      
      initlock(&kmem.lock, "kmem");
      freerange(end, (void*)PHYSTOP);
    }
    
    //初始化引用计数
    void
    freerange(void *pa_start, void *pa_end)
    {
      char *p;
      p = (char*)PGROUNDUP((uint64)pa_start);
      for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
      {
        mem_ref[(uint64)p/PGSIZE].cnt = 1;
        kfree(p);
      }
    }
    
    //引用计数为0时才释放物理内存
    void
    kfree(void *pa)
    {
      struct run *r;
    
      if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
        panic("kfree");
      
      uint64 pi = (uint64)pa/PGSIZE;
      acquire(&(mem_ref[pi].lock));
        
      mem_ref[pi].cnt = mem_ref[pi].cnt - 1;
      if(mem_ref[pi].cnt > 0)
      {
        release(&(mem_ref[pi].lock));
        return;
      }
    ......
    }
    
    //分配物理内存时初始化引用计数为1
    void *
    kalloc(void)
    {
    ......
      if(r)
      {
    
        uint64 pi = (uint64)r/PGSIZE;
        acquire(&(mem_ref[pi].lock));
        mem_ref[pi].cnt = 1;
        release(&(mem_ref[pi].lock));
        
        kmem.freelist = r->next;
      }
    ......
    }
    
    
    
    /*
    *	用于操作引用计数的辅助函数
    */
    
    //not thread safe 
    int
    get_mem_ref(uint64 pa)
    {
      return mem_ref[(uint64)pa/PGSIZE].cnt;
    }
    
    
    int
    add_ref(uint64 pa)
    {
      if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
        return -1;
      acquire(&(mem_ref[pa/PGSIZE].lock));
      mem_ref[(uint64)pa/PGSIZE].cnt = mem_ref[(uint64)pa/PGSIZE].cnt + 1;
      release(&(mem_ref[pa/PGSIZE].lock));
      return 1;
    }
    
  • 修改父子进程间页表复制,将父子页表都标志为 cow 页、撤销写权限,并映射到同一块物理内存,增加那块物理内存的引用。

/* vm.c	*/

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) & (~PTE_W); // set the write flag fault
    *pte = (*pte) | (PTE_COW);// set the cow flag true
    flags = PTE_FLAGS(*pte);
    //map the child pte to it's parent's phyical address
    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
      goto err;
    }
    add_ref(pa);

    
  }
  return 0;

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

  • 做了cow 页面修改后,在用户态访问cow 页面会发生缺页中断,内核态则在copy_out中会写入cow 页面引起冲突。接下来就处理这两种情况。
/*	vm.c	*/
/*
	操作cow页面的辅助函数
*/

//检查页表项是否有效以及是否cow页面
int
cow_check(pagetable_t pagetable, uint64 va)
{
  if(va > MAXVA)
    return 0;

  pte_t *pte = walk(pagetable, va, 0);

  if(pte == 0)
    return 0;
  // printf("judge here 0\n");
  if(((*pte) & (PTE_V)) == 0)
    return 0;
  // printf("judge here 1\n");
  int ans = (*pte) & (PTE_COW);

  return ans;
  
}

/*
	为引发缺页中断的页表项分配物理内存以及映射
	记得先将页表项的PTE_V标志位置0,否则会引发remap
	旧页表项的标志位我们不和新页表项一起处理,当访问旧页表项时再回到这个函数统一处理,减少情况判断
*/
uint64 
cow_copy(pagetable_t pagetable, uint64 va)
{
  if(cow_check(pagetable, va) == 0)
    return 0;

  va = PGROUNDDOWN(va);
  pte_t *pte = walk(pagetable, va, 0);
  uint64 pa = PTE2PA(*pte);


  if(get_mem_ref(pa) == 1)
  {
    *pte = (*pte) & (~PTE_COW);
    *pte = (*pte) | (PTE_W);
    return pa;
  }
  else
  {
    char *mem = kalloc();
    if(mem == 0){
      return 0;
    }

    memmove(mem, (char *)pa, PGSIZE);
    *pte = (*pte) & (~PTE_V);
    uint64 flag = PTE_FLAGS(*pte);
    flag = flag | PTE_W;
    flag = flag & (~PTE_COW);


    if(mappages(pagetable, va, PGSIZE, (uint64)mem, flag) != 0)
    {
      kfree(mem);
      return 0;
    }

    kfree((char*)PGROUNDDOWN(pa));

    return (uint64)mem;
    
  }
}


/*	trap.c	*/
void
usertrap(void)
{
......  
  if(r_scause() == 8){

  } else if(r_scause() == 13 || r_scause() == 15){

    uint64 va = r_stval();
    if(cow_copy(p->pagetable, va) == 0)
    {
      // panic("be kill\n");
      p->killed = 1;
    }
  } else if((which_dev = devintr()) != 0){
  }
......
  usertrapret();
}


/*	vm.c	*/
//若为cow页面则执行cow_copy操作
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;
  
  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pa0 = walkaddr(pagetable, va0);
    
    if(cow_check(pagetable, va0) != 0)
    {
      pa0 = cow_copy(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;
}

实验结果

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值