MIT 6.S081 Lab Ten -- mmap

MIT 6.S081 Lab Ten -- mmap

  • 引言
  • mmap(hard)
    • 代码解析


引言

本文为 MIT 6.S081 2020 操作系统 实验十解析。

MIT 6.S081课程前置基础参考: 基于RISC-V搭建操作系统系列


mmap(hard)

mapmunmap系统调用允许UNIX程序对其地址空间进行详细控制。它们可用于在进程之间共享内存,将文件映射到进程地址空间,并作为用户级页面错误方案的一部分,如本课程中讨论的垃圾收集算法。在本实验室中,您将把mmapmunmap添加到xv6中,重点关注内存映射文件(memory-mapped files)。

获取实验室的xv6源代码并切换到mmap分支:

$ git fetch
$ git checkout mmap
$ make clean

手册页面(运行man 2 mmap)显示了mmap的以下声明:

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

可以通过多种方式调用mmap,但本实验只需要与内存映射文件相关的功能子集。您可以假设addr始终为零,这意味着内核应该决定映射文件的虚拟地址。mmap返回该地址,如果失败则返回0xfffffffffffffffflength是要映射的字节数;它可能与文件的长度不同。prot指示内存是否应映射为可读、可写,以及/或者可执行的;您可以认为protPROT_READPROT_WRITE或两者兼有。flags要么是MAP_SHARED(映射内存的修改应写回文件),要么是MAP_PRIVATE(映射内存的修改不应写回文件)。您不必在flags中实现任何其他位。fd是要映射的文件的打开文件描述符。可以假定offset为零(它是要映射的文件的起点)。

允许进程映射同一个MAP_SHARED文件而不共享物理页面。

munmap(addr, length)应删除指定地址范围内的mmap映射。如果进程修改了内存并将其映射为MAP_SHARED,则应首先将修改写入文件。munmap调用可能只覆盖mmap区域的一部分,但您可以认为它取消映射的位置要么在区域起始位置,要么在区域结束位置,要么就是整个区域(但不会在区域中间“打洞”)。

YOUR JOB

  • 您应该实现足够的mmapmunmap功能,以使mmaptest测试程序正常工作。如果mmaptest不会用到某个mmap的特性,则不需要实现该特性。

完成后,您应该会看到以下输出:

$ mmaptest
mmap_test starting
test mmap f
test mmap f: OK
test mmap private
test mmap private: OK
test mmap read-only
test mmap read-only: OK
test mmap read/write
test mmap read/write: OK
test mmap dirty
test mmap dirty: OK
test not-mapped unmap
test not-mapped unmap: OK
test mmap two files
test mmap two files: OK
mmap_test: ALL OK
fork_test starting
fork_test OK
mmaptest: all tests succeeded
$ usertests
usertests starting
...
ALL TESTS PASSED
$

提示:

  • 首先,向UPROGS添加_mmaptest,以及mmapmunmap系统调用,以便让user/mmaptest.c进行编译。现在,只需从mmapmunmap返回错误。我们在kernel/fcntl.h中为您定义了PROT_READ等。运行mmaptest,它将在第一次mmap调用时失败。
  • 惰性地填写页表,以响应页错误。也就是说,mmap不应该分配物理内存或读取文件。相反,在usertrap中(或由usertrap调用)的页面错误处理代码中执行此操作,就像在lazy page allocation实验中一样。惰性分配的原因是确保大文件的mmap是快速的,并且比物理内存大的文件的mmap是可能的。
  • 跟踪mmap为每个进程映射的内容。定义与第15课中描述的VMA(虚拟内存区域)对应的结构体,记录mmap创建的虚拟内存范围的地址、长度、权限、文件等。由于xv6内核中没有内存分配器,因此可以声明一个固定大小的VMA数组,并根据需要从该数组进行分配。大小为16应该就足够了。
  • 实现mmap:在进程的地址空间中找到一个未使用的区域来映射文件,并将VMA添加到进程的映射区域表中。VMA应该包含指向映射文件对应struct file的指针;mmap应该增加文件的引用计数,以便在文件关闭时结构体不会消失(提示:请参阅filedup)。运行mmaptest:第一次mmap应该成功,但是第一次访问被mmap的内存将导致页面错误并终止mmaptest
  • 添加代码以导致在mmap的区域中产生页面错误,从而分配一页物理内存,将4096字节的相关文件读入该页面,并将其映射到用户地址空间。使用readi读取文件,它接受一个偏移量参数,在该偏移处读取文件(但必须lock/unlock传递给readi的索引结点)。不要忘记在页面上正确设置权限。运行mmaptest;它应该到达第一个munmap
  • 实现munmap:找到地址范围的VMA并取消映射指定页面(提示:使用uvmunmap)。如果munmap删除了先前mmap的所有页面,它应该减少相应struct file的引用计数。如果未映射的页面已被修改,并且文件已映射到MAP_SHARED,请将页面写回该文件。查看filewrite以获得灵感。
  • 理想情况下,您的实现将只写回程序实际修改的MAP_SHARED页面。RISC-V PTE中的脏位(D)表示是否已写入页面。但是,mmaptest不检查非脏页是否没有回写;因此,您可以不用看D位就写回页面。
  • 修改exit将进程的已映射区域取消映射,就像调用了munmap一样。运行mmaptestmmap_test应该通过,但可能不会通过fork_test
  • 修改fork以确保子对象具有与父对象相同的映射区域。不要忘记增加VMA的struct file的引用计数。在子进程的页面错误处理程序中,可以分配新的物理页面,而不是与父级共享页面。后者会更酷,但需要更多的实施工作。运行mmaptest;它应该通过mmap_testfork_test

运行usertests以确保一切正常。


代码解析

本实验是实现一个内存映射文件的功能,将文件映射到内存中,从而在与文件交互时减少磁盘操作。

(1). 根据提示1,首先是配置mmapmunmap系统调用,此前已进行过多次类似流程,不再赘述。在kernel/fcntl.h中定义了宏,只有在定义了LAB_MMAP时这些宏才生效,而LAB_MMAP是在编译时在命令行通过gcc的-D参数定义的

在这里插入图片描述

void* mmap(void* addr, int length, int prot, int flags, int fd, int offset);
int munmap(void* addr, int length);

(2). 根据提示3,定义VMA结构体,并添加到进程结构体中

#define NVMA 16
// 虚拟内存区域结构体
struct vm_area {
  int used;           // 是否已被使用
  uint64 addr;        // 起始地址
  int len;            // 长度
  int prot;           // 权限
  int flags;          // 标志位
  int vfd;            // 对应的文件描述符
  struct file* vfile; // 对应文件
  int offset;         // 文件偏移,本实验中一直为0
};

struct proc {
  ...
  struct vm_area vma[NVMA];    // 虚拟内存区域
}

(3). 在allocproc中将vma数组初始化为全0

static struct proc*
allocproc(void)
{
  ...

found:
  ...

  memset(&p->vma, 0, sizeof(p->vma));
  return p;
}

(4). 根据提示2、3、4,参考lazy实验中的分配方法(将当前p->sz作为分配的虚拟起始地址,但不实际分配物理页面),此函数写在sysfile.c中就可以使用静态函数argfd同时解析文件描述符和struct file

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;
}

(5). 根据提示5,此时访问对应的页面就会产生页面错误,需要在usertrap中进行处理,主要完成三项工作:分配物理页面,读取文件内容,添加映射关系

void
usertrap(void)
{
  ...
  if(cause == 8) {
    ...
  } else if((which_dev = devintr()) != 0){
    // ok
  } else if(cause == 13 || cause == 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(), cause) != 0) p->killed = 1;
    } else
      p->killed = 1;
#endif
  } else {
    ...
  }

  ...
}

/**
 * @brief mmap_handler 处理mmap惰性分配导致的页面错误
 * @param va 页面故障虚拟地址
 * @param cause 页面故障原因
 * @return 0成功,-1失败
 */
int mmap_handler(int va, int 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;
  // cause == 13:读取访问导致的页面故障(Load Page Fault)
  if(cause == 13 && vf->readable == 0) return -1;
  // cause == 15:写入访问导致的页面故障(Store Page Fault)
  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;
}

(6). 根据提示6实现munmap,且提示7中说明无需查看脏位就可写回

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;
}

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

if((*pte & PTE_V) == 0)
  continue;

(8). 根据提示8修改exit,将进程的已映射区域取消映射

void
exit(int status)
{
  // Close all open files.
  for(int fd = 0; fd < NOFILE; fd++){
    ...
  }

  // 将进程的已映射区域取消映射
  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;
    }
  }

  begin_op();
  iput(p->cwd);
  end_op();
  ...
}

(9). 根据提示9,修改fork,复制父进程的VMA并增加文件引用计数

int
fork(void)
{
 // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    ...
  ...

  // 复制父进程的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);
    }
  }

  safestrcpy(np->name, p->name, sizeof(p->name));

  ...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/54917.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot+mybatis+vue进销存管理信息系统

基于springbootmybatisvue进销存管理信息系统 一、系统介绍二、功能展示1.个人中心2.企业信息管理3.商品信息管理4.客户信息管理5.入库记录管理6.出库记录管理7.出库记录管理8.操作日志管理9.库存盘点管理 四、获取源码 一、系统介绍 系统主要功能&#xff1a; 普通用户&#…

华为OD机试真题 Java 实现【简单的自动曝光】【2023Q1 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、备注五、解题思路六、Java算法源码七、效果展示1、输入2、输出3、说明4、再输入5、输出6、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff…

css中flex后文本溢出的问题

原因&#xff1a; 为了给flex item提供一个合理的默认最小尺寸&#xff0c;flex将flex item的min-width 和 min-height属性设置为了auto flex item的默认设置为&#xff1a; min-width&#xff1a; auto 水平flex布局 min-height&#xff1a;auto 垂直flex布局 解决办法&…

PHP8的变量-PHP8知识详解

昨天我们讲解了PHP8的常量&#xff0c;今天讲解PHP8的变量。常量有定义常量和预定义常量&#xff0c;变量呢&#xff1f;那就没有定义变量了&#xff0c;那叫给变量赋值&#xff0c;但是还是有预定义变量的。下面就给大家讲解什么是变量、变量赋值及使用及预定义变量。 一、什么…

zookeeper基础

安装 https://archive.apache.org/dist/zookeeper/zookeeper-3.5.7/ 命令 bin/zkServer.sh start bin/zkServer.sh stop bin/zkServer.sh status bin/zkCli.sh ll / quit 各个配置项的含义&#xff1a; tickTime&#xff1a;每个时钟周期的毫秒数。ZooKeeper使用一个内部…

DevOps系列文章之 java调用python脚本

在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件&#xff08;推荐&#xff09; 调用python脚本中的函数 简单介绍 官网地址 首页 | (jython.org) Jython项目提供了Java中的Python实现&#xff0c; 为Python提供了…

功能测试也可以发现数据库相关的性能问题

很多同学认为功能测试和性能测试是严格分开的&#xff0c;功能测试人员无法发现性能问题。其实不是这样的&#xff0c;功能测试人员在验证功能时也可以发现性能问题&#xff1b;一些功能反而在功能测试环境不好验证&#xff0c;需要在性能环境上测试。 今天咱们就说一下测试涉及…

【Redis】内存数据库Redis进阶(搭建各种集群)

目录 单机安装Redis搭建Redis主从集群搭建Redis哨兵集群 基于 CentOS 7 的 Redis 集群 单机安装Redis 安装 Redis 所需要的依赖&#xff1a; yum install -y gcc tcl将 Redis 安装包&#xff08;redis-6.2.4.tar.gz&#xff09;上传到任意目录 解压缩&#xff1a; tar -xzf …

【数据结构】图文并茂,通过逻辑图带你轻松拿捏链表,实现各种接口功能

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;我们接着之前讲过的顺序表来继续介绍初阶数据结构的内容&#xff0c;今天给大家带来的是有关链表的基本知识和各种接口功能的实现 好了&#xff0c;废话不多说&#x…

【uniapp】实现买定离手小游戏

前言 最近玩了一个小游戏&#xff0c;感觉挺有意思&#xff0c;打算放进我的小程序【自动化小助手】里面&#xff0c;“三张押一张&#xff0c;专押花姑娘&#xff01;”&#xff0c;从三张卡牌&#xff0c;挑选一张&#xff0c;中奖后将奖励进行发放&#xff0c;并且创建下一…

Github-Copilot初体验-Pycharm插件的安装与测试

引言&#xff1a; 80%代码秒生成&#xff01;AI神器Copilot大升级 最近copilot又在众多独角兽公司的合力下&#xff0c;取得了重大升级。GitHub Copilot发布还不到两年&#xff0c; 就已经为100多万的开发者&#xff0c;编写了46%的代码&#xff0c;并提高了55%的编码速度。 …

AMEYA详解松下Panasonic HF SSOP 1 Form A AQY PhotoMOS继电器

Panasonic HF SSOP 1 Form A AQY PhotoMOS继电器采用微型SSOP封装&#xff0c;具有600V的负载电压和1500Vrms 的I/O隔离电压 这些继电器具有8Ω的低导通电阻和高速运行的特点&#xff0c;SSOP封装旨在实现高密度安装。Panasonic HF SSOP AQY PhotoMOS继电器适用于从测试和测量设…

【HDFS】Block、BlockInfo、BlockInfoContiguous、BlockInfoStriped的分析记录

本文主要介绍如下内容: 关于几个Block类之间的继承、实现关系;针对文章标题中的每个类,细化到每个成员去注释分析列出、并详细分析BlockInfo抽象类提供的抽象方法、非抽象方法的功能针对几个跟块组织结构的方法再进行分析。moveBlockToHead、listInsert、listRemove等。一、…

从引入并集成多LLM到发布自研模型,RPA与LLM的融合进度怎样了?

RPA厂商对于大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;的应用&#xff0c;比大家想象的还要早一些。 毕竟&#xff0c;2019年兴起的这一波RPA热&#xff0c;背后都是因为AI技术。没有AI技术与RPA的融合&#xff0c;也就没有现在的RPA。 为了全力…

flutter开发实战-旋转loading指示器

flutter开发实战-旋转loading指示器。 一、交织动画 有些时候我们可能会需要一些复杂的动画&#xff0c;这些动画可能由一个动画序列或重叠的动画组成。一个动画组合在不同阶段包含了多种动画&#xff0c;要实现这种效果&#xff0c;需要使用交织动画&#xff08;Stagger Anim…

通过社区参与解锁早期增长:Maven 远程医疗平台概览

Maven通过用户导向的渐进式验证&#xff0c;找到了一个被忽视的巨大女性医疗服务市场&#xff0c;作为女性医疗保健的先行者&#xff0c;已服务超过1500万用户&#xff0c;目前估值已达$14亿。本文将深入探索Maven实现产品市场匹配的三个阶段&#xff0c;从如何验证初始的市场机…

基于微信机器人的二次开发

使用微信ipad协议来开发微信机器人&#xff0c;可以开发的项目很多&#xff0c;例如一些娱乐机器人、云发单系统&#xff0c;私域流量的智能管理和营销拓客&#xff0c;还有一些自动采集和发朋友圈的云端系统等。每个行业都有需求这样的系统应用&#xff0c;在线教育、金融、电…

接口自动化代码不会写?试试RunnerGo

RunnerGo支持自动化测试功能&#xff0c;RunnerGo的工作流程是&#xff1a;接口管理-场景管理-性能测试-自动化测试&#xff0c;所以自动化测试的运行内容为场景下的用例&#xff0c;我们可以在“场景管理”中预先配置好该场景下的用例&#xff0c;也可以在自动化测试中创建用例…

Pytorch入门学习——快速搭建神经网络、优化器、梯度计算

我的代码可以在我的Github找到 GIthub地址 https://github.com/QinghongShao-sqh/Pytorch_Study 因为最近有同学问我如何Nerf入门&#xff0c;这里就简单给出一些我的建议&#xff1a; &#xff08;1&#xff09;基本的pytorch&#xff0c;机器学习&#xff0c;深度学习知识&a…

类Blip2的视觉文本多模态算法

一、Blip2出现的意义不比ChatGPT差 BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models 论文链接&#xff1a;https://arxiv.org/abs/2301.12597 代码仓库&#xff1a;https://github.com/salesforce/LAVIS/tree/mai…
最新文章