s081-2020 Lab5 lazy allocation

在这里插入图片描述

lazy page allocation

所谓的lazy allocation就是当用户进程通过sbrk()申请内存时,内核不会立即为其分配内存,而只是简单的增加了用户进程内存地址范围(增加p->sz),也没有为其分配页表项。这主要是因为用户程序申请的内存量可能会超过其实际使用的量,另一方面可以加快sbrk()的执行速度。

当cpu访问到lazy allocation的虚拟地址时,由于没有对应的页表项会触发缺页中断,我们要做的就是在缺页中断处理函数中为其分配物理内存。发生中断时,scause中存储的是中断类型,stval存储的是发生中断时访问的虚拟地址。下表给出了RISC-V支持的中断类型,第二列就是发生中断时scause的值,本实验中只涉及到两类缺页中断:Load page fault(scause=13)、Store page fault(scause=15)。

在这里插入图片描述

实验代码

本实验的三个部分实际上是实现lazy allocation的三步,为此这里不再分开讲述,而是直接讲述完整的lazy allocation的代码。

lazy allocation的第一步就是修改sys_sbrk(),这里需要强调的是lazy allocation对于申请内存空间是延迟执行的,但是对于释放内存空间却是立即执行的。即如果sbrk(n)的参数是负数,sys_sbrk()应该调用uvmdealloc缩减内存空间:

// sysproc.c

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) {
    uvmdealloc(p->pagetable, addr, addr + n);
  }
  // 如果是扩张内存,则采用lazy allocation的机制
  p->sz += n;
  return addr;
}

接下来就是在中断处理函数中为进程分配内存页面,如上所述我们只需要处理scause为13、或15的中断;且发生缺页异常的虚拟地址必须是在进程的有效内存地址范围内,为此我们需要先实现一个shouldAlloc()来判断缺页中断的虚拟地址是否合法:

// trap.c

// 检查虚拟地址是否处于lazy allocation的区域
// 要求虚拟地址不能在栈底以下,堆顶以上,并且还要是没有分配页表项的虚拟地址
int shouldAlloc(uint64 va) {
  pte_t* pte;
  struct proc* p = myproc();

  return va < p->sz // within size of memory for the process
    && va < r_sp() // not accessing stack guard page (it shouldn't be mapped)
    && (((pte = walk(p->pagetable, va, 0)) == 0) || ((*pte & PTE_V) == 0)); // page table entry does not exist
}

接下来实现为进程申请页面的alloc(),根据Hints如果内存分配失败映射杀死发生缺页中断的进程:

// trap.c


// 为虚拟地址分配页面
void alloc(uint64 va) {
  struct proc* p = myproc();
  char* mem = kalloc();
  if (mem == 0) {
    // failed to allocate physical memory
    printf("lazy alloc: out of memory\n");
    p->killed = 1;
  }
  else {
    memset(mem, 0, PGSIZE);
    if (mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)mem, PTE_W | PTE_X | PTE_R | PTE_U) != 0) {
      printf("lazy alloc: failed to map page\n");
      kfree(mem);
      p->killed = 1;
    }
  }
}

然后是在trap.c中处理缺页中断:

// trap.c

void
usertrap(void)
{
	...
	else if ((which_dev = devintr()) != 0) {
    // ok
  }
  else if (r_scause() == 13 || r_scause() == 15) {
    uint64 va = r_stval();
    // printf("page fault:%p\n", va);
    if (shouldAlloc(va)) {  // 由于lazy allocation引起的缺页异常
      alloc(va);
    }
    else {
      p->killed = 1;
    }
  }
  ...
}

到此还没有结束,我们还需要修改uvmunmap(),它会在proc_freepagetable()中被调用,用来释放进程的所有内存。因为lazy allocation没有为部分虚拟地址空间映射对应的页表项,所以要把uvmunmap()中一些原本在遇到无映射地址时会 panic 的函数的行为改为直接忽略这样的地址:

// kernel/vm.c

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; // 如果页表项不存在,跳过当前地址
    }
    if((*pte & PTE_V) == 0){
      continue; // 如果页表项不存在,跳过当前地址
    }
    if(PTE_FLAGS(*pte) == PTE_V)
      panic("uvmunmap: not a leaf");
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    *pte = 0;
  }
}

另外一个相同点是在fork时,uvmcopy也会遍历进程的页表,也存在类似问题:

// kernel/vm.c
// 修改这个解决了 fork 时的 panic
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; // 忽略不存在的映射项
    if((*pte & PTE_V) == 0)
      continue; // 忽略不存在的映射项
    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;
}

至此lazytests要求的都完成了,但是还没发通过usertestssbrkarg()。因为sbrkarg会涉及到内核与用户地址之间的内存拷贝,在拷贝之前需要完成内存的分配:

// kernel/vm.c
// 修改这个解决了 read/write 时的错误 (usertests 中的 sbrkarg 失败的问题)
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;

  if(shouldAlloc(dstva))
    alloc(dstva);

  // ......

}

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

  if(shouldAlloc(srcva))
    alloc(srcva);

  // ......
}

至此usertests的测试也能通过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值