0%

lab5-Lazy

Lab 5:Lazy Page Allocation

课程地址:https://pdos.csail.mit.edu/6.828/2020/schedule.html  
  Lab 地址:https://pdos.csail.mit.edu/6.828/2020/labs/lazy.html
  代码仓库:https://github.com/ajackchan/my-xv6-riscv/tree/lazy

Lazytests and Usertests

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

1.修改 sys_sbrk() 函数

kernel/sysproc.c 中修改 sys_sbrk() 函数,只修改虚拟内存大小而不立即分配物理内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uint64
sys_sbrk(void)
{
int addr;
int n;
struct proc *p = myproc();

if(argint(0, &n) < 0)
    return -1;  // 获取参数失败,返回错误
   
  addr = p->sz;  // 获取当前进程的地址空间大小
 
if(n < 0) {
uvmdealloc(p->pagetable, p->sz, p->sz+n);
}

  p->sz += n;  // 调整虚拟地址空间大小
  return addr;  // 返回原始大小
}

2.用户态异常处理 (usertrap())

kernel/trap.c 中处理缺页异常,并且在需要时分配物理内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
    }
  }
......
}

3.物理内存分配 (uvmlazytouch())

kernel/vm.c 中实现物理内存的分配和映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 触摸一个懒惰分配的页面,将其映射到一个实际的物理页面。
void uvmlazytouch(uint64 va) {
  struct proc *p = myproc();
  char *mem = kalloc();
  if (mem == 0) {
    // 分配物理内存失败
    printf("lazy alloc: out of memory\n");
    p->killed = 1;
  }
  else{
    memset(mem, 0, PGSIZE); // 初始化内存

    uint64 rounded_va = PGROUNDDOWN(va);
    if (mappages(p->pagetable, rounded_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;
    }
  }
}

4.地址验证 (uvmshouldtouch())

kernel/vm.c 中,添加一个函数来验证是否需要懒加载处理。

1
2
3
4
5
6
int uvmshouldtouch(uint64 va) {
struct proc *p = myproc();
return va < p->sz // 地址在进程内存范围内
&& PGROUNDDOWN(va) != r_sp() // 非栈的保护页
&& (walk(p->pagetable, va, 0) == 0 || (*(walk(p->pagetable, va, 0)) & PTE_V) == 0);
}

5.修改其他内核函数

对其他内核函数做适当修改,以支持懒加载机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// uvmunmap: 取消虚拟地址映射
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;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// uvmcopy: 将父进程的页表以及内存拷贝到子进程
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// copyin() 和 copyout(): 内核/用户态之间互相拷贝数据
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测试

usertests测试

-------------本文结束感谢您的阅读-------------