• MIT-6.S081-2020实验(xv6-riscv64)五:lazy


    实验文档

    概述

    这次实验主要实现Lazy allocation的功能,即进程在动态分配内存的时候先不分配,等到要用到发生缺页中断的时候再实际分配,核心是实现缺页中断的处理。xv6的文档介绍了三种缺页中断的应用,第一为Copy on write,即fork的时候先不复制内存,等到要用到发生缺页中断的时候再实际分配;第二为硬盘虚拟内存,就是当内存不够大的时候将一部分硬盘区域当作内存交换区,虚拟地址只映射到一个无效位置,当访问该虚拟地址发生缺页中断时再把一个页的内容保存进磁盘,然后从磁盘中加载当前这个虚拟地址指向的实际内容;第三就是本实验的内容。

    内容

    Eliminate allocation from sbrk()

    这个任务非常简单,没啥好说的:

    uint64
    sys_sbrk(void)
    {
      int addr;
      int n;
    
      if(argint(0, &n) < 0)
        return -1;
      struct proc *p = myproc();
      addr = p->sz;
      if (n < 0) p->sz = uvmdealloc(p->pagetable, p->sz, p->sz + n);
      else p->sz += n;
      // if(growproc(n) < 0)
      //   return -1;
      return addr;
    }
    

    对n小于0情况的处理是第三个任务的内容,这里可以忽略。

    Lazy allocation

    这个任务要求实现对缺页中断的处理,因为在sbrk的时候仅仅指扩大了进程的虚拟地址区域,所以在访问这些虚拟地址时会发生缺页中断,这里就需要在发生缺页中断的时候分配物理内存然后映射,中断处理函数usertrap()对缺页中断进行处理:

    	......
        syscall();
      } else if((which_dev = devintr()) != 0){
        // ok
      } else {
          if (r_scause() == 13 || r_scause() == 15) {
              uint64 va = r_stval(); if (handle_page(va, p) == -1) p->killed = 1;
          } else {
              printf("usertrap(): unexpected scause %p pid=%d
    ", r_scause(), p->pid);
              printf("            sepc=%p stval=%p
    ", r_sepc(), r_stval());
              p->killed = 1;
          }
      }
      if(p->killed)
        exit(-1);
    

    这里把缺页中断的实际处理过程抽象成了一个函数,实际上仅从任务2考虑是没有必要的,但是任务3中还需要对copyin、copyout这些函数中发生缺页的情况进行处理,所以抽象成一个函数方便各处调用。

    handle_page函数我写在proc.c里,因为这里已经包含了所需要的头文件:

    int handle_page(uint64 va, struct proc *p) {
        uint64 base =  PGROUNDDOWN(va);
        if (va >= p->sz || va < p->trapframe->sp) return -1;
        char *mem = kalloc();
        if (mem == 0) return -1;
        memset(mem, 0, PGSIZE);
        if(mappages(p->pagetable, base, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) {
            kfree(mem); return -1;
        }
        return 0;
    }
    

    这些return -1的情况也是任务3的内容,任务2可以忽略,主要都是借鉴函数uvmalloc。然后修改一下uvmunmap(),即把一些因为缺页导致的panic跳掉了,因为这些页从来就没分配过,也就不用释放:

      for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
        if((pte = walk(pagetable, a, 0)) == 0) continue;
          // panic("uvmunmap: walk");
        if((*pte & PTE_V) == 0) continue;
          // panic("uvmunmap: not mapped");
        if(PTE_FLAGS(*pte) == PTE_V)
          panic("uvmunmap: not a leaf");
        ......
    

    Lazytests and Usertests

    这个任务主要是把上个任务遗留的一些不合法情况进行处理。

    第一是sbrk的参数为负数的问题,根据growproc函数的内容,对参数为负数的情况就是释放参数绝对值大小的内存,仿造growproc()就行了,见上面的代码。uvmdealloc本身不用修改,因为内部就是调用uvmunmap的。

    第二是缺页中断中当虚拟地址不合法时应该直接返回并杀掉进程,不合法包含两种情况,一是虚拟地址太大,大出了进程所申请的内存(不管实际有没有分配),因为进程虚拟地址从0开始,所以只要保证虚拟地址小于p->sz即可;而是虚拟地址太小,比进程的栈顶还低(注意栈是从高往低增长的),这就需要知道栈顶的位置,查看测试程序usertests,发现它获取栈顶的方法就是读sp寄存器,但是缺页中断的处理是在内核态,sp指向的也是内核栈的栈顶,想要获得用户栈的栈顶,可以借助进程的中断帧来实现,即读取p->trapframe->sp,需要保证虚拟地址大于等于这个值。杀掉进程可以观察usertrap函数的其他位置,发现只要令p->killed=1即可,见上面的代码。

    第三是如果申请物理内存失败时也要杀掉进程,加上映射失败,照着uvmalloc里写就行了。

    第四是fork的时候复制到缺页的虚拟地址时的处理,注意到fork的这部分是调用的uvmcopy,所以改uvmcopy,和uvmunmap一样,缺页导致的panic跳掉:

      for(i = 0; i < sz; i += PGSIZE){
        if((pte = walk(old, i, 0)) == 0) continue;
          // panic("uvmcopy: pte should exist");
        if((*pte & PTE_V) == 0) continue;
          // panic("uvmcopy: page not present");
        ......
    

    第五是read和write文件的时候如果传入了一个缺页的虚拟地址(在将文件读入内存和将内存写入文件时需要传入地址),追踪这两个函数的过程可以发现最终处理地址调用的是copyin、copyinstr和copyout函数,注意到这几个函数会先walk一下传入的虚拟地址,如果得不到物理地址就直接返回失败,而不会经过缺页中断的过程,所以直接加入代码让其在判断得不到物理地址的情况下调用handle_page函数即可:

        va0 = PGROUNDDOWN(srcva);
        pa0 = walkaddr(pagetable, va0);
        if(pa0 == 0) {
            if (handle_page(va0, myproc()) == -1) return -1; else pa0 = walkaddr(pagetable, va0);
          // return -1;
        }
    	......
    

    总结一下,缺页中断的发生时刻应该是在MMU访问到一个PTE_V位为0的PTE时,在xv6中这个PTE的其他位是没有意义的,而在riscv-pk(用在spike模拟器上的代理内核)则让PTE的其他位指向一个标记结构体,里面包含了这个缺页的信息,比如该缺页是否是因为内存被置换到硬盘上了,置换到了哪个位置等信息,这样就使得该系统可以处理多种原因导致的缺页中断,而xv6应该是不支持硬盘虚拟内存的。

  • 相关阅读:
    【队列应用一】随机迷宫|随机起点终点*最短路径算法
    【堆栈应用二】迷宫算法,可直接动态运行
    【堆栈应用一】一个数divided=几个最小质因数的乘积(时间复杂度On)
    MyEclipse2014中Java类右键Run as没有JUnit Test
    C++中break/Continue,exit/return的理解
    windows下用C++修改本机IP地址
    windows下注册表的操作
    详解Java的Spring框架中的注解的用法
    什么是Java Bean
    JS windows对象的top属性
  • 原文地址:https://www.cnblogs.com/YuanZiming/p/14223188.html
Copyright © 2020-2023  润新知