MIT-6.s081-OS lab lazy: xv6 lazy page allocation

本实验要实现的是lazy allocation

很多时候,一个进程可能使用sbrk申请了大量的内存,但是其中的大部分并没有使用,造成了浪费

我们要做的是修改sbrk的实现,sbrk不分配物理内存,just remember which addresses are allocated

当进程第一次试图使用任何内存时,产生一个page fault,再让kernel分配内存,清零,映射

本实验涉及了第三章和第四章的内容,所以要看的还是挺多的

分析:因为我们没有实际分配内存(建立pte),所以用户程序访问对应地址时就会page fault,也就是一个来自用户空间的trap(异常)

根据第四章,page fault(trap)时,硬件会做这些事:

禁用中断(clear sie bit in sstatus)

把pc复制到sepc

保存当前mode到sstatus的spp bit

设置scause为trap的原因

设置模式为supervisor

把stvec的值设为pc(stvec保存trap handler的地址)

从pc执行(执行trap handler)

struct trapframe {
  /*   0 */ uint64 kernel_satp;   // kernel page table
  /*   8 */ uint64 kernel_sp;     // top of process's kernel stack
  /*  16 */ uint64 kernel_trap;   // usertrap()
  /*  24 */ uint64 epc;           // saved user program counter
  /*  32 */ uint64 kernel_hartid; // saved kernel tp

所以,page fault时,从uservec开始执行(trampoline.s),据书中所说,在进入用户空间之前,内核sscratch指向每个进程的trapframe,trapframe有空间用来保存寄存器的值,然后把一系列的寄存器的值保存在trapframe中,之后,把sp设为内核栈顶指针,tp设为hartid,切换到内核页表(修改satp),跳转到usertrap,处理trap,完成之后usertrapret,userret

...好像并不用分析这些 

update:

不得不说 做这个才正式开始学习虚拟内存,虽然之前csapp学过一点,并且之前也做过allocator的lab 但是都感觉没有深入 或者说不是全关于vm 刚开始还是不很适应的 一度stuck 找了好几篇博客才得以推进

要完成这个实验,之前要做的一个很重要的工作是:code walk through ,这个其实是lecture5里提到了的,但是因为1.时间跨度拉的有点长2.没有重视课程给出的lecture(毕竟实在是太简陋了)

阅读代码的笔记:https://blog.csdn.net/RedemptionC/article/details/107709618 (大概就这样)

另外,不得不说给出的材料真的要好好看,不要遗漏,所以这里还是从实验指导书开始吧

剩下的提纲也按照实验指导书来:

print page table:

这里要求实现一个函数,打印页表,实现如下的效果:

这里要注意几点:

  • 实现的函数调用放在哪呢?实验指导书里说了,exec()里,并且只需要打印出第一个用户进程的页表,谁是第一个用户进程?debug一下就知道了,在exec打一个断点,可以知道,是init:

        所以可以用strncmp比较一下path和/init,当然也可以用p->name比较init,甚至可以试试都打印出来,只要能通过test即可

  • 注意格式,最好是选用" ..",而不是".. ",print test对格式的要求还是很严格的

Eliminate allocation from sbrk()

原来的sbrk是调用growproc实现的,而growproc,则针对n的正负,分别调用了uvmalloc,uvmdealloc,但是我们现在需要lazy allocation

也就是在分配内存时,不直接分配物理内存和设置pte,所以这里要删去对growproc的调用,仅仅修改p->sz,但是回收内存还是要马上做的:

uint64
sys_sbrk(void)
{
  int addr;
  int n;

  if(argint(0, &n) < 0)
    return -1;
  addr = myproc()->sz;
  myproc()->sz+=n;
  if(n<0){
    myproc()->sz=uvmdealloc(myproc()->pagetable,addr,myproc()->sz);    
  }
  return addr;
}

此时我们运行xv6,运行echo hi,会出现:

init: starting sh 
$ echo hiusertrap(): unexpected scause 0x000000000000000f pid=3 
            sepc=0x0000000000001258 stval=0x0000000000004008 
va=0x0000000000004000 pte=0x0000000000000000 
panic: uvmunmap: not mapped

搜索panic的内容,可以定位到出错位置:vm.c中的uvmunmap函数,判断出当前pte为invalid,而由panic的实现可知,最终会无限循环

那么是怎么运行到uvmunmap的呢?突然发现不知道是在哪usertrap的 TODO

但是我们知道出错的原因:sbrk中没有申请物理内存并设置pte,因此会发生page fault,指导书给出的建议是学习uvmalloc,也就是在usertrap中,判断出scause,如果是与page fault相关的,那么首先由stval知道出错的虚拟地址,然后申请物理内存,并且建立pte

我之前在这里犯了一个错误,在申请内存时,因为看到uvmalloc中使用了一个循环,每次处理一页,所以我也照搬过来,把va~p->sz之间的内存全部申请了,这也导致一直通不过lazy test...

实际上指导书里也提到了是map "a" newly allocated page,但是可能对英语不太敏感吧,没想到是指一个页=-=

另外没注意的一点是,指导书里提到,如果有没有映射的页,uvmunmap会panic,去掉panic,当时可能没想到可以直接去掉出错的语句=-= 以为要尽量必要运行到那里,但是后来发现这两者并不矛盾

于是现在可以重新echo hi了

Usertests

现在要修复一系列问题 还挺复杂的 跟着指导书的指示来:

图中绿色的是比较简单的:

第一条 已经解决了

第二条,是指使用的不能超过通过sbrk分配的,我们知道sbrk分配内存之后,会增加p->sz,所以这里就是要求faulting addr不要超过p->sz(faulting addr和p->sz为什么可以比较?)

第五条,就是如果kalloc分配失败,直接kill,这个只要看kalloc的返回值是不是0就行

第六条,是要处理栈溢出,即如果访问了guard page要kill 我看了下网上的,大致有三种做法:

其中第一种,比较复杂,没有采用;第二种,因为用户栈的生成是在exec中,调用uvmclear,将guard page标记为invalid,我们只要在之后set pte_guard即可

第三种当然是最简单的

接着看第三条,处理fork:

fork中相关的代码主要在uvmcopy里:将父进程的内存复制到子进程,但是他如果判断invalid,会panic,而我们使用的lazy allocation,并不能保证此时父进程的内存都已经分配了,所以即使invalid,只要跳过,继续下一页就好

第四条:指这样一种情况,现在处理进程内存的不是用户进程,而是read/write等系统调用,这样一来,处理异常的就不是usertrap

内核读写用户内存,是通过vm.c中的copyin/out函数,其中主要是使用walkaddr找到用户内存虚拟地址对应的物理地址,然后用memmove直接通过物理地址(实际上还是虚拟地址,但是内核内存大部分是va=pa)传送

关键在于walkaddr:

uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;
  uint64 pa;

  if(va >= MAXVA)
    return 0;

  pte = walk(pagetable, va, 0);
  if (pte == 0 || ((*pte & PTE_V) == 0))
  {
    if (va >= myproc()->sz)
      return 0;

    if (uvmcheck_guard(pagetable, va))
      return 0;

    uint64 a = PGROUNDDOWN(va);
    char *mem = kalloc();
    if (mem == 0)
    {
      return 0;
    }
    memset(mem, 0, PGSIZE);
    if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W | PTE_X | PTE_R | PTE_U) != 0)
    {
      kfree(mem);
      return 0;
    }
    pte = walk(pagetable, va, 0);
  }

  if ((*pte & PTE_U) == 0)
    return 0;
  pa = PTE2PA(*pte);
  return pa;
}

其中pte如果为0,就是没分配,如果没分配或者invalid,那么就会缺页,这里就要做和usertrap里类似的事

如果pte有效,那么判断该页是否该页是否能被用户访问

如果都通过 就返回物理地址

猜你喜欢

转载自blog.csdn.net/RedemptionC/article/details/107464400