首页 > 其他分享 >[MIT 6.S081] Lab: mmap

[MIT 6.S081] Lab: mmap

时间:2024-02-23 21:11:08浏览次数:27  
标签:return int mmap len vma vmas file S081 MIT

Lab: mmap

在本次实验中,我们要实现的是一个比较简简单的 mmap 实现,将文件映射到内存中的某个块,并根据权限设置这块内存的行为,以及为其提供延迟分配策略。

mmap

对于将文件映射到内存,其实是先规划好一块区域给文件使用,为什么要提供延迟分配,是因为如果需要映射一个文件时,就规划好一块内存,将文件读取后直接载入这块内存中的操作显然是花费巨大的,因此需要延迟分配。

当采取延迟分配的策略时,我们就需要先分配好文件载入内存的内存区域,从进程的虚拟内存中找一块可以开辟的区域,然后这样就好了。当进程需要读取这块内存时,因为我们还未映射到实际内存中,因此其会发生缺页中断。我们需要在其发生缺页中断时将对应文件载入一块实际的物理内存中,并将之前的虚拟地址映射到这块分配的物理内存中,也就是载入进程的页表中,此时,我们就实现好了 mmap 的功能了。

在添加好系统调用后,我们先不管两个系统调用,而是先加入支持 vma 的数据结构。

// kernel/proc.h
#define VMA_COUNT 16

struct virtual_memory_area {
  int is_used;
  uint64 address;
  int length;
  int prot;
  int flags;
  int fd;
  struct file *file;
  int offset;
};

struct proc {
  ...

  struct virtual_memory_area vmas[VMA_COUNT];
};

接着,我们着手初始化一下这个 vma 数组,只需要将里面的使用标记记为 0 即可。

// kernel/proc.c
static struct proc* allocproc(void) {
  ...
  for (int i = 0; i < VMA_COUNT; i ++) {
    p->vmas[i].is_used = 0;
  }

  return p;
}

然后,就是实现 sys_mmap 。在这里,我将两个系统调用定义放置在了 kernel/sysfile.h 中,这样就可以直接使用 argfd 了。

sys_mmap 中,我们所要做的事情是,从当前的进程中找到一个可用的 vma ,然后利用输入的参数将这个 vma 初始化。对于输入的参数,如果目标文件的权限和输入参数的权限不一致,那么就意味者错误了。在检查好权限之后,我们需要将 mmap 申请的内存大小对齐一下,即为 len = PGROUNDUP(len) ,对于 p->sz 中在 sbrk 扩大时已经是地址对齐页大小了,这里也需要将 len 对齐一下。

对于新地址的分配,使用的是之前懒惰分配的方式,也就是直接为 p->sz

根据提示,我们需要对打开的文件增加引用计数,防止文件被关闭。

// kernel/sysfile.c
uint64 sys_mmap(void) {
  uint64 addr;
  int len;
  int prot;
  int flags;
  int fd;
  struct file *file;
  int offset;

  if (argaddr(0, &addr) < 0
  || argint(1, &len) < 0
  || argint(2, &prot) < 0
  || argint(3, &flags)
  || argfd(4, &fd, &file) < 0
  || argint(5, &offset) < 0) {
    return -1;
  }

  struct proc *p = myproc();
  struct virtual_memory_area *vma = 0;
  for (int i = 0; i < VMA_COUNT; i ++) {
    if (!p->vmas[i].is_used) {
      vma = &p->vmas[i];
      break;
    }
  }
  if (vma == 0) {
    return -1;
  }

  if (!file->readable && (prot & PROT_READ))
    return -1;
  if (!file->writable && (prot & PROT_WRITE) && (flags & MAP_SHARED))
    return -1;

  len = PGROUNDUP(len);
  if (p->sz + len >= MAXVA) 
    return -1;

  vma->is_used = 1;
  vma->address = p->sz;
  vma->length = len;
  vma->prot = prot;
  vma->flags = flags;
  vma->fd = fd;
  vma->file = file;
  vma->offset = offset;
  
  filedup(file);
  p->sz += len;

  return vma->address;
}

现在,我们实现了 sys_mmap ,此时应当转向中断处理程序,处理一下缺页中断。还记得之前实验所处理的两个缺页中断吗?我们现在就需要处理这两个,当发生缺页中断时,将文件真正载入内存中。

我们所需要做的,就是确定这个缺页中断是否属于 vma ,然后将其所读取的 va 依然是对齐页面,并分配一页页面给它,并将文件的这一页大小数据载入内存中,确认好 pte 的权限后将其建立映射关系,这样就做好了延迟分配了。

// kernel/trap.c
void usertrap(void) {
  ...
  } else if (r_scause() == 13 || r_scause() == 15) {
    if (mmap_alloc_page(p, r_stval()) != 0)
      p->killed = -1;
  } else {
  ...
}

// kernel/proc.c
int mmap_alloc_page(struct proc *p, uint64 va) {
  struct virtual_memory_area *vma = 0;
  for (int i = 0; i < VMA_COUNT; i ++) {
    if (p->vmas[i].is_used
    && p->vmas[i].address <= va && va < p->vmas[i].address + p->vmas[i].length) {
      vma = &p->vmas[i];
      break;
    }
  }
  if (vma == 0) {
    return -1;
  }

  uint64 pa = (uint64)kalloc();
  va = PGROUNDDOWN(va);
  if (pa == 0) {
    return -1;
  }
  memset((void*)pa, 0, PGSIZE);

  ilock(vma->file->ip);
  if (readi(vma->file->ip, 0, pa, va - vma->address + vma->offset, PGSIZE) < 0) {
    iunlock(vma->file->ip);
    kfree((void*)pa);
    return -1;
  }
  iunlock(vma->file->ip);

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

  if (mappages(p->pagetable, va, PGSIZE, pa, pte_flags) < 0) {
    kfree((void *)pa);
    return -1;
  }

  return 0;
}

接下来是 sys_munmap 的实现,在确认是哪一块 vma 之后,我们需要将其内存对齐一下,检查是否要写回磁盘,然后解除那一块区域内存的映射,并计算剩下的区域。由于保证只会释放两端的内存,所以可以直接计算。记得,当所有虚拟内存区域都被释放了之后,要将文件关闭,同时释放这一块虚拟内存区域。

uint64 sys_munmap(void) {
  uint64 addr;
  int len;

  if (argaddr(0, &addr) < 0 || argint(1, &len) < 0) {
    return -1;
  }

  struct proc *p = myproc();
  struct virtual_memory_area *vma = 0;
  for (int i = 0; i < VMA_COUNT; i ++) {
    if (p->vmas[i].is_used && p->vmas[i].address <= addr && addr < p->vmas[i].address + p->vmas[i].length) {
      vma = &p->vmas[i];
      break;
    }
  }
  if (vma == 0) {
    return -1;
  }

  addr = PGROUNDDOWN(addr);
  len = PGROUNDUP(len);
  if (vma->flags & MAP_SHARED && filewrite(vma->file, addr, len) < 0) {
    // printf("munmap: wtf on this file");
  }

  uvmunmap(p->pagetable, addr, len / PGSIZE, 1);

  if (addr == vma->address) {
    vma->address += len;
    vma->length -= len;
  } else if (addr + len == vma->address + len) {
    vma->length -= len;
  } else {
    panic("munmap: wtf");
  }

  if (vma->length == 0) {
    fileclose(vma->file);
    vma->is_used = 0;
  }

  return 0;
}

你可能会注意到,这里如果还没被使用就解除映射关系的话,那么在之前的实验中 uvmunmappanic ,这里的解决方案与之前的一致。还有,这里也需要将 uvmcopy 处理掉。

剩下的,就是解决 forkexit 的问题了。对于 fork ,则是将 vma 数组拷贝一份,但是需要将文件引用计数增加一。对于 exit ,在等待子进程结束之后,将所有的 vma 中文件关闭,如果存在需要写入的内存,将其写入即可。对于这里写入内存,可以实现为脏位才写入,否则不写入,不过都可以通过测试。

// kernel/proc.c
int fork(void) {
  ...
  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }

  for (int i = 0; i < VMA_COUNT; i ++) {
    np->vmas[i] = p->vmas[i];
    if (np->vmas[i].is_used) {
      filedup(np->vmas[i].file);
    }
  }
  ...
}

void
exit(int status)
{
  ...
  // Close all open files.
  for(int fd = 0; fd < NOFILE; fd++){
    if(p->ofile[fd]){
      struct file *f = p->ofile[fd];
      fileclose(f);
      p->ofile[fd] = 0;
    }
  }

  for (int i = 0; i < VMA_COUNT; i ++) {
    struct virtual_memory_area *vma = &p->vmas[i];
    if (vma->is_used) {
      if (vma->flags & MAP_SHARED && filewrite(vma->file, vma->address, vma->length) < 0) {
        // printf("exit: wtf on this file");
      }
      fileclose(vma->file);
      uvmunmap(p->pagetable, vma->address, vma->length / PGSIZE, 1);
      vma->is_used = 0;
    }
  }
  ...
}

这样,本次实验内容就结束了。

标签:return,int,mmap,len,vma,vmas,file,S081,MIT
From: https://www.cnblogs.com/FlandreScarlet/p/18030381

相关文章

  • rebase 删除分支中某个 commit 之前的 commit
    要删除分支中的commit,可以使用gitrebase命令。以下是具体步骤:首先,使用gitlog命令查看要删除的commit的哈希值。然后,使用gitrebase-i<commit>命令进入交互式rebase模式,其中<commit>是要删除的commit的前一个commit的哈希值。在交互式rebase模式中......
  • MIT 6.5840 MapReduce Lab
    MapReduceMapReduce是一种编程模型,其思想是让程序员通过编写简单的Map和Reduce程序就能完成分布式系统的任务,而不需要关注分布式的具体细节。用户自定义的Map函数接受一个key/valuepair的输入值,然后产生一个中间key/valuepair值的集合。MapReduce库把所有具有相同中......
  • [GIT] 修改之前的commit提交的作者信息和邮箱信息 [转]
    1总体思路更改之前提交的作者信息和邮箱信息需要进行两步操作。首先,使用gitfilter-branch命令进行历史重写然后,使用gitpush--force将更改推送到远程仓库。Step1使用gitfilter-branch进行历史重写在终端或命令行中执行以下命令:gitfilter-branch--env-filte......
  • 树上dp——cf_928_G. Vlad and Trouble at MIT
    目录问题概述思路分析参考代码做题反思问题概述原题参考:G.VladandTroubleatMIT某学校的宿舍可以用一棵n个顶点的树来表示,每个顶点代表一个房间,房间内有一个学生,树是一个联通的无向图。今天晚上有三种学生:参加派对和玩音乐的学生(标记为P)想睡觉和享受安静的学生(标记为S)......
  • Git commit日志基本规范
    Git commit日志基本规范<type>(<scope>): <subject><BLANK LINE><body><BLANK LINE><footer>对格式的说明如下:type代表某次提交的类型,比如是修复一个bug还是增加一个新的feature。所有的type类型如下:feat: 新增featurefix: 修复bugdocs: 仅仅修改了文档,比如READM......
  • SpringBoot使用git-commit-id-maven-plugin打包
    简介git-commit-id-maven-plugin是一个maven插件,用来在打包的时候将git-commit信息打进jar中。这样做的好处是可以将发布的某版本和对应的代码关联起来,方便查阅和线上项目的维护。至于它的作用,用官方说法,这个功能对于大型分布式项目来说是无价的。功能你是否经常遇到这样的......
  • Git第十三阶段:Rebase -> 调整commit 顺序
     一、缘起:我有一些修改涉及到旧的commit,我想把这种同类的修改放在一起,这就需要我把原来的commit放在"TOP"的位置。图示:这是我原来的commit:C1-C2-C_TARGET-C3-C4我想将它变成:C1-C2-C3-C4-C_TARGET二、进入我的测试gitrepo,我将生成三个测试commit,然后用gitrebase来调......
  • [MIT 6.S081] Lab: file system
    Lab:filesystem在这个实现中我们将为xv6的文件系统实现二级间接块以支持大文件,与实现文件的软链接。Largefiles在该任务中,我们将为xv6的文件系统实现二级间接块,以支持大文件。对于这个功能,我们需要修改structinode中addr字段的功能。在实现功能之前,xv6的struct......
  • err code 90030,err msg webhook over limit
    钉钉群机器人发文本消息提示"webhookoverlimit"从2024.02.01钉钉修改了自定义机器人推送政策,免费额度,由之前的不限制,改为:目前钉钉已经限制了免费版的发送次数每个企业每月5000条(之前创建的外部群不限制,但是已经不能新增机器人了。)。钉钉收费版是9800元每年,然后每月5w条(如果......
  • G. Vlad and Trouble at MIT
    原题链接题解细节很多的树形dp,请看代码code#definelllonglong#include<bits/stdc++.h>usingnamespacestd;llsit[100005]={0};llf[100005]={0};vector<ll>G[100005];charstr[100005];inlinevoidread(ll&x){x=0;llflag=1;charc=......