MIT 6.S081 lab 10:Mmap

Lab: mmap

mmapmunmap系统调用允许UNIX程序对地址空间进行细节控制。这可以被用来在多进程之间分享内存,把文件映射到进程地址空间,还有作为一部分用户层页错方案,比如在讲座中谈到的垃圾回收算法。在这个实验中,你将会给xv6添加mmapmunmap到xv6,关注于内存映射文件。

mmap能够用于很多场景,但是这个实验只需要实现文件内存映射的相关特性即可。你可以假设addr一直是0,意味着内核应当决定在什么虚拟地址映射这个文件。如果失败,mmap会返回 0xffffffffffffffff。length是需要映射的字节数;这可能不会和文件长度一样大。prot表示了该内存的权限。你可以假设prot是PROT_READ或者PROT_WRITE或者都有。flags可以是MAP_SHARED,这意味着这个修改应当被写回文件,或者MAP_PRIVATE,意味着不会写回。你不需在flags中实现任何其他位。fd是一个将要映射未见的打开的文件描述符。你可以假设offset是0(文件开始映射的位置)

映射相同MAP_SHARED文件的进程可以不使用相同的物理页。

*munmap(addr,length)应当移除mmap的映射。如果进程已经修改了内存,并且都是MAP_SHARED,这个修改应当先写入到文件。一个munmap调用可能会只覆盖映射过的部分区域,但你可以假设它要么在开始时取消映射,要么在结束时取消映射,或者整个区域(但请不要在中间取消映射)。

你应当你应当实现足够的mmap和munmap的特性,来通过mmaptest的测试程序。如果mmaptestmmaptest不需要使用到某个mmap的特性,你可以不实现。

当你完成之后,你应当看到以下的输出:

$ mmaptest
mmap_test starting
test mmap f
test mmap f: OK
test mmap private
test mmap private: OK
test mmap read-only
test mmap read-only: OK
test mmap read/write
test mmap read/write: OK
test mmap dirty
test mmap dirty: OK
test not-mapped unmap
test not-mapped unmap: OK
test mmap two files
test mmap two files: OK
mmap_test: ALL OK
fork_test starting
fork_test OK
mmaptest: all tests succeeded
$ usertests
usertests starting
...
ALL TESTS PASSED
$ 

一些建议:

  • 为了使得user/mmaptest.c能够编译,先添加_mmaptest到UPROGS,mmap和munmap系统调用。现在,只需要在mmap和munmap中返回错误。我们已经在kernel/fcntl.h中定义了PROT_READ等。允许mmaptest,这会在mmap调用中返回失败。
  • 在page fault的时候,再进行页分配。这一位置,mmap不应当分配物理内存或者读取文件。而且,需要在发生page fault时,在usertrap中进行处理。lazy allocation的原因是确保mmap大文件是迅速的,且mmap的文件可以比物理内存大。
  • 追踪mmap位每个进程映射的部分。定义一个在讲座15中描述的于VMA相关的结构,记录mmap创建的虚拟地址范围的地址,长度,许可权限,以及文件等。因为xv6内核中没有一个内存分配器,可以通过声明一个固定长度的VMAs并且从这个数组中进行分配。16大小以及足够。
  • 实现mmap:找到一个未使用的虚拟地址范围来映射,并且增加一个VMA到进程映射区域的部分**?????**足够VMA应当包含一个指向映射的struct file的指针。mmap樱花当增加文件的引用计数,因此当文件关闭的时候(提示:看fileduip)这个结构不会消失。允许mmaptest:第一个mmap会成功,但是第一次获取映射的内存会造成page fault并且kill mmaptest。
  • 增加代码,该代码用于处理映射空间的page fault时分配页,读入4096字节的相关文件内容到页中,并且把它映射到用户空间。用readi读取文件,这需要用一个偏移参数来读取文件(但是你将必须lock/unlock传入的inode)。不要忘记在页表中设置正确的权限位。允许mmaptest;这会到第一个munmap。
  • 实现munmap:找到地址范围内的VMA,并且取消对相应页的映射(提示:使用uvmummap)。如果munmap移除了之前一次映射的所有的页,那么就要降低相关struct file的引用计数。如果一个未映射的页面被修改,并且该文件被映射为MAP_SHARED,则将该页面写回该文件。查看filewrite寻找灵感。
  • 理想情况下,你的实现只能在程序修改了MAP_SHARED页的时候才写回。RISC-V PTE中的脏位说明了是否一个页被写回。然而,mmaptest不会检查非脏页是否被写回;因此你可以在写回时,不去管脏位。
  • 修改exit来取消映射,就像munmap被调用那样。允许mmaptest;mmap_test应当会通过,但是fork_test可能不会通过。
  • 修改fork来确保子程序拥有相同的映射区域。不要忘记增加VMA的struct file的计数。在page fault时,子程序可以自己分配独自的物理空间,即使继承的是一个MAP_SHARED。允许mmaptest,它应当通过mmap_test和fork_test。
  • 允许usertests确保所有东西都运行正常。

添加struct VMA

struct VMA{
    void *add;
    size_t length;
    int perm;
    struct file* f;
}

实现sys_mmap

先获取参数,并且增加文件的引用计数。

然后找到空闲VMA,写入VMA的信息。

然后就是扩容。

想法一:因为在扩容前,sz的大小并不等于已分配的全部页的总和,可能会小于,但是这少的一部分其实已经被分配了。因为我不了解,如果访问到这块内存的话,是否会报page fault,而且即使报了page fault,在trap里面也不知道如何去辨别它。所以最后决定在mmap时,就把内容写入到内存中,因为是已分配页,所以说并不会导致内存分配。显然这种方法会在munmap时候非常麻烦。

想法二:然后想到好像文档里说了分配的内存大小可以不等于文件的大小。那么起始点就从一个新的页开始,结束点在一个独占的页。然后想munmap是否会处理非整页的内存范围,看了测试文件,munmap的长度都是页的倍数,起始点也是页的起始位置。

添加sys_mmap()

注意当flags是MAP_SHARED时,打开文件的模式不能是只读的。

uint64 sys_mmap(void){
    uint64 addr;
    int prot,flags,fd;
    struct file *f;
    uint len,off;

    struct proc* p = myproc();

    if(argaddr(0, &addr) < 0 || argint(1,(int *)&len) < 0 || argint(2,&prot) < 0 || argint(3,&flags) < 0 || argfd(4,&fd,&f) < 0 || argint(5,(int *)&off) < 0)
        return -1;
    filedup(f);

    struct VMA *v = findEmptyVMA(p);
    // No available VMA slot.
    if(!v)
        return -1;


    v->avai = 1;
    v->f = f;
    v->addr = PGROUNDUP((uint64)p -> sz);
    v->perm = prot;
    v->flags = flags;
    v->off = off;
    v->length = len;

    p->sz = (uint64 )v->addr + PGROUNDUP((uint64)len);

    return (uint64)v->addr;
}

修改trap.c

跟之前的Lazy alloc类似,不过要加上从文件系统读入数据到内存的部分。

else {
      uint64 va = r_stval();
      uint64 sp = p->trapframe->sp;
      // not a page fault
      if((r_scause() != 13 && r_scause() != 15) || va >= p->sz || (va < PGROUNDDOWN(sp) && va >= PGROUNDDOWN(sp - PGSIZE))){
          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;
      }
      else{
          va = PGROUNDDOWN(va + 1);
          char* mem;
          // if kalloc fails,kill the proc
          if((mem = kalloc()) == 0){
              p->killed = 1;
          }
          else{
              memset(mem,0,PGSIZE);
              struct VMA* vma = findVMA(va);
              if((mappages(p->pagetable, va, PGSIZE, (uint64)mem,PTE_W | PTE_R | PTE_X |PTE_U|PTE_V )) != 0){
                  kfree(mem);
                  // if fails,unmap and free the page
                  uvmunmap(p->pagetable,va,1,0);
              }

              uint readsz = PGSIZE;
              if(va + PGSIZE > vma->addr + (uint64)vma->length){
                  readsz = vma->addr + (uint64)vma->length - va;
              }
              struct file *f = vma->f;
//              printf("readsz %p\n",readsz);

              ilock(f->ip);
              readi(f->ip,1,va,vma->off + (uint)(va - vma->addr),readsz);
              iunlock(f->ip);



          }
      }
  }

添加sys_munmap

因为测试文件中的munmap区域都是从头开始的整页的操作,所以针对case实现还是比较简单的。

先判断是否是取消全部映射,如果是的话,就要修改VMA以及减少文件的计数。

如果flags是MAP_SHARED,且该页已分配,那么需要把其中的内容写入到文件系统。

如果该页已分配,则清空内容。

清除内存部分是只是清空内容,并不释放页。释放页会导致中空。

最后修改VMA的信息。

uint64 sys_munmap(void){
    uint64 addr;
    uint len;
    if(argaddr(0, &addr) < 0 || argint(1,(int*)&len) <0)
        return -1;

    struct VMA* vma = findVMA(addr);

    // munmap all region
    if(addr + (uint64 )len >= vma->addr + (uint64 )vma->length){
        if(vma->flags == MAP_SHARED){
            begin_op();
            ilock(vma->f->ip);
            writei(vma->f->ip,1,vma->addr,vma->off,vma->length);
            iunlock(vma->f->ip);
            end_op();
        }

        // When the page already allocated,free it.
        for(int i = 0;i < PGROUNDUP(len)/PGSIZE;i++){
            if(walkaddr(myproc()->pagetable,vma->addr + i * PGSIZE)){
                uvmunmap(myproc()->pagetable,vma->addr + i * PGSIZE, 1,0);
            }

        }

        fileclose(vma->f);

        vma->avai = 0;
    }else{
        if(vma->flags == MAP_SHARED){
            begin_op();
            ilock(vma->f->ip);
            writei(vma->f->ip,1,vma->addr,vma->off,len);
            iunlock(vma->f->ip);
            end_op();

        }
        // When the page already allocated,free it.
        for(int i = 0;i < PGROUNDUP(len)/PGSIZE;i++){
            if(walkaddr(myproc()->pagetable,vma->addr + i * PGSIZE)){
                uvmunmap(myproc()->pagetable,vma->addr + i * PGSIZE, 1,0);
            }

        }

        vma->addr   = vma->addr + len;
        vma->off    = vma->off  + len;
        vma->length = vma->length - len;
    }
    return 0;
}

修改exit

进程退出需要把映射全部取消。

逻辑和sys_munmap类似。

  // Munmap all mmap-ed region.
  struct VMA* vma = p->Vlist;
  for(int i = 0;i < 16;i++){
      if(vma->avai){
          for(int i = 0;i < PGROUNDUP(vma->length)/PGSIZE;i++){
              // If already allocated,unmap it.
              if(walkaddr(myproc()->pagetable,vma->addr + i * PGSIZE)){
                    if(vma->flags == MAP_SHARED){
                      begin_op();
                      ilock(vma->f->ip);
                      writei(vma->f->ip,1,vma->addr + i * PGSIZE,vma->off + i * PGSIZE,PGSIZE);
                      iunlock(vma->f->ip);
                      end_op();
                  }

                  uvmunmap(myproc()->pagetable,vma->addr + i * PGSIZE, 1,0);
              }

          }
          fileclose(vma->f);
          vma->avai = 0;
      }
      vma++;
  }


修改fork()

fork需要把父进程的Vlist复制到子进程中,并且增加打开文件的引用计数。

  // Copy the Vlist from parent process.
  for(int i = 0;i < 16;i++){
      np->Vlist[i].f = p->Vlist[i].f;
      np->Vlist[i].addr = p->Vlist[i].addr;
      np->Vlist[i].length = p->Vlist[i].length;
      np->Vlist[i].avai = p->Vlist[i].avai;
      np->Vlist[i].flags = p->Vlist[i].flags;
      np->Vlist[i].off = p->Vlist[i].off;
      np->Vlist[i].perm = p->Vlist[i].perm;
      if(np->Vlist[i].avai)
        filedup(np->Vlist[i].f);
  }

修改vm.c的相关内容

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)
      panic("uvmunmap: walk");
    if((*pte & PTE_V) == 0){
//        panic("uvmunmap: not mapped");
        return;
    }

    if(PTE_FLAGS(*pte) == PTE_V)
      panic("uvmunmap: not a leaf");
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    *pte = 0;
  }
}

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");
    if((*pte & PTE_V) == 0)
        return 0;
//      panic("uvmcopy: page not present");
    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;
}

这个lab跟之前的lazy allocation很像,算是一个小变形。所以不会很难。

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值