美文网首页
lab5: lazy copy

lab5: lazy copy

作者: sarto | 来源:发表于2022-06-22 11:04 被阅读0次

    通常,用户程序使用 sbrk 进行内存扩容,这里存在两个问题

    1. 用户频繁调用 sbrk,如果每次都进行内存分配,耗费时间
    2. 用户可能分配了许多,但是并不一定用这么多,导致浪费

    基于这两点,懒分配就变得有必要了,当一个进程进行内存扩容时,内核仅记录其分配的大小,并不实际进行分配,只有当使用的时候,进行内存分配。

    懒分配的流程

    1. 进程请求进行 n 个字节的内存空间扩容
    2. 内核修改该进程的空间大小标识 p->sz += n,不进行实际分配
    3. 进程访问分配的地址
    4. 页表项不存在,触发中断
    5. 中断捕获,通过 s_tval 得知异常地址,对该地址所处的页进行内存分配
    6. 从中断返回,进程重新执行该指令,正常执行

    xv6 懒分配实现

    1. growproc 在扩容这里,取消扩容的实际实现,但是要加上限定条件,如果扩容大小超过最大虚拟地址,则返回错误,同时,为了保证应用程序的合理性,缩容不采用懒分配策略。
    int 
    growproc(int n)
    {
      uint sz;
      struct proc *p = myproc();
    
      sz = p->sz;
      if(n > 0){
        if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
          return -1;
        }
      } else if(n < 0){
        sz = uvmdealloc(p->pagetable, sz, sz + n);
      }
      p->sz = sz;
      return 0;
    }
    
      1 int
    239 growproc(int n)                                                                                                                                                                           
      1 {
      2   uint sz;
      3   struct proc *p = myproc();
      4 
      5   sz = p->sz;
      6   if (n > 0) {
      7       sz = sz + n;
      8       if (sz > MAXVA)
      9           return -1;
     10   } else if (n < 0) {
     11     sz = uvmdealloc(p->pagetable, sz, sz + n);
     12   }
     13   p->sz = sz;
     14   return 0;
     15 }
    
    
    1. usertrap.c 在原来的基础上加上 r_scause == 13 || r_scause == 15的处理逻辑。也就是读写内存地址异常。
     36 void
     35 usertrap(void)
     34 {
     33   int which_dev = 0;
     32 
     31   if((r_sstatus() & SSTATUS_SPP) != 0)
     30     panic("usertrap: not from user mode");
     29 
     28   // send interrupts and exceptions to kerneltrap(),
     27   // since we're now in the kernel.
     26   w_stvec((uint64)kernelvec);
     25 
     24   struct proc *p = myproc();
     23   
     22   // save user program counter.
     21   p->trapframe->epc = r_sepc();
     20   
     19   if(r_scause() == 8){
     18     // system call
     17 
     16     if(p->killed)
     15       exit(-1);
     14 
     13     // sepc points to the ecall instruction,
     12     // but we want to return to the next instruction.
     11     p->trapframe->epc += 4;
     10 
      9     // an interrupt will change sstatus &c registers,
      8     // so don't enable until done with those registers.
      7     intr_on();
      6 
      5     syscall();
      4   } else if((which_dev = devintr()) != 0){
      3     // ok
      2   } else if(r_scause() == 13 || r_scause() == 15) {
      1       uint64 va = PGROUNDDOWN(r_stval());
    72        if (va >= p->sz) {                                                                                                                                                                  
      1           //printf("invalid address: %d\n", va);
      2           p->killed = 1;
      3           goto err;
      4       }
      5       uint64 pa = (uint64)kalloc();
      6       mappages(p->pagetable, va, PGSIZE, pa, PTE_V|PTE_R|PTE_W|PTE_U);
      7   } else {
      8     printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
      9     printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
     10     p->killed = 1;
     11   }
    
    1. vm.c 关于 p->sz 含义更改的一些处理
      以前 p->sz 代表进程实际占用的地址空间大小,而现在意味着这些地址可能未实际分配,含义的更改导致一些使用该变量的函数需要作出相应的改变。
      uvmunmap 将在释放进程时调用,其释放虚拟地址的范围是 p->sz,但是 p->sz 存在一些未映射的部分,所以当这些地址未映射时,不进行释放,直接跳过即可。
     14 void
     13 uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
     12 {
     11   uint64 a;
     10   pte_t *pte;
      9 
      8   if((va % PGSIZE) != 0)
      7     panic("uvmunmap: not aligned");
      6 
      5   for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
      4     if((pte = walk(pagetable, a, 0)) == 0)
      3         continue;
      2     if((*pte & PTE_V) == 0)
      1         continue;
    187     if(PTE_FLAGS(*pte) == PTE_V)                                                                                                                                                          
      1       panic("uvmunmap: not a leaf");
      2     if(do_free){
      3       uint64 pa = PTE2PA(*pte);
      4       kfree((void*)pa);
      5     }
      6     *pte = 0;
      7   }
      8 }
    
    

    uvmcopy 在进程 fork 时使用,拷贝父进程的地址空间,而父进程的虚拟地址空间为 p->sz。所以当这些地址空间有一些未实际映射时,直接跳过。

     34 int
     33 uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
     32 {
     31   pte_t *pte;
     30   uint64 pa, i;
     29   uint flags;
     28   char *mem;
     27 
     26   for(i = 0; i < sz; i += PGSIZE){
     25     if((pte = walk(old, i, 0)) == 0)
     24         continue;
     23     if((*pte & PTE_V) == 0)
     22         continue;
     21     pa = PTE2PA(*pte);
     20     flags = PTE_FLAGS(*pte);
     19     if((mem = kalloc()) == 0)
     18       goto err;
     17     memmove(mem, (char*)pa, PGSIZE);
     16     if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
     15       kfree(mem);
     14       goto err;
     13     }
     12   }
     11   return 0;
     10 
      9  err:
      8   uvmunmap(new, 0, i / PGSIZE, 1);
      7   return -1;
      6 }
    
    

    具体代码记录
    https://github.com/merore/xv6-labs-2020/commit/a247d13e5287ae71abb708decb6af872b218a5da

    相关文章

      网友评论

          本文标题:lab5: lazy copy

          本文链接:https://www.haomeiwen.com/subject/ojxxqrtx.html