Contents

6.s081_lab5

lab5

Eliminate allocation from sbrk

将sys_sbrk()中的growproc函数调用删除,因为其是给新增加的堆空间分配内存的,我们现在需要惰性分配,所以并不真正分配空间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
uint64
sys_sbrk(void)
{
  int addr;
  int n;

  if(argint(0, &n) < 0)
    return -1;

  addr = myproc()->sz;
  // lazy allocation
  myproc()->sz += n;

  return addr;
}

未分配的页表比如512项中只分配了两项,那么其余的均为0x00000000000000000。

../../../../6.s081_notes/lab5.assets/image-20230404163003253.png

Lazy allocation

(1). 修改usertrap()(*kernel/trap.c*)函数,使用r_scause()判断是否为页面错误,在页面错误处理的过程中,先判断发生错误的虚拟地址(r_stval()读取)是否位于栈空间之上,进程大小(虚拟地址从0开始,进程大小表征了进程的最高虚拟地址)之下,然后分配物理内存并添加映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  uint64 cause = r_scause();
  if(cause == 8) {
    ...
  } else if((which_dev = devintr()) != 0) {
    // ok
  } else if(cause == 13 || cause == 15) {
    // 处理页面错误
    uint64 fault_va = r_stval();  // 产生页面错误的虚拟地址
    char* pa;                     // 分配的物理地址
    if(PGROUNDUP(p->trapframe->sp) - 1 < fault_va && fault_va < p->sz &&
      (pa = kalloc()) != 0) {
        memset(pa, 0, PGSIZE);
        if(mappages(p->pagetable, PGROUNDDOWN(fault_va), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) {
          kfree(pa);
          p->killed = 1;
        }
    } else {
      // printf("usertrap(): out of memory!\n");
      p->killed = 1;
    }
  } else {
    ...
  }

(2). 修改uvmunmap()(*kernel/vm.c*),之所以修改这部分代码是因为lazy allocation中首先并未实际分配内存,所以当解除映射关系的时候对于这部分内存要略过,而不是使系统崩溃,这部分在课程视频中已经解答。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
  ...

  for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
    if((pte = walk(pagetable, a, 0)) == 0)
      panic("uvmunmap: walk");
    if((*pte & PTE_V) == 0)
      continue;

    ...
  }
}

Lazytests and Usertests

(1). 处理sbrk()参数为负数的情况,参考之前sbrk()调用的growproc()程序,如果为负数,就调用uvmdealloc()函数,但需要限制缩减后的内存空间不能小于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
uint64
sys_sbrk(void)
{
  int addr;
  int n;

  if(argint(0, &n) < 0)
    return -1;

  struct proc* p = myproc();
  addr = p->sz;
  uint64 sz = p->sz;

  if(n > 0) {
    // lazy allocation
    p->sz += n;
  } else if(sz + n > 0) {
    sz = uvmdealloc(p->pagetable, sz, sz + n);
    p->sz = sz;
  } else {
    return -1;
  }
  return addr;
}

(2). 正确处理fork的内存拷贝:fork调用了uvmcopy进行内存拷贝,所以修改uvmcopy如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  ...
  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walk(old, i, 0)) == 0)
      continue;
    if((*pte & PTE_V) == 0)
      continue;
    ...
  }
  ...
}

(3). 还需要继续修改uvmunmap,否则会运行出错,关于为什么要使用两个continue,请看本文最下面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
  ...

  for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
    if((pte = walk(pagetable, a, 0)) == 0)
      continue;
    if((*pte & PTE_V) == 0)
      continue;

    ...
  }
}

(4). 处理通过sbrk申请内存后还未实际分配就传给系统调用使用的情况,系统调用的处理会陷入内核,scause寄存器存储的值是8,如果此时传入的地址还未实际分配,就不能走到上文usertrap中判断scause是13或15后进行内存分配的代码,syscall执行就会失败

  • 系统调用流程:
    • 陷入内核**==>usertrapr_scause()==8的分支==>syscall()==>**回到用户空间
  • 页面错误流程:
    • 陷入内核**==>usertrapr_scause()==13||r_scause()==15的分支==>分配内存==>**回到用户空间

因此就需要找到在何时系统调用会使用这些地址,将地址传入系统调用后,会通过argaddr函数(*kernel/syscall.c*)从寄存器中读取,因此在这里添加物理内存分配的代码

 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
int
argaddr(int n, uint64 *ip)
{
  *ip = argraw(n);
  struct proc* p = myproc();

  // 处理向系统调用传入lazy allocation地址的情况
  if(walkaddr(p->pagetable, *ip) == 0) {
    if(PGROUNDUP(p->trapframe->sp) - 1 < *ip && *ip < p->sz) {
      char* pa = kalloc();
      if(pa == 0)
        return -1;
      memset(pa, 0, PGSIZE);

      if(mappages(p->pagetable, PGROUNDDOWN(*ip), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) {
        kfree(pa);
        return -1;
      }
    } else {
      return -1;
    }
  }

  return 0;
}