MIT xv6 2020系列实验:Lab10 mmap

实现两个功能:分别是mmap与munmap,将文件映射到内存当中,并为一个线程记录他管理的文件所在的页表目录。

函数原型如下:

char* mmap( char* addr, int len, int prot, int flags,int fd, int off);
int munmap( char* addr , int len );

其中mmap参数含义分别是映射地址(为0时由内核代码决定),映射内存长度,映射文件权限(read/write),映射类型(SHARED/PRIVATE,为SHARED则将文件写入到物存中),文件描述符,偏移位置。

为了实现映射,对于一个进程我们当然需要分配一块内存区域出来供mmap使用。这里我的思路是以文件为单元对物理内存区域进行记录,因为一个进程可以打开多个文件,所以要建立一个文件结构体数组用来表示已经映射的内存区域片:

 #define VMA_MAX 16
 struct VMA{
   int valid;
   uint64 addr;
   int len;
   int prot;
   int flags;
   int off;
   struct file* f;
   uint64 mapcnt;
 };
 ​
 struct proc {
 //...
   struct VMA vma[VMA_MAX];     // virtual memory address field arr
 //...
 };

至于内存分配策略,需要考虑是从堆区向下开始分还是栈区向上。

如果是从栈区开始,那么就是和sbrk绑定在一起,但这一部分有懒空间分配,所以从堆区,heap处向下分配。

对于sys_mmap,直接找到一个当前进程未使用的位置进行进程赋值。对于分配空间,直接从maxaddr向下分配。

uint64 
sys_mmap(void){
  uint64 addr;
   int len , prot , flags , fd , off;
   if( argaddr( 0 , &addr ) < 0 || argint( 1 , &len ) < 0 || argint( 2 , &prot ) < 0 || argint( 3 , &flags ) < 0 || argint( 4 , &fd ) < 0 || argint( 5 , &off ) < 0 )
     return -1;
   struct proc* p = myproc();
   struct file* f = p->ofile[fd];

   
   // to ensure the prot
   if( ( flags == MAP_SHARED && f->writable == 0 && (prot&PROT_WRITE)) )
     return -1;
  
   // to find a empty vma and init it
   int idx;
   for( idx = 0 ; idx < VMA_MAX ; idx++ )
     if( p->vma[idx].valid == 0 )
       break;
   if( idx == VMA_MAX )
     panic("no empty vma field");
   
   struct VMA* vp = &p->vma[idx];
   vp->valid = 1;
   vp->len = len;
   vp->flags = flags;
   vp->off = off;
   vp->prot = prot;
   vp->f = f;
   filedup( f );  //increase the ref of the file
   vp->addr = (p->maxaddr-=len);  // asign a useable virtual address to the vma field , and maintain the maxaddr
   return vp->addr;
}

对于sys_unmap,如果页表发生了修改,就进行写回操作。

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);
      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中实现缺页,这里采用逐页分配,也即读到一处mmap也只分配相应那一页,不分配文件的所有页。


else if( r_scause() == 0xd )
   {
     uint64 addr = r_stval();
     struct VMA* vp = 0;
     //to fina the target vma
     for( int i=0 ; i<VMA_MAX ; i++ )
       if( p->vma[i].addr <= addr && addr < p->vma[i].addr + p->vma[i].len && p->vma[i].valid == 1 )
       {
         vp = &p->vma[i];
         break;
       }
     if( vp != 0 )
     {
       uint64 mem = (uint64)kalloc();
       memset( (void*)mem , 0 , PGSIZE );
       if( -1 ==  mappages( p->pagetable , addr,  PGSIZE , mem , PTE_U | PTE_V | ( vp->prot << 1 ) ) )
         panic("pagefault map error");
       
       vp->mapcnt += PGSIZE; //maintain the mapcnt
       ilock( vp->f->ip );
       readi( vp->f->ip , 0 , mem , addr-vp->addr , PGSIZE); //copy a page of the file from the disk
       iunlock( vp->f->ip );
     }
     else
     {
       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;
     }
   }

根据提示修改exit与fork两处系统调用:

修改完毕,测试通过。

posted @   Thaudmin  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示