首页 > 其他分享 >【XV6】 mmap

【XV6】 mmap

时间:2024-02-14 19:24:19浏览次数:35  
标签:VMA addr int mmap vma vp XV6

代码:https://github.com/JasenChao/xv6-labs.git

文件映射到进程地址

题目要求实现两个系统调用:mmapmunmap。主要功能就是将文件映射到进程的内存中。

题目给出了mmapmunmap的声明:

void *mmap(void *addr, size_t len, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t len);

其中:

  • addr为映射的地址,在本实验中addr总是0,也就是由操作系统来决定映射的地址
  • length是映射的长度
  • prot对应读写的权限
  • flags为映射的类型,类型为shared时最终需要写回外存
  • fd是文件描述符
  • offset是偏移量,本实验中总是0

进程的虚拟内存

proc.h中定义一个结构体来标记虚拟内存,每个进程都需要有这样的一个结构体数组:

#define VMA_MAX 16
struct VMA{
  int valid;        //有效位,当值为 0 时表示无效,即为 empty element
  uint64 addr;      //记录起始地址
  int len;          //长度
  int prot;         //权限(read/write)
  int flags;        //区域类型(shared/private)
  int off;          //偏移量
  struct file* f;   //映射的文件
  uint64 mapcnt;    //(延迟申请)已经映射的页数量
};

struct proc {
  ...
  struct VMA vma[VMA_MAX];     // VMA
  uint64 maxaddr;              // heap可用最大地址
};

映射的地址从进程空间的高地址向低地址生长,因此增加一个变量maxaddr用来标记heap中可用的地址。

在进程初始化的时候,需要对应地处理新增的这部分内容,在proc.c中的allocproc函数中增加:

  for(int i = 0; i < VMA_MAX; i++){
    p->vma[i].valid = 0;              // 一开始所有的VMA都是无效的
    p->vma[i].mapcnt = 0;             // 一开始映射的数量为0
  }
  p->maxaddr = MAXVA - 2 * PGSIZE;    // 减去已被使用的trampoline和trapframe的空间

系统调用的具体实现

上面提到flagsshared时需要写回,先在file.c中实现写回的函数:

int
filewriteoff(struct file *f, uint64 addr, int n, int off)
{
  int r, ret = 0;

  if(f->writable == 0)
    return -1;

  if(f->type == FD_INODE){
    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);
      if ((r = writei(f->ip, 1, addr + i, off, n1)) > 0)
        off += r;
      iunlock(f->ip);
      end_op();

      if(r != n1){
        // error from writei
        break;
      }
      i += r;
    }
    ret = (i == n ? n : -1);
  } else {
    panic("my filewrite");
  }
  return ret;
}

defs.h中声明:

int             filewriteoff(struct file*, uint64, int n, int off);

sysfile.c中实现具体的系统调用函数:

uint64 sys_mmap(void)
{
  uint64 addr;
  int len , prot , flags , fd , off;
  argaddr(0, &addr);
  argint(1, &len);
  argint(2, &prot);
  argint(3, &flags);
  argint(4, &fd);
  argint(5, &off);
  
  struct proc* p = myproc();
  struct file* f = p->ofile[fd];
  
  // 检查权限和类型,MAP_SHARED类型对文件的修改会写回外存
  if(flags == MAP_SHARED && f->writable == 0 && (prot & PROT_WRITE))
    return -1;

  // 找到一块空的VMA
  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);                       // 保证文件不会被关闭
  vp->addr = (p->maxaddr -= len);   // len长度的内存分配出来,maxaddr减去len
  return vp->addr;
}

uint64 sys_munmap(void)
{
  uint64 addr;
  int len;
  argaddr(0, &addr);
  argint(1, &len);
  struct proc* p = myproc();

  struct VMA* vp = 0;
  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)
    panic("munmap no such vma");  

  // if the page has been mapped 
  if(walkaddr(p->pagetable , addr) != 0){
    // MAP_SHARED类型需要写回
    if(vp->flags == MAP_SHARED)
      filewriteoff(vp->f, addr, len, addr-vp->addr);
    uvmunmap(p->pagetable, addr, len/PGSIZE, 1);
    return 0;
  }
  // 引用计数为0时关闭文件
  if(0 == (vp->mapcnt -= len)){
    fileclose(vp->f);
    vp->valid = 0;
  }
  return 0;
}

按照之前的方法在以下文件中添加系统调用:

# user/user.h
void *mmap(void *addr, size_t len, int prot, int flags,
           int fd, off_t offset);
int munmap(void *addr, size_t len);

# user/usys.pl
entry("mmap");
entry("munmap");

# kernel/syscall.h
#define SYS_mmap   22
#define SYS_munmap 23

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

static uint64 (*syscalls[])(void) = {
...
[SYS_mmap]    sys_mmap,
[SYS_munmap]  sys_munmap,
};

注意到测试程序未进入编译,需要在Makefile中添加:

UPROGS=\
	$U/_cat\
	$U/_echo\
	$U/_forktest\
	$U/_grep\
	$U/_init\
	$U/_kill\
	$U/_ln\
	$U/_ls\
	$U/_mkdir\
	$U/_rm\
	$U/_sh\
	$U/_stressfs\
	$U/_usertests\
	$U/_grind\
	$U/_wc\
	$U/_zombie\
	$U/_mmaptest\

缺页中断

mmap不负责分配物理内存,因此只有在出现缺页中断时需要处理真实的内存分配,类似于之前实验中的写时复制,要在trap.c中的usertrap函数中增加对0xd缺页中断的处理:

  } else if(r_scause() == 0xd){
    uint64 addr = r_stval();
    struct VMA* vp = 0;
    // 找到缺页的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(mappages(p->pagetable, addr, PGSIZE, mem, PTE_U | PTE_V | (vp->prot << 1)) == -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;
    }

这里用到了很多无法调用的结构体,需要在trap.c中添加对应的头文件:

#include "fs.h"
#include "sleeplock.h"
#include "file.h"

fork & exit

根据提示,fork需要保证父子进程有一样的内存映射,exit也需要处理取消映射,在proc.c中修改这两个函数,fork中需要多复制一份VMA数组:

  np->maxaddr = p->maxaddr;
  for(int i = 0; i < VMA_MAX; i++)
    if(p->vma[i].valid){
      filedup(p->vma[i].f);
      memmove(&np->vma[i], &p->vma[i], sizeof(struct VMA));
    }

exit需要取消已有的映射,发现类型为MAP_SHARED时调用写回函数:

  for(int i = 0; i < VMA_MAX; i++){
    if(p->vma[i].valid == 1){
      struct VMA* vp = &p->vma[i];
      for(uint64 addr = vp->addr; addr < vp->addr + vp->len; addr += PGSIZE){
        if(walkaddr(p->pagetable, addr) != 0){
          if(vp->flags == MAP_SHARED)
            filewriteoff(vp->f, addr, PGSIZE, addr-vp->addr);
          uvmunmap(p->pagetable, addr, 1, 1);
        }
      }
      fileclose(p->vma[i].f);
      p->vma[i].valid = 0;
    }
  }

这里用到了MAP_SHARED宏定义,因此要在proc.c中添加头文件:

#include "fcntl.h"

测试结果

使用make grade测试,结果如下:

== Test running mmaptest == 
$ make qemu-gdb
(2.4s) 
== Test   mmaptest: mmap f == 
  mmaptest: mmap f: OK 
== Test   mmaptest: mmap private == 
  mmaptest: mmap private: OK 
== Test   mmaptest: mmap read-only == 
  mmaptest: mmap read-only: OK 
== Test   mmaptest: mmap read/write == 
  mmaptest: mmap read/write: OK 
== Test   mmaptest: mmap dirty == 
  mmaptest: mmap dirty: OK 
== Test   mmaptest: not-mapped unmap == 
  mmaptest: not-mapped unmap: OK 
== Test   mmaptest: two files == 
  mmaptest: two files: OK 
== Test   mmaptest: fork_test == 
  mmaptest: fork_test: OK 
== Test usertests == 
$ make qemu-gdb
usertests: OK (38.6s)

标签:VMA,addr,int,mmap,vma,vp,XV6
From: https://www.cnblogs.com/JasenChao/p/18015457

相关文章

  • 【XV6】 Xv6 and Unix utilities
    代码:https://github.com/JasenChao/xv6-labs.git运行xv6实验环境使用的是Ubuntu20.04,需要安装一些工具:sudoapt-getinstallgitbuild-essentialgdb-multiarchqemu-system-miscgcc-riscv64-linux-gnubinutils-riscv64-linux-gnu安装完成后验证qemu是否安装成功,成功的话......
  • 【XV6】 page tables
    代码:https://github.com/JasenChao/xv6-labs.git快速获取pid-ugetpid题目要求参考已实现的ugetpid()使用USYSCALL快速获取pid。实现的思路是在每一个进程中增加一个共享页面,通过USYSCALL指定的虚拟地址,找到指定的页面。参考进程中的Trampoline页和Trapframe页。Trampoline页保......
  • 【XV6】 system calls
    代码:https://github.com/JasenChao/xv6-labs.git使用GDB调试安装risc-v的GDB先安装依赖:sudoapt-getinstalllibncurses5-devpython2python2-devtexinfolibreadline-dev再下载源码,可以从清华镜像源下载:wgethttps://mirrors.tuna.tsinghua.edu.cn/gnu/gdb/gdb-13.2.ta......
  • 【XV6】 Multithreading
    代码:https://github.com/JasenChao/xv6-labs.git用户级线程切换题目要求完成用户级线程系统,提示程序要在uthread.c和uthread_switch.S中补充完成。用户级线程调度和进程的机制是类似的,因此uthread_switch.S可以复制swtch.S中的内容: .globlthread_switchthread_switch:......
  • 【XV6】 Copy-on-Write Fork for xv6
    代码:https://github.com/JasenChao/xv6-labs.gitCopy-on-WriteFork系统调用fork()会复制一个父进程的用户空间到子进程,一方面如果进程较大,复制需要很长的时间,另一方面复制的内存的大部分会被丢弃,造成浪费。题目要求实现写时复制COW来延迟fork的物理内存复制,子进程只创建了一个......
  • 【XV6】 traps
    代码:https://github.com/JasenChao/xv6-labs.gitbacktrace题目要求实现backtrace来对堆栈上调用发生错误的地方进行跟踪。寄存器s0包含指向当前堆栈帧的指针,那么返回地址就位于帧指针的固定偏移量-8,前一个fp地址的偏移量为-16。在riscv.h文件中增加提示中的代码:staticinline......
  • 【XV6】 file system
    代码:https://github.com/JasenChao/xv6-labs.git支持大文件XV6目前只支持268个blocks大小的文件,一个block(BSIZE)为1024,文件块inode包含12个一级地址和1个二级地址,二级地址指向另一个block,其中存放了256个一级地址,因此一共是268个。题目要求支持大文件(65803个blocks),提示通过三级......
  • 【XV6】 locks
    代码:https://github.com/JasenChao/xv6-labs.git内存分配器单个空闲内存列表可能引起多个CPU的频繁锁争用,题目要求设计内存分配器,让每个CPU维护一个空闲内存列表,不同CPU的分配和释放可以并行执行,但如果一个CPU可用列表为空,而其他CPU可用列表不为空,则这个CPU必须窃取其他CPU的空......
  • 【XV6】 networking
    代码:https://github.com/JasenChao/xv6-labs.gitE1000网络设备驱动题目已经在kernel/e1000.c中给出了E1000的初始化函数和发送接收函数,要求完善发送和接收的功能。其他相关的代码,上层的网络协议在kernel/net.c和kernel/net.h中。PCI总线上搜索网卡的代码在kernel/pci.c中://t......
  • [MIT 6.S081] Lab: Copy-on-Write Fork for xv6
    Lab:Copy-on-WriteForkforxv6在这个实验中,我们要为xv6实现cowfork。Implementcopy-onwrite根据写时复制的方式,我们在复制页面的时候应该采用的是将父级的物理页面映射到子级的页面,因此我们需要修改kernel/vm.c中的uvmcopy,将页面复制修改为映射的方式,同时应当将......