Loading

MIT 6.S081 操作系统 LAB5:Lazy allocation

Lab: xv6 lazy page allocation

实验的三个部分,逐步实现一个lazy allocation,我就按照自己的思路写,不分三个部分了

修改sbrk()

不直接分配物理内存,这也就是lazy allocation最本质的地方

  • n>0 只改变p->sz
  • n<0 改变p->sz同时调用uvmdealloc()取消对应的映射

uvmalloc()中调用uvmunmap()取消映射

uint64
sys_sbrk(void)
{
  int addr;
  int n;
  struct proc* p=myproc();

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

handle page fault

判断r_scause()的值等于13或15则发生了page fault
此时r_stval()的值就是发生page fault的虚拟地址
分配对应的物理内存,逻辑类似于uvmalloc()
先调用kalloc()分配内存,再调用mappages()映射
这里如果内存资源已经用完了,则kalloc()返回0,直接杀死进程就行

uint64 ka=(uint64)kalloc();
if(ka==0)
    p->killed=1;
else{
    memset((void*)ka,0,PGSIZE);
    va=PGROUNDDOWN(va);
    if(mappages(p->pagetable,va,PGSIZE,ka,PTE_W|PTE_U|PTE_R)!=0){
        kfree((void*)ka);
        p->killed=1;
    }
}

modify uvmunmap() and uvmcopy()

因为lazy allocation允许了未映射的地址也是合法的
所以修改对应的两个函数,让它们在发现未映射的时候直接跳过就行了

if((pte = walk(old, i, 0)) == 0 || (*pte & PTE_V) == 0)
    continue;

fork()里面会调用uvmcopy()复制内存,按如上修改后就没问题了

negative sbrk() arguments

如果sbrk()中n是负数,我们直接调用uvmunmap()就可以了
因为我们已经修改了uvmunmap()函数,不用管那部分地址有没有映射
如果没有映射,里面直接跳过就是了,如果映射了,就释放掉

if(n<0)
{
    uvmdealloc(p->pagetable,p->sz,p->sz+n);
}

illegal address

有两种情况地址是不合法的

  • va>=p->sz 超出了分配的最大空间
  • 访问的是stack下面的guard page

所以在处理缺页中断的时候要先判断是否合法,如果不合法,则杀死进程
通过p->killed=1杀死进程

if(va>=p->sz||is_guarded(p->pagetable,va))
    p->killed=1;

is_guarded()

加入is_guarded()函数判断是否是guard page
guard page有个特点,对应的虚拟地址分配了物理内存,但是pte中的flag没有设置PTE_U

就像这里的虚拟地址0x1000一样,有对应的物理地址,flag为0x000f
所以参考walkaddr()中的写法,如果发现一个虚拟地址,映射了对应的物理页,但没有设置PTE_U,则是guard page

int
is_guarded(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;

  if(va >= MAXVA)
    return 0;

  pte = walk(pagetable, va, 0);
  if(pte == 0)
    return 0;
  if((*pte & PTE_V) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 1;
  return 0;
}

valid system call arguements

lazy allocation的一个特点是你不能让用户程序察觉到内存还未分配
因此当程序往system call传递一个合法的地址时,要分配对应内存
传递地址都要通过argaddr()这个函数,所以在这里截胡

int
argaddr(int n, uint64 *ip)
{
  *ip = argraw(n);
  struct proc* p=myproc();
  uint64 va=*ip,pa=walkaddr(p->pagetable,va);
  if(va<p->sz&&!is_guarded(p->pagetable,va)&&pa==0)
  {
    uint64 ka=(uint64)kalloc();
    if(ka==0)
      p->killed=1;
    else{
      memset((void*)ka,0,PGSIZE);
      va=PGROUNDDOWN(va);
      if(mappages(p->pagetable,va,PGSIZE,ka,PTE_W|PTE_U|PTE_R)!=0){
        kfree((void*)ka);
        p->killed=1;
      }
    }
  }
  return 0;
}

只有在传递的地址合法,且对应内存未分配的情况下,我们才分配内存
通过walkaddr()判断是否分配

这里我们只检查合法性,而不用检查非法情况
因为按原xv6代码中的注释,argaddr()不检查合法性,交给copyin/copyout去检查

Retrieve an argument as a pointer.
Doesn't check for legality, since
copyin/copyout will do that.

test

posted @ 2021-12-30 16:09  traver  阅读(167)  评论(0编辑  收藏  举报