xv6-lab10-mmap

这篇博客详细介绍了操作系统中内存映射(mmap)和取消映射(munmap)的实现过程,包括VMA数据结构的定义、mmap系统调用的新增、munmap系统调用的实现以及在缺页中断处理和进程复制(fork)时如何处理映射信息。实验内容涉及文件映射、缺页中断处理和进程退出时的资源清理,确保了内存管理的正确性和效率。
摘要由CSDN通过智能技术生成

Lab: mmap

实验目标

在实验环境中实现 mmap() 系统调用

hints

  • char* mmap(void *addr, int length, int prot, int flags,
               int fd, int offset);
    int munmap(void *addr, int length); 
    
  • mmap() 映射的页面应该是 lazy alloc 的,以保证在映射大文件时不会阻塞

  • 每个进程应保持对 mmap() 映射的记录。创建一个符合 VMA 要求的结构体来保存它。每个进程的结构体数组应为16。

  • mmap() 的工作:在用户空间地址中寻找未映射的区域来映射文件,并用在进程信息中用结构体保存该映射的信息。该结构体应该保存指向被映射文件struct file 的指针,并且应该增加文件的引用次数

  • 处理缺页中断。在缺页中断中读入 对应位置的4096字节 文件放入内存并映射

  • munmap() 的工作:取消特定页面的映射,并将MAP_SHARED页面写回磁盘。如果一个映射被完全取消,记得减少相对应的文件引用

  • 你可以假设 munmap() 只从区域开头、结尾,或从中间开始到结尾的一整段开始,不会在中间打个洞这样子

  • 在本实验中不用考虑是否为脏页

  • 修改 exit() 来保证退出前所有的 mmap 已经取消

  • 修改 fork() 来确保子进程也拥有正确的映射

实验实现

看过提示我们要做的工作大概有如下几点:

  • 新增VMA数据结构,在struct proc 中新增VMA数据来保存 mmap() 信息
/*	proc.h	*/

struct map_unit
{
  int used;
  uint64 addr;
  int length;
  int prot;
  int flags;
  int fd;
  struct file *mfile;
  int offset;
};
// Per-process state
struct proc {
  ......
  struct map_unit map_addr[MAXMMAP];  // Trace map
};

  • 新增mmap() 的系统调用,完成相对应的工作
/*	sysfile.c	*/
uint64
sys_mmap(void)
{
  uint64 addr;
  int length;
  int prot;
  int flags;
  int fd;
  struct file *mfile;
  int offset;
  uint64 mmap_error = 0xffffffffffffffff;

  if(argaddr(0, &addr) || argint(1, &length) || argint(2, &prot) || argint(3, &flags) || argfd(4, &fd, &mfile) || argint(5, &offset))
  {
    return mmap_error;
  }

  if(!mfile->writable && (prot & PROT_WRITE) && (flags == MAP_SHARED))
    return mmap_error;


  // printf("kernel flag : %d\n", fd);

  struct proc *p = myproc();

  if(p->sz > MAXVA - length)
    return mmap_error;

  for(int i = 0; i < MAXMMAP; ++i)
  {
    if(p->map_addr[i].used == 0)
    {
      p->map_addr[i].used = 1;
      p->map_addr[i].addr = p->sz;
      p->map_addr[i].length = length;
      p->map_addr[i].prot = prot;
      p->map_addr[i].flags = flags;
      p->map_addr[i].fd = fd;
      p->map_addr[i].mfile = mfile;
      p->map_addr[i].offset = offset;

      filedup(mfile);
      p->sz = p->sz + length;

      
      return p->map_addr[i].addr;
    }
  }

  return mmap_error;
}

  • 新增munmap() 的系统调用,完成相对应的工作
/*	sysfile.c	*/
uint64
sys_munmap(void)
{
  uint64 addr;
  int length;
  if(argaddr(0, &addr) || argint(1, &length))
    return -1;

  struct proc *p = myproc();

  for(int i = 0; i < MAXMMAP; ++i)
  {
    if((p->map_addr[i].addr == addr) || (p->map_addr[i].addr + p->map_addr[i].length == addr + length))
    {
      if(p->map_addr[i].addr == addr) p->map_addr[i].addr += length;
      p->map_addr[i].length -= length;
      if((p->map_addr[i].flags & MAP_SHARED) && (p->map_addr[i].prot & PROT_WRITE))
        filewrite(p->map_addr[i].mfile, addr, length);
/*
 *这里没考虑addr是否页对齐,居然也过了,说明条件十分宽松了
 *给来的地址都是页对齐的
 */
      uvmunmap(p->pagetable, addr, length/PGSIZE, 1);

      if(p->map_addr[i].length == 0)
      {
          
/*
 *
 *这里写文件没有考虑偏移量也能过!
 *十分不合理但也懒得完善嘿嘿
 *
 */
        fileclose(p->map_addr[i].mfile);
        p->map_addr[i].used = 0;
      }
      break;
    }
  }
  return 0;
}

  • 修改 usertrap() 来处理缺页,要注意一个文件很大,由很多页组成,我们要根据缺页页地址VMA结构 里保存的虚拟地址的偏移量,来找到我们需要读入的部分
/*	trap.c	*/
void
usertrap(void)
{
......
  if(temp == 8)
  {
......
  }
  else if((which_dev = devintr()) != 0)
  {
......
  }
  else if (temp == 13 || temp == 15)
  {
    uint64 va = r_stval();
    
    if(va >= p->sz || PGROUNDUP(va) == PGROUNDDOWN(p->trapframe->sp)) p->killed = 1;
    else
    {
      
      for(int i = 0; i < MAXMMAP; ++i)
      {
        
        if(p->map_addr[i].used && p->map_addr[i].addr <= va && va < (p->map_addr[i].addr + p->map_addr[i].length))
        {

          uint64 pa = (uint64)kalloc();
          if(pa == 0)
          {
            p->killed = 1;
            break;
          }
          memset((void *)pa, 0, PGSIZE);

          struct file *mfile = p->map_addr[i].mfile;

          ilock(mfile->ip);
          
            
          //计算要读取文件页面离文件开头的偏移量 
          int offset = PGROUNDDOWN(va - p->map_addr[i].addr);
          int cnt = readi(mfile->ip, 0, pa, offset, PGSIZE);

          if(cnt == 0)
          {
            iunlock(mfile->ip);
            kfree((void *)pa);
            p->killed = 1;
            break;
          }

          iunlock(mfile->ip);

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

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

  }
  else 
......
}
  • 修改 fork() 来保证子进程拥有对父进程 mmap() 的记录
/*	proc.c	*/
int
fork(void)
{
......
  for(int i = 0; i < MAXMMAP; ++i)
  {
    if(p->map_addr[i].used)
    {
      memmove(&(np->map_addr[i]), &(p->map_addr[i]), sizeof(p->map_addr[i]));
      filedup(p->map_addr[i].mfile);
    }
  }

  np->state = RUNNABLE;
......
}

  • 修改 exit() 来取消所有映射
 /*	proc.c	*/
void
exit(int status)
{
......

  for(int i = 0; i < MAXMMAP; ++i)
  {
    if(p->map_addr[i].used)
    {
      if((p->map_addr[i].flags & MAP_SHARED) && (p->map_addr[i].prot & PROT_WRITE))
        filewrite(p->map_addr[i].mfile, p->map_addr[i].addr, p->map_addr[i].length);
      fileclose(p->map_addr[i].mfile);
      uvmunmap(p->pagetable, p->map_addr[i].addr,  p->map_addr[i].length/PGSIZE, 1);
      p->map_addr[i].used = 0;
    }
  }
......
}

实验结果

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值