zoukankan      html  css  js  c++  java
  • MIT-6.S081-2020实验(xv6-riscv64)六:cow

    实验文档

    概述

    这次实验实现copy on write功能,和上次实验一样也是缺页中断的应用,但不同的是,这次实验涉及的物理内存和虚拟地址的操作要比上个实验多不少,因此难度也更大一些。

    内容

    首先是uvmcopy的部分,原来的操作是从老页表中获得虚拟地址对应的物理地址,创建一个新物理页,然后将老物理地址的内容复制到新物理页,再把新物理页通过新页表映射到虚拟地址,现在就要改成直接将老物理地址通过新页表映射到虚拟地址,同时需要将老页表和新页表对应底层pte抹去PTE_W为并添加PTE_C位,这里的PTE_C是我自己定义的一个标志位。根据Riscv的标准,pte的低10位作为标志位,其中的0-7位是包括PTE_V、PTE_W之类已经被用掉的标志位,8-9位是可供用户自定义使用的标志位,这里我选取第8位,即PTE_C = 1L << 8。0表示该pte没有用在copy on write中,1表示有,这样在处理缺页中断的时候就比较方便了,只要该pte的PTE_C位为0,说明这次缺页中断的原因不是copy on write,而是真的缺页,就可以直接返回错误:

      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_C;
        flags = PTE_FLAGS(*pte);
        // if((mem = kalloc()) == 0)
        //   goto err;
        // memmove(mem, (char*)pa, PGSIZE);
        if(mappages(new, i, PGSIZE, pa, flags) != 0) goto err;
        add_count(pa);
      }
      return 0;
    
     err:
      uvmunmap(new, 0, i / PGSIZE, 1);
      panic("uvmcopy: map page failed");
      return -1;
    

    这里我映射失败就直接让程序panic了,因为如果真的要处理的话还得把之前所有新老页表里的底层pte的标志位改回去,事实上我也想不出mappages失败且内部没有panic的情况。

    然后是缺页中断处理,和上次实验一样,单独抽象成一个函数,主要过程就是获取缺页的物理地址,如果物理地址不存在或者PTE_C不为1就返回错误,然后创建新页,把老页的内容复制过来,并修改新页对应pte的标志位,注意下一步需要尝试释放老页,防止内存泄漏。这里的“尝试释放”指的是让老页的引用计数-1,如果引用计数为0了就真的释放。“尝试释放”的过程直接就写在kfree函数里面,因为加入copy on write机制后,所有对物理内存的操作都需要受引用计数的制约:

    int handle_page(uint64 va, pagetable_t pgtbl) {
        pte_t *pte; char *mem; uint flags;
        if ((pte = walk(pgtbl, va, 0)) == 0) return -1;
        if ((*pte & PTE_C) == 0) return -1;
        if ((mem = kalloc()) == 0) return -1;
        flags = PTE_FLAGS((*pte & (~PTE_C)) | PTE_W);
        uint64 pa = PTE2PA(*pte);
        memmove(mem, (char*)pa, PGSIZE);
        *pte = PA2PTE((uint64)mem) | flags;
        kfree((void *)pa); return 0;
    }
    

    然后就是copyout函数的修改,为什么不需要修改copyin和copyinstr函数呢,因为fork涉及的都是用户区的内存,所以缺页也只会在写用户内存的情况下发生,copyout是内核内存写到用户内存,所以需要处理,另外两个函数是用户内存写到内核内存,是读用户内存,所以不需要处理。另一个和上次实验不同的地方是,上次实验之所以copyout函数需要修改,是因为在对虚拟地址调用walkaddr函数的时候,因为实际的物理地址不存在,所以返回错误,因此只要在walkaddr返回不存在的物理地址时进行缺页处理即可;而这次实验walkaddr是可以得到合法的物理地址的,只是这个物理地址不能被写,所以错误会在memmove到这个物理地址的时候才发生,而且这个缺页中断是在内核态发生的,走的也是kerneltrap函数,因此我们定义在usertrap函数里的处理代码捕获不到它。因此我们要做的,就是在调用walkaddr函数后对pte进行检查,如果PTE_C位为1,则进行缺页处理。

      while(len > 0){
        va0 = PGROUNDDOWN(dstva);
        pa0 = walkaddr(pagetable, va0);
        if(pa0 == 0)
          return -1;
        if (*(walk(pagetable, va0, 0)) & PTE_C) handle_page(va0, pagetable);
        pa0 = walkaddr(pagetable, va0);
        ......
    

    这里我的代码写的比较矬,为了检查标志位还重新walk一遍,最后要获得新物理地址又walkaddr一遍,实际上可以定义另一个版本的walkaddr直接返回pte,handle_page也可以改写让其返回新物理地址,后面有时间再改。trap.c的代码和上次实验几乎一样,就不贴了。

    然后是物理内存的处理,为了节省空间,我没有直接用物理地址模4096,而是先将物理地址减掉内核的地址空间,再模4096,因为fork不涉及物理内存,即数组索引为(pa - KERNBASE) >> PGSHIFT,当然代价就是每次进行处理引用计数的时候需要先判断pa必须大于等于KERNBASE,不然内核申请或释放物理内存的时候一减变成负数,就访问非法内存了。数组大小就可以根据memlayout.h里的值进行计算,发现物理内存的最大值PHYSTOP减KERNBASE等于128*1024*1024`,因此总页数为128*1024/4=32768,这就是数组的大小。另外很重要的一点是引用数组的声明:

    struct {
        struct spinlock lock;
        uint a[32768];
    } count;
    

    需要用到锁,这个实验文档没讲,略坑,我也是看了别人的代码才知道,不用锁的话会内存泄漏,应该是多进程竞争扰乱了引用计数的加减,目前还没看到xv6文档里关于锁的部分,所以也不知道哪些地方可能产生资源竞争。了解这一点后面就很容易了,kfree函数:

      if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
        panic("kfree");
    
      if ((uint64)pa >= KERNBASE) {
          acquire(&count.lock);
          if (count.a[((uint64)pa - KERNBASE) >> PGSHIFT] > 1) {
              count.a[((uint64)pa - KERNBASE) >> PGSHIFT]--;
              release(&count.lock); return;
          } else {
              count.a[((uint64)pa - KERNBASE) >> PGSHIFT] = 0;
              release(&count.lock);
          }
      }
    

    alloc函数里直接在返回物理地址前使用add_count函数让计数加1(初始时和释放后引用计数都为0,所以加1后就是1),这里代码不贴了,add_count函数:

    void add_count(uint64 pa) {
      if (pa >= KERNBASE) {
          acquire(&count.lock);
          count.a[(pa - KERNBASE) >> PGSHIFT]++;
          release(&count.lock);
      }
    }
    
  • 相关阅读:
    notepad++ 编辑器链接地址可点击
    window的cmd窗口运行git
    php update for mac
    sublime打开文件时自动生成并打开.dump文件
    不能设置sublime text 2 为默认编辑器
    sublime text 2 配置文件
    Compass被墙后如何安装安装
    everything搜索工具小技巧
    Ubuntu安装已经下载好的文件包
    Flutter 异步Future与FutureBuilder实用技巧
  • 原文地址:https://www.cnblogs.com/YuanZiming/p/14242491.html
Copyright © 2011-2022 走看看