【mit 6.S081】学习笔记 Lab10 Mmap

Lab10 Mmap

实现内存映射文件的操作,将文件映射到内存中,从而在与文件交互的时候减少磁盘操作。

添加系统调用

配置mmapmunmap系统调用,这里不再赘述。

向进程添加虚拟内存区域
#define NVMA 16
// 虚拟内存区域结构体
struct vm_area
{
  int used;           // 是否已被使用
  uint64 addr;        // 起始地址
  int len;            // 长度
  int prot;           // 权限
  int flags;          // 标志位
  int vfd;            // 对应的文件描述符
  struct file *vfile; // 对应文件
  int offset;         // 文件偏移,本实验中一直为0
};
// Per-process state
struct proc
{
  ......
  struct vm_area vma[NVMA];
};
static struct proc *
allocproc(void)
{
  ......
  memset(&p->vma, 0, sizeof(p->vma)); // 初始化
  return p;
}
实现映射关系
uint64
sys_mmap(void)
{
  uint64 addr;
  int length;
  int prot;
  int flags;
  int vfd;
  struct file *vfile;
  int offset;
  uint64 err = 0xffffffffffffffff;

  // 获取系统调用参数
  if (argaddr(0, &addr) < 0 || argint(1, &length) < 0 || argint(2, &prot) < 0 ||
      argint(3, &flags) < 0 || argfd(4, &vfd, &vfile) < 0 || argint(5, &offset) < 0)
    return err;

  // 实验提示中假定addr和offset为0,简化程序可能发生的情况
  if (addr != 0 || offset != 0 || length < 0)
    return err;

  // 文件不可写则不允许拥有PROT_WRITE权限时映射为MAP_SHARED
  if (vfile->writable == 0 && (prot & PROT_WRITE) != 0 && flags == MAP_SHARED)
    return err;

  struct proc *p = myproc();
  // 没有足够的虚拟地址空间
  if (p->sz + length > MAXVA)
    return err;

  // 遍历查找未使用的VMA结构体
  for (int i = 0; i < NVMA; ++i)
  {
    if (p->vma[i].used == 0)
    {
      p->vma[i].used = 1;
      p->vma[i].addr = p->sz;
      p->vma[i].len = length;
      p->vma[i].flags = flags;
      p->vma[i].prot = prot;
      p->vma[i].vfile = vfile;
      p->vma[i].vfd = vfd;
      p->vma[i].offset = offset;

      // 增加文件的引用计数
      filedup(vfile);

      p->sz += length;
      return p->vma[i].addr;
    }
  }

  return err;
}
识别页错误
void usertrap(void)
{
  ......
  else if ((which_dev = devintr()) != 0)
  {
    // ok
  }
  else if (r_scause() == 13 || r_scause() == 15)
  {
#ifdef LAB_MMAP
    // 读取产生页面故障的虚拟地址,并判断是否位于有效区间
    uint64 fault_va = r_stval();
    if (PGROUNDUP(p->trapframe->sp) - 1 < fault_va && fault_va < p->sz)
    {
      if (mmap_handler(r_stval(), r_scause()) != 0)
        p->killed = 1;
    }
    else
      p->killed = 1;
#endif
  }
  ......
  usertrapret();
}

int mmap_handler(int va, uint64 cause)
{
  int i;
  struct proc *p = myproc();
  // 根据地址查找属于哪一个VMA
  for (i = 0; i < NVMA; ++i)
  {
    if (p->vma[i].used && p->vma[i].addr <= va && va <= p->vma[i].addr + p->vma[i].len - 1)
    {
      break;
    }
  }
  if (i == NVMA)
    return -1;

  int pte_flags = PTE_U;
  if (p->vma[i].prot & PROT_READ)
    pte_flags |= PTE_R;
  if (p->vma[i].prot & PROT_WRITE)
    pte_flags |= PTE_W;
  if (p->vma[i].prot & PROT_EXEC)
    pte_flags |= PTE_X;

  struct file *vf = p->vma[i].vfile;
  // 读导致的页面错误
  if (cause == 13 && vf->readable == 0)
    return -1;
  // 写导致的页面错误
  if (cause == 15 && vf->writable == 0)
    return -1;

  void *pa = kalloc();
  if (pa == 0)
    return -1;
  memset(pa, 0, PGSIZE);

  // 读取文件内容
  ilock(vf->ip);
  // 计算当前页面读取文件的偏移量,实验中p->vma[i].offset总是0
  // 要按顺序读读取,例如内存页面A,B和文件块a,b
  // 则A读取a,B读取b,而不能A读取b,B读取a
  int offset = p->vma[i].offset + PGROUNDDOWN(va - p->vma[i].addr);
  int readbytes = readi(vf->ip, 0, (uint64)pa, offset, PGSIZE);
  // 什么都没有读到
  if (readbytes == 0)
  {
    iunlock(vf->ip);
    kfree(pa);
    return -1;
  }
  iunlock(vf->ip);

  // 添加页面映射
  if (mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)pa, pte_flags) != 0)
  {
    kfree(pa);
    return -1;
  }

  return 0;
}
实现解除映射关系
uint64
sys_munmap(void)
{
  uint64 addr;
  int length;
  if (argaddr(0, &addr) < 0 || argint(1, &length) < 0)
    return -1;

  int i;
  struct proc *p = myproc();
  for (i = 0; i < NVMA; ++i)
  {
    if (p->vma[i].used && p->vma[i].len >= length)
    {
      // 根据提示,munmap的地址范围只能是
      // 1. 起始位置
      if (p->vma[i].addr == addr)
      {
        p->vma[i].addr += length;
        p->vma[i].len -= length;
        break;
      }
      // 2. 结束位置
      if (addr + length == p->vma[i].addr + p->vma[i].len)
      {
        p->vma[i].len -= length;
        break;
      }
    }
  }
  if (i == NVMA)
    return -1;

  // 将MAP_SHARED页面写回文件系统
  if (p->vma[i].flags == MAP_SHARED && (p->vma[i].prot & PROT_WRITE) != 0)
  {
    filewrite(p->vma[i].vfile, addr, length);
  }

  // 判断此页面是否存在映射
  uvmunmap(p->pagetable, addr, length / PGSIZE, 1);

  // 当前VMA中全部映射都被取消
  if (p->vma[i].len == 0)
  {
    fileclose(p->vma[i].vfile);
    p->vma[i].used = 0;
  }

  return 0;
}

如果对惰性分配的页面调用了uvmunmap,或者子进程在fork中调用uvmcopy复制了父进程惰性分配的页面都会导致panic,因此需要修改uvmunmapuvmcopy检查PTE_V后不再panic

if((*pte & PTE_V) == 0)
  continue;
修改exit解除已映射区域
void exit(int status)
{
  struct proc *p = myproc();
  ......
  // 将进程的已映射区域取消映射
  for (int i = 0; i < NVMA; ++i)
  {
    if (p->vma[i].used)
    {
      if (p->vma[i].flags == MAP_SHARED && (p->vma[i].prot & PROT_WRITE) != 0)
      {
        filewrite(p->vma[i].vfile, p->vma[i].addr, p->vma[i].len);
      }
      fileclose(p->vma[i].vfile);
      uvmunmap(p->pagetable, p->vma[i].addr, p->vma[i].len / PGSIZE, 1);
      p->vma[i].used = 0;
    }
  }
  ......
}
修改fork增加引用计数
int fork(void)
{
  ......
  // increment reference counts on open file descriptors.
  for (i = 0; i < NOFILE; i++)
    if (p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);

  // 复制父进程的VMA
  for (i = 0; i < NVMA; ++i)
  {
    if (p->vma[i].used)
    {
      memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
      filedup(p->vma[i].vfile);
    }
  }
  ......
  return pid;
}

所有的问题都解决了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值