MIT 6.S081 Lab6 CopyOnWrite

前面的5个Lab,边抄边过的,虽然比较顺利,很快,觉得没啥意思,Lab6 打算自己安心做一做,不图结果和快了

问题描述:

fork()系统调用会把父进程的用户空间全拷贝到子进程,这种拷贝在父进程用户空间很大的时候,很耗费资源,而且还经常被浪费,比如fork后面紧跟exec,那么子进程会直接启用掉fork来的用户空间, 但是另一方面来讲,如果父 子进程都要写页面,那么复制是需要的

copy on write的目标,就是延迟分配,只在真的需要分配时,才放页表

COW 首先为子进程分配一个页表,但是页表里面的页表项(PTEs)是指向父进程的内存空间的,并且cow把该内存空间标为只读,当父子进程尝试写的时候,触发页错误,内核会处理这个异常,为写进程分配一个新页面,并把父进程的内容拷贝到该新页面,然后更新页表PTE,映射到新的内存,并把该页表项设置为可写,页错误hanlder返回以后,用户进程便能够写页表

cow的fork,会有一个比较麻烦的点,就是多个虚拟空间,会指向同一个物理空间,这样释放的时候,我们就得确认,最后一个指向物理空间的虚拟地址被释放以后,才能真正释放该物理空间

你必须通过usertests和cowtests

第一个测试就会失败,因为fork父进程先申请空闲内存的一半,子进程会再申请一半,内存不足,导致错误

hints

2. 修改usertrap来识别页错误,当出现cow页错误时,用kalloc分配内存,将老的页复制到新的页,并把新的页置为PTE_W

3. 当最后一个对物理空间的指针drop以后,释放该物理空间,要生成一个引用计数, 在kalloc时将该引用计数设置为1,每次释放虚拟页表时,将该引用减一,kfree只有在引用计数为0的时候,才真正把该页放入空闲页表中

4. 你可以把这些引用计数,放入一个数组中,但是你需要想办法解决如何在数组中查找,以及选择数组的大小 For example, 你可以用 物理地址/4096来作为数组的索引,并且给这个数组一个free list里面最高的物理页空间

5. 修改copyout,当其遇见一个COW页时

Lab 5的lazy allocation和COW看上去有点相似,但是你不该在lazy allocation的基础上改写,而应该重新写代码

对于每个PTE,都应该描述它是否是一个COW映射,你可以用PTE的RSW保留位来标注他

同时测试usertest和cowtest

riscv.h包含一些有用的宏和定义

如果COW也错误出现,而此时没有空闲内存,那么这个进程就该被kill

先把问题描述清楚

1.修改usertrap

对老的页面写失效,通过kalloc分配新的页面,如果无法分配则杀死进程,并通过walkaddr来找到原来的老物理页面地址,复制给新页面

 } else if(r_scause() == 15) {
        char* mem;
        mem = kalloc();
        if(mem == 0) {
          p->killed = 1;
          return 0;
        }
        uint64 va = PGROUNDDOWN(r_stval());
        uint64 pa = walkaddr(p->pagetable, va);
        pte_t* pte = walk(p->pagetable, va, 0);
        if(pte & PTE_COW) {
        memmove(mem, (char*)pa, PGSIZE);
        //kfree((char*)pa);     
        uvmunmap(p->pagetable, va, 1, 1);
        if(mappages(p->pagetable, va, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){

          kfree(mem);
          p->killded = 1;
          return 0;
        }
       } else {

          kfree(mem);
          p->killded = 1;
          return 0;
       }
   
  }

2. 修改uvmcopy成只读~PTE_W,并且让子进程的虚拟地址映射也指向父进程的物理地址

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);
    flags = (PTE_FLAGS(*pte) & ~PTE_W) | PTE_COW ;
    //if((mem = kalloc()) == 0)
    //  goto err;
    //memmove(mem, (char*)pa, PGSIZE);
    *pte = (*pte & ~PTE_W) | PTE_COW;
    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
      //kfree(mem);
      goto err;
    }
  }
  return 0;

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

3.在kalloc.c里面添加数组,用来记录指向某物理页的指针总数


extern int[] refNum = new int[PHYSTOP/PGSIZE];

4. 修改kalloc和kfree代码

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

  if(--refNum[(uint64)pa/PGSIZE] > 0) {
    return;
  }
  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  // 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);
}

// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
  struct run *r;

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

  if(r) {
    refNum[(uint64)r/PGSIZE] += 1;
    memset((char*)r, 5, PGSIZE); // fill with junk
  }
  return (void*)r;
}

5. riscv.h添加PTE_COW置位

#define PTE_U (1L << 4) // 1 -> user can access
#define PTE_COW (1L << 8)

6. 修改vm.c的copyout

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(pa0 == 0) {
        return -1;
    }
    pte_t *pte = walk(pagetable, va0, 0);
    if (*pte & PTE_COW) {
        char* mem;
        mem = kalloc();
        if(mem == 0) {
          return -1;
        }
        //pa0 = walk(pagetable, va0, 1);
        memmove(mem, (char*)pa0, PGSIZE);

        if(mappages(pagetable, va0, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){

          kfree(mem);
          return -1;
        }

7. defs.h添加函数声明

pte_t*          walk(pagetable_t, uint64, int);

总结

思考程序流程的时候,可以拟人化,这样你自己更容易接受

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值