程序笔记   发布时间:2022-07-16  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了6.s081 Lab6 cow大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

Lab: Copy-on-Write Fork for xv6

当sHell处理指令时, 会先通过fork创建一个子进程, 子进程的第一件事就是调用exec来运行一些其他程序, exec会将之前复制的地址空间丢弃这会造成资源的浪费. 所以创建子进程时, 与其创建, 分配并复制内容到新的物理内存, 不如直接和父进程共享物理内存(通过设置子进程的pte只想父进程对应的物理内存pagE). 但由于子进程和父进程是独立的, 所以当修改子进程内存时, 不能影响父进程. 所以将pte设置为只读的.

6.s081 Lab6 cow

当父进程或子进程试图修改内存时, 会page fault(因为向一个只读pte里写数据). page fault后, handler需要复制相应的物理page到新分配的物理内存page中, 该page只对子进程地址空间可见, 所以可以将pte设为读写, 然后重新执行write指令. 由于该物理内存此时只对父进程可见, 所以父进程相应的pte为读写.

6.s081 Lab6 cow

当发生page fault时, 内核需要能够分辨是一个copy-on-write fork的场景. 所以在cow时, 要在pte标志位添加一个PTE_COW的标志来表明发生page fault时, 是因为cow.

释放相应的物理page也要小心, 因为要判断是否能够立即释放. 如果有其他进程正在使用物理page, 而释放了该page就会出现问题. 所以需要对每一个物理page的引用计数.

Implement copy-on write (hard)

在xv6内核实现cow fork, 通过cowtest和usertests.

  1. 为每个物理页维护一个reference count, 指明有多少pte指向该页.

    struct {
      struct spinlock lock;
    	int ref_count[PHYSTOP/PGSIZE];
    } count;
    
  2. ref_count初始化, 使其每个元素为0. 初始化函数kinit会调用freerange, freerange会调用kfree, 但kfree之后会被修改, 所以重新写一个用于初始化的kfree_init函数, 用于初始化ref_count.

    void
    freerange(void *pa_start, void *pa_end)
    {
      char *pa;
      p = (char*)PGROUNDUP((uint64)pa_start);
      for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
        kfree_init(p);
    }
    
    void
    kfree_init(void *pa)
    {
      struct run *r;
    
      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;
    	count.ref_count[(uint64)pa / PGSIZE] = 0;
      release(&kmem.lock);
    }
    
  3. 每次调用kalloc, 说明该物理页第一次被使用, 所以将相应物理页的ref_count设置为1.

    void *
    kalloc(void)
    {
      struct run *r;
    
      acquire(&kmem.lock);
      r = kmem.FREELIst;
      if(r) {
        kmem.FREELIst = r->next;
    		count.ref_count[(uint64)r/PGSIZE] = 1;
    	}
      release(&kmem.lock);
    
      if(r)
        memset((char*)r, 5, PGSIZE); // fill with junk
      return (void*)r;
    }
    
  4. 由于其他函数会调用kfree, 由于cow的存在, 一个物理页可能有多个虚拟页映射着, 当调用kfree时, 需要判定, 当该物理页的ref_count为1时, 才会清除该物理页. 每次调用kfree都会对该物理页的ref_count减1.

    void
    kfree(void *pa)
    {
      struct run *r;
    
      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;
    */
    	if ((--count.ref_count[(uint64)pa / PGSIZE]) == 0) {
    	  memset(pa, 1, PGSIZE);
    		r->next = kmem.FREELIst;
    		kmem.FREELIst = r;
    	}
      release(&kmem.lock);
    }
    
  5. 最后再在kalloc.c中添加一个inc函数, 每次将虚拟页映射到物理页时都需要将该物理页的ref_count加1.

    void
    inc(uint64 pa)
    {
      acquire(&count.lock);
    	count.ref_count[pa/PGSIZE]++;
    	release(&count.lock);
    }
    
  6. 由于fork调用的是uvmcopy函数来创建子进程, 所以需要修改uvmcopy函数, 使其不实际分配物理内存, 而是将子进程的虚拟页映射到父进程的物理页上, 并将该物理页的ref_count加1. 当父进程的pte是可写的时候, 需要清除父子pte的PTE_W, 并给父子pte设置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);
    		flags = PTE_FLAGS(*ptE);
    		if (flags & PTE_W) {
    		  *pte = (*pte & ~PTE_W) | PTE_COW;
    		  flags = PTE_FLAGS(*ptE);
    		}
    		inc(pa);
    		if (mappages(new, i, PGSIZE, (uint64)pa, flags) != 0) {
    		  goto err;
    		}
    /*
        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;
    }
    
  7. 写一个用于处理cow页未分配物理内存的函数cowalloc, 该函数通过页表和虚拟地址va来获取相应的ptepa, 并将pa复制到新分配的物理页@H_490_3@mem上, 此时pte是可写的, 并且不是一个cow页.

    int
    cowalloc(pagetable_t pagetable, uint64 va)
    {
      pte_t *pte;
    	uint64 pa;
      struct proc *p = myproc();
    
      if (va >= MAXVA) {
    	  return -1;
    	}
    	if (va <= PGROUNDDOWN(p->trapframe->sp) && va >= PGROUNDDOWN(p->trapframe->sp) - PGSIZE) {
    	  return -1;
    	}
    
    	va = PGROUNDDOWN(va);
    
    //	if (va > p->sz || va < PGROUNDDOWN(p->trapframe->sp)) {
    //	  return -1;
    //	}
    	if ((pte = walk(pagetable, va, 0)) == 0) {
    	  return -1;
    	}
    	if ((*pte & PTE_V) == 0) {
    	  return -1;
    	}
    	if ((*pte & PTE_COW) == 0) {
    	  return -1;
    	}
    	if ((pa = PTE2PA(*ptE)) == 0) {
    		return -1;
    	}
      char *mem = kalloc();
    	if (mem == 0)
    		return -1;
    	memmove(mem, (char *) pa, PGSIZE);
    	kfree((void *) pa);
    	uint flags = (PTE_FLAGS(*ptE) | PTE_W) & (~PTE_COW);
    	*pte = PA2PTE((uint64) mem) | flags;
    	return 0;
    }
    
  8. 最后在copyoutusertrap中调用该函数来处理cow页.

    int
    copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
    {
      uint64 n, va0, pa0;
    
      while(len > 0){
        va0 = PGROUNDDOWN(dstva);
    
    		cowalloc(pagetable, va0);
    
        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;
    }
    
    void 
    usertrap(void)
    {
    ...
      else if (r_scause() == 13 || r_scause() == 15) {
        uint64 va = r_stval();
        if (cowalloc(p->pagetable, va) < 0)
          p->killed = 1;
      }
    }
    

大佬总结

以上是大佬教程为你收集整理的6.s081 Lab6 cow全部内容,希望文章能够帮你解决6.s081 Lab6 cow所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。