首页 > 其他分享 >MIT6.S081 Lab lazy page allocation

MIT6.S081 Lab lazy page allocation

时间:2024-04-24 14:58:21浏览次数:17  
标签:uint64 lazy return pagetable MIT6 pte Lab 缺页 va0

本次实验是有关内存页懒分配的。所谓内存页懒分配,在本实验中,指的是在用户进程使用 sbrk() 系统调用来增加内存中堆的空间时,我们不直接在物理内存中分配相应的页,而是只是记录了分配到了哪些用户地址,在用户页面表中这些地址默认标记为无效。当进程首次尝试使用任何给定页面的懒惰分配内存时,CPU会生成一个 Page fault,内核通过分配物理内存、将其归零并将其映射来处理。

这次实验的难度不大,主要是写了个很无语的错误导致调了很久。

Eliminate allocation from sbrk() (easy)

Your first task is to delete page allocation from the sbrk(n) system call implementation, which is the function sys_sbrk() in sysproc.c. The sbrk(n) system call grows the process's memory size by n bytes, and then returns the start of the newly allocated region (i.e., the old size). Your new sbrk(n) should just increment the process's size (myproc()->sz) by n and return the old size. It should not allocate memory -- so you should delete the call to growproc() (but you still need to increase the process's size!).

这一步的要求只是将原本 sys_sbrk() 中调用 growproc() 的部分删去,使得增加堆空间的时候,只是简单地增加 myproc()->sz 的值,不进行页面分配。

这里我比任务要求多做了一步(这实际上会是下面的任务),判断了一下 n 的正负,如果为负则正常调用 growproc() 进行页面的删除,否则就只是记录 myproc()->sz 的值。

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) {
    if(growproc(n) < 0)
      return -1;
  } else p->sz += n;
  return addr;
}

./20231121-MIT6-S081-lazy/image-20231121160453033

正如期望的那样,这里出现了一个 SCAUSE15 的写缺页异常。

Lazy allocation (moderate) & Lazytests and Usertests (moderate)

Modify the code in trap.c to respond to a page fault from user space by mapping a newly-allocated page of physical memory at the faulting address, and then returning back to user space to let the process continue executing. You should add your code just before the printf call that produced the "usertrap(): ..." message. Modify whatever other xv6 kernel code you need to in order to get echo hi to work.

实现缺页处理

一个正常的缺页异常,可以通过 SCAUSE 寄存器的值来判断。SCAUSE 寄存器的值是 12, 13, 15 时就是缺页异常。在本实验中,我们只需要考虑 1315 两种,分别是由读取和写入带来的缺页(12 是由指令执行带来的缺页,显然不应该出现在 sbrk 增加的堆空间里)

引发缺页的地址会被存储在 STVAL 寄存器中。

在实现缺页处理的时候,我们要首先判断缺页地址是不是由我们的 sbrk 扩展的堆空间中的。除了这个地址之外的所有地址,都不应该补充分配物理内存,而是直接 kill 掉。判断的方法由很多,我这里采用的是,这个地址应该小于 p->sz,而不应该小于用户态 SP 寄存器的值(也就是 p->trapframe->sp,这个条件主要是防止读写栈页前面的 guard page)。

然后,我们可以申请一个物理页面,如果不能申请到,那么就是 OOM(Out Of Memory) 了,没有可用的物理地址,那么就把进程 kill

如果成功了,我们就将物理页面清零,然后将虚拟地址所在页面 map 上这个物理页面。在 map 的过程中也有可能会 OOM(没有空间存放页表项了),那么我们也把这个进程 kill

因为在后面的任务中可能会重用这个过程的代码,我将缺页处理放在了一个函数中,方便直接调用。

int pfdeal(uint64 va) {
  struct proc *p = myproc();
  if (va >= p->sz || va < p->trapframe->sp)
    return -1;
  else {
    va = PGROUNDDOWN(va);
    uint64 pa = (uint64)kalloc();
    if (pa == 0) {
      return -1;
    } else {
      memset((void *)pa, 0, PGSIZE);
      if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W|PTE_R|PTE_U) != 0) {
        kfree((void *)pa);
        return -1;
      }
    }
  }
  return 0;
}

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

修改 uvmunmap

如果我们做完以上的修改,然后直接运行,那么我们可以发现,会出现 panic: uvmunmap: not mapped 的错误。

这是因为 xv6 原始的 uvmunmap 函数中,默认参数提供的虚拟地址范围都是已经分配好了的。这是理所当然的,只是在我们 lazy allocation 的版本中,有效的虚拟地址范围不一定都对应着物理页面。

所以我们可以将循环中前两个 if 判断原本的报错,全都修改为 continue。这两个判断,一个是判断页表项是否存在,一个是判断是否有效。(显然,在一个虚拟地址没有分配物理页面的时候,其页表项所在的页目录可能存在,也可能不存在,这就分别对应着标志位无效和页表项不存在两种情况)

void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
  uint64 a;
  pte_t *pte;

  if((va % PGSIZE) != 0)
    panic("uvmunmap: not aligned");

  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");
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    *pte = 0;
  }
}

修改 uvmcopy

这里和 uvmunmap 需要修改的原本是相似的,都是需要遍历一段虚拟页(只不过这里是将虚拟页对应的物理页拷贝到新页表中),修改内容也比较相似。

这个修改影响的往往是调用 fork 的时候,在父进程和子进程之间复制内存内容的时候。

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)
      continue;
      // panic("uvmcopy: pte should exist");
    if((*pte & PTE_V) == 0)
      continue;
      // panic("uvmcopy: page not present");
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    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;
}

修改 copyin/copyout/copyinstr

修改到这里,应该 echo hi 这个命令已经可以正常执行了(其实应该只修改到 uvmunmap 就应该已经可以了)。

但是如果进行 usertests,会发现还有很多点无法通过。

这是因为除了由用户进程读写一个地址引起缺页意外,在系统调用(比如 readwrite)中,用户也有可能传递一个用户空间地址过来,由内核对用户空间的这个地址进行读写。不会触发用户态的缺页异常,因此不能通过 usertrap 来处理。

对用户地址的读写往往是由 copyin/copyout/copyinstr 这几个函数完成的,因此我们只需要在这三个函数内部,判断读写的页是否是通过 sbrk 分配且还没有映射物理内存,然后进行物理页面的分配即可。这个过程已经被我们封装在 pfdeal 函数中了。

(为了能在 vm.c 中调用这个函数,记得修改 defs.h

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) {
      if (pfdeal(va0) != 0)
        return -1;
      pa0 = walkaddr(pagetable, va0);
    }
    // ...
  }
  return 0;
}

int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(srcva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0) {
      if (pfdeal(va0) != 0)
        return -1;
      pa0 = walkaddr(pagetable, va0);
    }
    // ...
  }
  return 0;
}

int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
  uint64 n, va0, pa0;
  int got_null = 0;

  while(got_null == 0 && max > 0){
    va0 = PGROUNDDOWN(srcva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0) {
      if (pfdeal(va0) != 0)
        return -1;
      pa0 = walkaddr(pagetable, va0);
    }
    // ...
  }
  // ...
}

测试

lazytests

./20231121-MIT6-S081-lazy/image-20231122150057756

usertests

./20231121-MIT6-S081-lazy/image-20231122150212027

标签:uint64,lazy,return,pagetable,MIT6,pte,Lab,缺页,va0
From: https://www.cnblogs.com/hankeke303/p/18155289/MIT6-S081-lazy

相关文章

  • MIT6.S081 Lab syscall
    这一个实验的主要内容就是给xv6添加两个系统调用:trace和sysinfo。Usinggdb(easy)这个部分我就不做了……M1的MacbookAir上的gdb太难安装了,所以暂时用不了gdb调试……Systemcalltracing(moderate)Inthisassignmentyouwilladdasystemcalltracingfe......
  • MIT6.S081 Lab Page Tables
    实验开始前的折腾突然发现2023版的和2020版的实验内容其实还不一样……因为我正在看的视频以及参考资料都是基于2020版的课程,因此我还是决定将之前的实验都迁移到2020版的xv6-lab-2020来。在自己的MacbookAir上折腾了好久……还是没能成功。因此还是用上了我在阿......
  • CSAPP Lab 7 Malloc Lab
    本次实验的内容也比较清晰,只需要完成一个手写的malloc动态内存分配器即可。书上第\(9\)章第\(9\)节介绍了这样的分配器的实现方法。实验要求本次实验提供了基本的框架,需要完成下面几个函数:intmm_init(void);void*mm_malloc(size_tsize);voidmm_free(void*ptr);v......
  • CSAPP Lab 8 Proxy lab
    终于到最后一个Lab啦!这个Lab的任务是实现一个代理服务器,将客户端发送的请求转发到服务端。这个Lab分为三个任务,第一个任务需要实现这个代理服务,第二个任务支持处理并发请求,第三个任务需要实现缓存。PartI:Implementingasequentialwebproxy这个部分其实很好写,很多......
  • CSAPP Lab-1 DATALAB
    本文原发于2023-09-0215:32:57于我的hexo博客,现迁移至此。最近看完了CSAPP整本书,发现官网上还有11次实验可以做。UPD:好像只有9个,因为有两个是旧版本的,可以被新版的替代掉。UPD:好像只有8个,performance也算是旧的实验了,但是没有明确指出。Lab地址:http://csapp.cs......
  • CSAPP Lab-3 ATTACKLAB
    书接上回,这次做到了第三个Lab啦。任务描述这一个Lab的任务就更有意思了,实验给了我们两个程序,每个程序都会让我们输入一行字符串,而它们是通过下面这个函数来读取的:unsignedgetbuf(){ charbuf[BUFFER_SIZE]; Gets(buf); return1;}其中,Gets函数和C库的gets函数......
  • CSAPP Lab-4 Architecture Lab
    本次实验是有关书上第四章设计的Y86-64处理器的,实验分为三个部分,分别是编写几个简单的Y86-64程序、使用一条新指令扩展SEQ模拟器以及优化Y86-64的基准测试程序和处理器设计。实验准备需要简单复习一下Y86-64的指令集架构以及处理器架构呢。指令集架构指令集:指令功......
  • CSAPP Lab5 Cache Lab
    到实验5啦!这次的实验是有关高速缓存的。让我们先来复习一下高速缓存的基础知识吧!复习高速缓存的结构在一个存储器地址有\(m\)位的系统上,一共有\(M=2^m\)个地址。假设高速缓存被组织成一个有\(S=2^s\)个高速缓存组的数组,其中每个组包括\(E\)个高速缓存行,每行存......
  • CSAPP Lab6 Shell Lab
    本次实验的任务很清晰,实现一个简单的UnixShell。需要用到基础的进程控制、信号处理等知识。简单来说,实验已经提供了一些简单的功能,我们需要在此基础上,实现下面的功能:eval:解析和解释命令行的主例程。[70行]builtin_cmd:识别并解释内置命令quit(退出)、fg(前台运行某个作业)、bg(后......
  • CSAPP Lab-2 BOMBLAB
    第二个Lab就比较有趣了。这一个Lab的任务是,我们得到了一个bomb炸弹程序,这个炸弹程序有\(6\)个phase,每个phase都会读取我们的输入,判断我们的输入是否符合要求,如果正确这个phase的炸弹就会被拆除,否则炸弹就会爆炸。我们需要借助各种工具,对程序进行反汇编等等,获得能够......