Loading

6.S081 lab6 cow

#Copy-On-Write

  • 当调用fork创建子进程时,会调用uvmcopy根据父进程的页表把父进程的内存空间拷贝至子进程。修改uvmcopy使此时父子进程共用物理页,并且关闭物理页的PTE_W标志,开启PTE_COW标志便于触发页面错误。代码如下:

    int
    uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
    {
      pte_t *pte;
      uint64 pa, i;
      /* uint flags; */
      /* char *mem; */
      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_COW;
        if (mappages(new, i, PGSIZE, pa, PTE_FLAGS(*pte)) != 0){
            goto err;
        }
        int idx = pa / PGSIZE;
        cnt[idx]++;
        /* if((mem = kalloc()) == 0) */
        /*   goto err; */
        /* memmove(mem, (char*)pa, PGSIZE); */
        /* if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ */
        /*   kfree(mem); */
        /*   goto err; */
        /* } */
      }
      return 0;
    
     err:
      uvmunmap(new, 0, i / PGSIZE, 1);
      return -1;
    }
    

    上述代码的19~20行操作了全局cnt数组,定义如下:

    image.png

    该数组为所有物理页的引用计数,当某个物理页被使用时,对应的引用计数值+1,用物理地址/PGSIZE得到索引值。

  • usertrap中处理页面错误。调用copy_on_write函数,该函数为进程分配一个新物理页,并且将之前共享的物理页内容拷贝到新物理页中。若分配失败返回-1

    void
    usertrap(void)
    {
      ...
      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() == 13 || r_scause() == 15){
             uint64 va = r_stval();
             if (copy_on_write(p, va) != 0){
                 p->killed = 1;
             }
      }
      ...
    }
    
    //cow函数,成功分配物理内存则返回0,否则返回-1
    int copy_on_write(struct proc *p, uint64 va){
        if (va >= MAXVA){
            return -1;
        }
        pte_t *pte = walk(p->pagetable, va, 0);
        if (pte == 0){
            return -1;
        }
        if ((*pte & PTE_V) == 0 || (*pte & PTE_U) == 0){
            return -1;
        }
        if ((*pte & PTE_COW) == 0){
            return -1;
        }
    
        char *mem = (char*)kalloc();
        if (mem == 0){
            printf("cow: kalloc failed\n");
            return -1;
        }
    
        memset(mem, 0, PGSIZE);
        *pte |= PTE_W;
        uint64 pa = PTE2PA(*pte);
        memmove(mem, (char*)pa, PGSIZE);
        // 重新添加映射关系
        *pte = PA2PTE((uint64)mem) | PTE_FLAGS(*pte);
        // 由于已经分配了一个新物理页,应该使旧物理页的引用计数减1
        kfree((void*)pa);
    
        return 0;
    }
    

    函数的3~15行检查了一些非法情况,注意13行处检查了物理页的PTE_COW标志,假如未开启该标志,说明这不是一个COW物理页,va是一个非法虚拟地址。为进程分配新的物理页后,需要将旧的物理页拷贝至新的物理页,并且重新添加映射关系。30行调用了kfree函数,但此处并不是真正释放物理页。

  • copyout作用是把内核的数据复制到用户进程中,由于该函数是在内核中被调用的,并不会触发usertrap 。修改copyout,使其能够处理COW物理页。

    int
    copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
    {
      uint64 n, va0, pa0;
    
      while(len > 0){
        va0 = PGROUNDDOWN(dstva);
        if (va0 >= MAXVA){
            return -1;
        }
        pte_t *pte = walk(pagetable, va0, 0);
        if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0){
            return -1;
        }
        if ((*pte & PTE_COW) != 0){
            if (copy_on_write(myproc(), va0) != 0){
                return -1;
            }
        }
    
        pa0 = PTE2PA(*pte);
        /* 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;
    }
    
  • 更改kfree函数。由于加入了COW机制,使得某些物理页被共享了,所以释放物理页的时机尤为重要。当对某个物理页调用kfree时,说明该物理页不会再被用到,应该对其的引用计数-1。如果引用计数值为0,则没有任何进程使用该物理页,此时应该回收。

    kfree(void *pa)
    {
      ...
      acquire(&kmem.lock);
      int idx = (uint64)pa / PGSIZE;
      if (--cnt[idx] == 0){
          /* printf("%p\n", (uint64)pa); */
          // Fill with junk to catch dangling refs.
          memset(pa, 1, PGSIZE);
          r->next = kmem.freelist;
          kmem.freelist = r;
      }
      release(&kmem.lock);
    }
    
posted @ 2022-01-18 11:05  Kyoz1  阅读(80)  评论(0)    收藏  举报
7