2021 XV6 10:mmap

实验要求实现mmap和munmap的系统调用,这两个系统调用是把文件映射到内存,即内存映射文件。

实现内存映射文件的作用在于,我们一个进程在对文件进行大量读写的时候可以避免大量的I/O操作,因为文件在内存里面,所以我们对内存进行读写即可。

首先是系统调用的添加。

然后是实现sys_mmap,给每个进程开一个16个vma的数组:

// virtual memory area
struct vma
{
  int valid;
  uint64 vm_start;// 虚拟地址
  uint64 vm_end;// 虚拟地址
  int length;// 地址长度
  int prot;// 操作文件权限
  int flags;
  struct file *fd;// 映射文件
  int offset;// 从文件offset处开始映射
};

之后完成mmap,把文件映射到内存中,这里分配虚拟地址的时候从高地址往下分配,考虑到sbrk也要分配内存,从低地址往高地址分配,所以我们反过来:

这里的MMAPEND是MAXVA减去两个PGSIZE得到的,也就是TRAMPOLINE和TRAPFRAME,下次分配页面的时候从最低地址开始分配,这样不可避免有外部碎片。

uint64
sys_mmap(void){
  uint64 addr;
  int length;
  int prot;
  int flags;
  int offset;
  int int_fd;
  struct file *fd;
  // 获取参数
  if (argaddr(0,&addr)<0 || argint(1,&length)<0 || argint(2,&prot)<0 || 
  argint(3,&flags)<0 || argfd(4,&int_fd,&fd)<0 || argint(5,&offset)<0)
    return -1;

  // 只读文件 映射为内存可写以及写回标记
  if (!fd->writable && (prot&PROT_WRITE) && flags==MAP_SHARED)
    return -1;
  
  struct proc *p=myproc();
  struct vma *tmp=0;
  uint64 va_end=MMAPEND;
  for(int i = 0; i < MAXVMA; i++){
    if (p->p_vma[i].valid==0){
      if (tmp==0){
        tmp=&p->p_vma[i];
      }
    }else if(va_end>p->p_vma[i].vm_start){
      va_end=PGROUNDDOWN(p->p_vma[i].vm_start);
    }
  }

  if (!tmp)
    return -1;
  

  tmp->valid=1;
  tmp->vm_start=va_end-length;
  tmp->vm_end=va_end;
  tmp->length=length;
  tmp->prot=prot;
  tmp->flags=flags;
  tmp->fd=fd;
  tmp->offset=offset;
  filedup(fd);
  // printf("valid:%d, va_start:%d, va_end:%d\n",p->p_vma[0].valid, p->p_vma[0].vm_start,p->p_vma[0].vm_end);
  return tmp->vm_start;
}

然后由于是懒加载,我们在trap.c里面增加对缺页的处理,处理流程就是找到对应的vma,分配一个物理页,把文件内容读到物理页,再将虚拟地址和物理页建立映射:

else if(r_scause()==13 || r_scause()==15){
    uint64 va=r_stval();
    for (int i = 0; i < MAXVMA; i++){
      if (p->p_vma[i].valid && p->p_vma[i].vm_start<=va && va<p->p_vma[i].vm_end){
        // 分配一个物理页
        uint64 pa=(uint64)kalloc();
        if (pa==0){
          p->killed=1;
          kfree((void*)pa);
          break;
        }
        memset((void *)pa,0,PGSIZE);
        // 从文件读内容至物理页中
        struct inode *ip=p->p_vma[i].fd->ip;
        uint off=va-p->p_vma[i].vm_start;
        // printf("off:%d\n",off);
        ilock(ip);
        readi(ip,0,pa,off,PGSIZE);
        iunlock(ip);
        // 关联虚拟地址和物理页
        int flags=PTE_U;
        if (p->p_vma[i].prot&PROT_READ) flags|=PTE_R;
        if (p->p_vma[i].prot&PROT_WRITE) flags|=PTE_W;
        if (p->p_vma[i].prot&PROT_EXEC) flags|=PTE_X;

        if (mappages(p->pagetable,PGROUNDDOWN(va),PGSIZE,pa,flags)<0){
          p->killed=1;
          kfree((void*)pa);
          break;
        }

        break;
      }
    }
  }

接着实现sys_unmap,主要就是,有MAP_SHARED把物理内存的内容写回缓冲块(缓冲块由日志文件系统自动写回),减少vma的区域范围,如果存在映射把映射给释放了,如果长度是0了还要把文件关闭(也就是把inode的块映射全部删了):

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

  struct proc *p=myproc();
  for (int i = 0; i < MAXVMA; i++){
    if (p->p_vma[i].valid && (addr==p->p_vma[i].vm_start || addr+length==p->p_vma[i].length)){
      if (addr==p->p_vma[i].vm_start)
        p->p_vma[i].vm_start+=length;
      p->p_vma[i].length-=length;
      // 文件内容从用户物理内存写回缓冲块
      struct file *fd=p->p_vma[i].fd;
      if (p->p_vma[i].flags&MAP_SHARED)
        filewrite(fd,addr,length);
      // 释放虚拟地址和物理内存的映射
      uint64 npages=(PGROUNDUP(addr+length)-PGROUNDDOWN(addr))/PGSIZE;
      // printf("npages:%d,start:%x,end:%x,walk:%d\n",npages,addr,addr+length,walkaddr(p->pagetable,addr));
      if (walkaddr(p->pagetable,addr))
        uvmunmap(p->pagetable,PGROUNDDOWN(addr),npages,1);
      
      if (p->p_vma[i].length==0){
        fileclose(fd);
        p->p_vma[i].valid=0;
      }
      return 0;
    }
  }
  return -1;
}

在allocproc里面要把vma的valid清0,fork里边注意要增加文件引用数,在exit里边要清空整个vma区域,前两个很简单,这里给出exit中的实现:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值