前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >6.S081/6.828: 10 Lab mmap

6.S081/6.828: 10 Lab mmap

原创
作者头像
冰寒火
修改2023-02-18 20:11:42
3480
修改2023-02-18 20:11:42
举报
文章被收录于专栏:软件设计软件设计

1 目的

本实验实现mmap和munmap系统调用来更好的控制进程地址空间,可以向数组那样读写文件,写的数据放在buffer cache可以被其他进程所看到。

2 问题分析

代码语言:c
复制
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);

实现内存映射机制需要维护用户地址和文件偏移量的映射关系vma,为了节省空间,采用lazy allocate方式分配,通过page fault来分配物理页。munmap时只能是从左边界开始或者在右边界结束,不能unmap中间部分。

  1. lazy分配,通过page fault来分配物理页,使得物理页少于文件大小时也能够可用;
  2. 定义数据结构VMA来追踪每个进程的mmap,VMA应该包含file;
  3. 增加代码来导致mmaped区域触发page fault,即创建页表项但不分配物理页;page fault时读取4KB文件内容填充物理页并映射到用户空间,通过readi进行读取;
  4. munmap时寻找VMA并unmap这个区域,如果unmap掉mmap映射的所有页,那么就减少对文件的引用。如果unmapped page被修改过且MAP_SHARED,filewrite写回文件;
  5. 修改exit来unmap进程内存映射区域,修改fork来确保子进程会复制mapped区域,需要对文件增加引用;

3 代码实现

3.1 接口定义

MAP_SHARED是进程共享,会将修改的数据写回文件,MAP_PRIVATE不会刷回文件,只在内存中临时保存。PROT_*是读写权限,在分配物理页时会根据这个来设置PTE的flag。vma是维护进程空间和文件偏移的映射关系的,每个进程都有一个vma数组来维护。

代码语言:c
复制
void* mmap(void *addr,int len,int prot,int flags,int fd,int offset);
int munmap(void *addr,int len);

//fcntl.h
#ifdef LAB_MMAP
#define PROT_NONE       0x0
#define PROT_READ       0x1
#define PROT_WRITE      0x2
#define PROT_EXEC       0x4

#define MAP_SHARED      0x01 //刷盘使得其他进程能够看到
#define MAP_PRIVATE     0x02 //修改的数据不刷盘
#endif

//proc.h
struct vma{
  int valid;
  uint64 addr; //user space addr
  uint len; //按页向上取整
  struct file *f;
  uint offset;
  uint flags; //MAP_PRIVATE: 不会更新到磁盘 MAP_SHARED: 会刷盘
  uint prot; //访问权限
};
struct proc{
  //...
  struct vma pvma[MAXVMAPERPROC];
}

然后在user/user.h添加接口;

代码语言:c
复制
void* mmap(void *addr,int len,int prot,int flags,int fd,int offset);
int munmap(void *addr,int len);

再在usys.pl添加entry;

代码语言:c
复制
entry("mmap");
entry("munmap");

最后添加到系统调用表中。

代码语言:c
复制
//syscall.h
#define SYS_mmap   22
#define SYS_munmap  23

//syscall.c
extern uint64 sys_mmap(void);
extern uint64 sys_munmap(void);

[SYS_mmap]   sys_mmap,
[SYS_munmap]   sys_munmap,

3.2 sys_mmap

mmap实现放在sysfile.c中,首先校验参数,如果在MAP_SHARED且文件不可写且mmap可写,那么就error,因为文件可能也同时被其他进程读,会有影响。然后从进程的pvma数组中选择一个空槽位来分配,此时只需要移动sz,不需要真分配物理内存。

代码语言:c
复制
uint64
sys_mmap(void){
  uint64 addr;
  int len,prot,flags,fd,offset;
  if(argaddr(0,&addr)<0 || argint(1,&len)<0 || argint(2,&prot)<0 
     || argint(3,&flags)<0 || argint(4,&fd)<0 || argint(5,&offset)<0 )
    return -1;
  struct proc *p=myproc();
  struct file *f=p->ofile[fd];
  if(f==0)
    return -1;
  if((flags&MAP_SHARED) && !f->writable && (prot & PROT_WRITE))
    return -1;
  //addr默认是0,由内核决定映射到进程空间的位置
  struct vma *pvma=p->pvma;
  for(int i=0;i< MAXVMAPERPROC;i++){
    if(!pvma[i].valid){
      // printf("mmap: file.ref[%d]\n",f->ref);
      pvma[i].f=filedup(f);
      pvma[i].addr=p->sz;
      pvma[i].len=PGROUNDUP(len);
      pvma[i].prot=prot;
      pvma[i].flags=flags;
      pvma[i].offset=offset;
      pvma[i].valid=1;
      p->sz+=len;
      // printf("mmap: ref[%d], inode.ref[%d]\n",f->ref,f->ip->ref);
      return pvma[i].addr;
    }
  } 
  return -1;
}

3.3 page fault handler

mmap时是lazy allocate,读写时会发生缺页中断,需要处理。先从stval寄存器中获取缺页地址,然后扫描proc.pvma数组,找到该地址所在的vma;然后根据分配一页内存并根据相对偏移量从文件指定位置读取一页;最后将这一页map到用户页表上。

代码语言:c
复制
//trap.c
//...
} else if((which_dev = devintr()) != 0){
    // ok
  } else if(r_scause()==13 || r_scause()==15){
    uint64 va=r_stval();
    if(mmap_pgfaulthandler(va)!=0){
      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;
    }
  }
//...

int vm_exists(pagetable_t pagetable, uint64 va){
  pte_t *pte;
  return (pte=walk(pagetable,va,0)) && (*pte & PTE_V);
}
int mmap_pgfaulthandler(uint64 va){
  va=PGROUNDDOWN(va);
  struct vma *a=0;
  //缺页处理
  struct proc *p=myproc();
  struct vma *pvma=p->pvma;
  for(int i=0;i< MAXVMAPERPROC;i++){
    if(p->pvma[i].valid && va>=pvma[i].addr && va<pvma[i].addr+pvma[i].len){
      a=&pvma[i];
      break;
    }
  }       
  if(a==0)
    return -1;

  uint64 pa=(uint64)kalloc();
  if(pa==0)
    return -1;
  memset((void*)pa,0,PGSIZE);
  int flag=PTE_U;
  flag|=a->prot & PROT_READ ? PTE_R:0;
  flag|=a->prot & PROT_WRITE ? PTE_W:0;
  if(mappages(p->pagetable,va,PGSIZE,pa,flag)!=0){
    kfree((void*)pa);
    return -1;
  }
  ilock(a->f->ip);
  printf("trap: va[%p]\n",va);
  if(readi(a->f->ip,0,pa,a->offset+va-a->addr,PGSIZE)<=0){
    iunlock(a->f->ip);
    return -1;
  }
  iunlock(a->f->ip);
  return 0;  
}

3.4 sys_munmap

munmap会释放掉映射的内存,如果是MAP_SHARED还会写回文件中。先从pvma数组找到释放的vma,并决定需要释放左边、右边还是全部,释放左边需要移动vma.addr和vma.offset,释放右边减少vma.len即可;然后逐页判断PTE是否有效,有且MAP_SHARED则释放并写回文件;然后uvmunmap页表项映射;最后,如果整个释放,则关闭文件并将vma.valid=0。

代码语言:c
复制
//sysfile.c
uint64
sys_munmap(void){
  uint64 addr;
  int len;
  if(argaddr(0,&addr)<0 || argint(1,&len)<0)
    return -1;
  return munmap(addr,len);
}

//vm.c
int munmap(uint64 addr,int length){

  struct proc *p = myproc();
  struct vma *a = 0;
  addr = PGROUNDDOWN(addr);

  for(int i = 0; i < MAXVMAPERPROC; i++){
    if(p->pvma[i].valid && addr >= p->pvma[i].addr && addr < p->pvma[i].addr+p->pvma[i].len){
      a = &p->pvma[i];
      break;
    }
  }

  if (a == 0) return -1;

  uint64 unstart, unlen;
  uint64 start = a->addr, offset = a->offset, orilen = a->len;

  if(addr == a->addr){
    // Unmap at the start
    unstart = addr;
    unlen = PGROUNDUP(length) < a->len ? PGROUNDUP(length) : a->len;

    a->addr = unstart + unlen; 
    a->len = start+orilen - a->addr;
    a->offset = a->offset + unlen;
  } else if(addr + length >= start+orilen){
    // Unmap at the end
    unstart = start;
    unlen = start+orilen - unstart;

    a->len = unstart-start;
  } else{
    // Unmap the whole region
    unstart = start;
    unlen = orilen;
  }
  
  for(int i = 0; i < unlen / PGSIZE; i++){
    uint64 va = unstart + i * PGSIZE;
    // May not be alloced due to lazy alloc through page fault.
    if(vm_exists(p->pagetable, va)){
      if(a->flags & MAP_SHARED){
        munmap_writeback(va, PGSIZE, start, offset, a);
      }

      uvmunmap(p->pagetable, va, 1, 1);
    }
  }

  if(unlen == orilen){
    fileclose(a->f);
    a->valid = 0;
  }
  
  return 0;
} 

因为采用了lazy allocate,uvmunmap也需要稍作修改;

代码语言: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)
      continue; //修改处
    if((*pte & PTE_V) == 0)
      continue;
    if(PTE_FLAGS(*pte) == PTE_V)
      continue;
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }
    *pte = 0;
  }
}

释放映射区域需要将数据写回文件,参照filewrite中的逻辑,通过log和inode层的函数写回。

代码语言:c
复制
int
munmap_writeback(uint64 unstart, uint64 unlen, uint64 start, uint64 offset, struct vma *a)
{
  struct file *f = a->f;
  uint off = unstart - start + offset;
  uint size;

  ilock(f->ip);
  size = f->ip->size;
  iunlock(f->ip);

  if(off >= size) return -1;

  uint n = unlen < size - off ? unlen : size - off;

  int r, ret = 0;
  int max = ((MAXOPBLOCKS-1-1-2) / 2) * BSIZE;
  int i = 0;
  while(i < n){
    int n1 = n - i;
    if(n1 > max)
      n1 = max;

    begin_op();
    ilock(f->ip);
    r = writei(f->ip, 1, unstart, off + i, n1);
    iunlock(f->ip);
    end_op();

    if(r != n1){
      // error from writei
      break;
    }
    i += r;
  }
  ret = (i == n ? n : -1);

  return ret;
}

3.5 fork、exit

fork时需要拷贝vma数组,释放时也要重置。

代码语言:c
复制
//proc.c
//fork()
for(int i=0;i< MAXVMAPERPROC;i++){
    if(p->pvma[i].valid){
      np->pvma[i]=p->pvma[i];
      filedup(np->pvma[i].f);
    }
  } 

//exit()
//释放mmap region
  for(int i=0;i< MAXVMAPERPROC;i++){
    if(p->pvma[i].valid){
      munmap(p->pvma[i].addr,p->pvma[i].len);
      p->pvma[i].valid=0;
    }
  } 

4 测试结果

测试结果
测试结果

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 目的
  • 2 问题分析
  • 3 代码实现
    • 3.1 接口定义
      • 3.2 sys_mmap
        • 3.3 page fault handler
          • 3.4 sys_munmap
            • 3.5 fork、exit
            • 4 测试结果
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档