MIT6.S081 Lab5:Xv6 lazy page allocation

本文描述了一个实验,目标是实现内存页的懒分配,即在调用sbrk时不立即分配内存,仅记录请求。在遇到pagefault时才分配物理内存。实验分为三步,包括修改sbrk()避免实际分配,处理用户态的pagefault,以及测试懒分配机制。在处理pagefault时,会检查是否因懒分配导致,并相应地分配内存、建立映射。同时,处理了内存超出分配范围、未分配的内存读写、内存不足等情况。
摘要由CSDN通过智能技术生成

实验目的

实现一个内存页懒分配机制,在调用 sbrk() 的时候,不立即分配内存,而是只作记录。在访问到这一部分内存的时候才进行实际的物理内存分配。

本次 lab 分为三个部分,但其实都是属于同一个实验的不同步骤,所以本文将三点集合到一起:

Eliminate allocation from sbrk()[easy]

在sbrk()的具体实现函数sys_sbrk()中只增加(或者减少)进程内存大小,但不实际分配内存(不调用growproc()),下面的代码考虑到了sbrk参数为负的情况

//kernel/sysproc.c
uint64
sys_sbrk(void)
{
  int addr;
  int n;

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

Lazy allocation[moderate] & Tests[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
  • 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.
    提示
  • You can check whether a fault is a page fault by seeing if r_scause() is 13 or 15 in usertrap().
  • r_stval() returns the RISC-V stval register, which contains the virtual address that caused the page fault.
  • Steal code from uvmalloc() in vm.c, which is what sbrk() calls (via growproc()). You’ll need to call kalloc() and mappages().(参照uvmalloc()代码,kalloc()和mappages()结合使用来分配空间和建立映射)
  • Use PGROUNDDOWN(va) to round the faulting virtual address down to a page boundary.
  • uvmunmap() will panic; modify it to not panic if some pages aren’t mapped.(不要直接抛出panic)
  • If the kernel crashes, look up sepc in kernel/kernel.asm
  • Use your vmprint function from pgtbl lab to print the content of a page table.
  • If you see the error “incomplete type proc”, include “spinlock.h” then “proc.h”.(proc.h之前要先包含spinlock.h)
  • Kill a process if it page-faults on a virtual memory address higher than any allocated with sbrk().(造成page fault的虚拟地址不能超过进程已经分配的虚拟地址大小p->sz)
  • Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write,but the memory for that address has not yet been allocated.(对于有效地址(PTE_V)但物理空间还没有分配的情况应该予以处理,不能像之前那样抛出panic)
  • Handle out-of-memory correctly: if kalloc() fails in the page fault handler, kill the current process.(即如果kalloc返回0,修改p->killed为非零值)
  • Handle faults on the invalid page below the user stack.
    实验过程
    1.修改 usertrap 用户态 trap 处理函数,为缺页异常添加检测,如果为缺页异常((r_scause() == 13 || r_scause() == 15)),且发生异常的地址是由于懒分配而没有映射的话,就为其分配物理内存,并在页表建立映射:
// kernel/trap.c

// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
void
usertrap(void)
{
  // ......
    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
  } else {
    uint64 va = r_stval();
    if((r_scause() == 13 || r_scause() == 15) && uvmshouldtouch(va)){ // 缺页异常,并且发生异常的地址进行过懒分配
      uvmlazytouch(va); // 分配物理内存,并在页表创建映射
    } 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;
    }
  }

  // ......

}

2.uvmlazytouch()和uvmshouldtouch()函数的实现
uvmlazytouch()函数负责分配实际的物理内存并建立映射。懒分配的内存页在被 touch 后就可以被使用了。
uvmshouldtouch()用来判断虚拟地址是否是待懒分配的地址,下面三种情况都满足表明它是这样的地址:

  • 该虚拟地址没有超过进程已经分配的地址
  • 该虚拟地址不是守护页地址(具体见 xv6 book,栈页的低一页故意留成不映射,作为哨兵用于捕捉 stack overflow 错误。懒分配不应该给这个地址分配物理页和建立映射,而应该直接抛出异常)
  • 页表项不存在
//kernel/vm.c
//+
// touch a lazy-allocated page so it's mapped to an actual physical page
void uvmlazytouch(uint64 va) {
	struct proc *p = myproc();
	char *mem = kalloc();
	if(mem == 0) {
		printf("lazy alloc : out of memory");
		p->killed = 1; //kill because kalloc() failed
	}
	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;
		}
	}
}


// whether a page is previously lazy-allocated and needed to be touched before use
int uvmshouldtouch(uint64 va) {
	pte_t *pte;
	struct proc *p = myproc();
	
	return va < p->sz   //within size of memory for process
	       && PGROUNDDOWN(va) != r_sp() //not acessing stack guard page(it shouldn't mapped)
	       && ((pte = walk(p->pagetable, va, 0)) == 0 || (*pte & PTE_V) == 0);//pagetable entry does not exist 
}

3.由于懒分配的页,在刚分配的时候是没有对应的映射的,所以要把一些原本在遇到无映射地址时会 panic 的函数的行为改为直接忽略这样的地址。

//kernel/vm.c
//修改这里解决了proc_freepagetable()中调用uvmunmap()出现的panic

// Remove npages of mappings starting from va. va must be
// page-aligned. The mappings must exist.
// Optionally free the physical memory.
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)
      //panic("uvmunmap: walk");
      continue;   //if pte does not exist,
    if((*pte & PTE_V) == 0)
      //panic("uvmunmap: not mapped");
      continue;   //if pte does not valid
    if(PTE_FLAGS(*pte) == PTE_V)
      panic("uvmunmap: not a leaf");
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    *pte = 0;
  }
}
//kernel/vm.c
//修改这个解决了fork时调用uvmcopy()产生的panic
// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
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)
      //panic("uvmcopy: pte should exist");
      continue;  //a pte does not exist,we consider it might be a lazy page
    if((*pte & PTE_V) == 0)
      //panic("uvmcopy: page not present");
      continue;  a pte does not valid,we consider it might be a lazy page
    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;

4.copyin() 和 copyout():内核/用户态之间互相拷贝数据

由于这里可能会访问到懒分配但是还没实际分配的页,所以要加一个检测,确保 copy 之前,用户态地址对应的页都有被实际分配和映射。

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

  if(uvmshouldtouch(dstva))
    uvmlazytouch(dstva);

  // ......

}

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

  if(uvmshouldtouch(srcva))
    uvmlazytouch(srcva);

  // ......

}

Lazytests and Usertests[moderate]

提示

  • Kill a process if it page-faults on a virtual memory address higher than any allocated with sbrk().(造成page fault的虚拟地址不能超过进程已经分配的虚拟地址大小p->sz)
  • Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write,but the memory for that address has not yet been allocated.(对于有效地址(PTE_V)但物理空间还没有分配的情况应该予以处理,不能像之前那样抛出panic)
  • Handle out-of-memory correctly: if kalloc() fails in the page fault handler, kill the current process.(即如果kalloc返回0,修改p->killed为非零值)
  • Handle faults on the invalid page below the user stack.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值