6.1810: Operating System Engineering 2023 <Lab9: mmap>

一、本节任务

二、Lab: mmap (hard)

2.1 mmap 介绍

mmap(2) 系统调用能将文件或者设备映射到内存中,返回映射区域的起始地址。

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

参数: 

  • addr: 指定映射的虚拟内存地址,若为 NULL 则由内核选择合适的虚拟内存地址;
  • length:映射的长度;
  • prot:映射内存的保护模式,可选值如下:

  PROT_EXEC:可以被执行;

  PROT_READ:可以被读取;

  PROT_WRITE:可以被写入;

  PROT_NONE:不可访问;

  • flags:指定映射的类型,可选值如下:

  MAP_FIXED:使用指定的起始虚拟内存地址进行映射;

  MAP_SHARED:与其他所有映射到这个文件的进程共享映射空间(可实现共享内存);

  MAP_PRIVATE:建立一个写时复制(copy-on-write)的私有映射空间;

  .....

  • fd:进行映射的文件描述符;
  • offset:文件偏移量(从文件何处开始映射);

例子

int fd = open(filepath, O_RDWR, 0644);                           // 打开文件
void *addr = mmap(NULL, 8192, PROT_WRITE, MAP_SHARED, fd, 4096); // 对文件进行映射

在上面的例子中,我们使用 open 以可读可写的方式打开文件,然后使用 mmap 对文件进行映射,映射的方式如下:

  • addr 为 NULL 表示让内核自动选择合适的虚拟内存地址进行映射;
  • length 为 8192 表示映射的区域为 2 个内存页的大小(一个内存页大小为 4KB);
  • prot 为 PROT_WRITE 表示映射的内存区域为可写;
  • flags 为 MAP_SHARED 表示映射区域为其他进程所共享;
  • fd 为打开文件描述符;
  • offset 为 4096 表示从文件的 4096 处开始映射;

下面是上述例子在内核中的结构: 

2.2 lab 实现

mmap 系统调用可以将文件映射到进程地址空间中、实现进程间的内存共享,而本次的 lab 就需要我们实现 mmap 和 munmap 系统调用。在实现 mmap 之前,有如下约定:

  • 测试文件中的 addr 一直为 0(NULL),所以内核应该决定在哪个地址映射文件,并且要把该地址返回,如果映射失败则返回 0xffffffffffffffff;
  • len 是要映射的字节个数,可能和文件长度不同;
  • prot 可以为 PROT_READ 和 PROT_WRITE;
  • flags 为 MAP_SHARED(映射内存区域的修改应该被写回文件中)和 MAP_PRIVATE(不写回文件),MAP_SHARED 的实现可以不共享内存区域(帮你减小难度);
  • offset 一直为 0(映射的开始位置为文件的开始);
  • munmap 需要能释放指定区域的映射空间,如果进程指定了 MAP_SHARED 并且修改了映射的内存区域,则需要先把它写回文件。munmap 释放的区域只会为映射区域的开头部分和结尾部分或者全部区域,不会出现释放中间部分的情况。

代码实现

首先为 struct proc 结构体增加一个长度为 16 的 vma 表:

/** kernel/proc.h **/// VMA struct 
#define VMASIZE 16
struct vma {int valid;uint64 addr;int length;int prot;int flags;int offset;struct file *fp;
};// Per-process state
struct proc {......char name[16];               // Process name (debugging)struct vma vmas[VMASIZE];    // Virtual memory area array
};

接下来实现 mmap 系统调用, 在 mmap 中不需要分配实际的物理页面,等用户访问到相应页面时再触发中断,进入 trap 中拷贝相应页面。

/** kernel/sysfile.c **/uint64
sys_mmap(void)
{uint64 failure = (uint64)((char *) -1);uint64 addr;int len, prot, flags, offset;struct file *f;struct proc *p = myproc();argaddr(0, &addr);argint(1, &len);argint(2, &prot);argint(3, &flags);if(argfd(4, 0, &f) < 0)return failure;argint(5, &offset);len = PGROUNDUP(len);if (MAXVA - len < p->sz)return failure;if(!f->readable && (prot & PROT_READ))return failure;if(!f->writable && (prot & PROT_WRITE) && (flags == MAP_SHARED))return failure;// find a empty vmastruct vma *vp = p->vmas;for (int i = 0; i < VMASIZE; i++) {if (vp[i].valid == 0) {vp[i].valid = 1;vp[i].addr = p->sz;vp[i].length = len;p->sz += len; // 虚拟的增加进程大小, 但没有实际分配物理页vp[i].prot = prot;vp[i].flags = flags;vp[i].offset = offset;vp[i].fp = f;filedup(f); // add the file ref countreturn vp[i].addr;}}return failure;
}

在这样写完 mmap 后,当用户试图去访问 mmap 所返回的地址时,由于我们没有分配物理页,将会触发缺页中断。这个时候我们就需要在 usertrap 里把对应 offset 的文件内容读到一个新分配的物理页中,并把这个物理页加入这个进程的虚拟内存映射表里。 

/** kernel/trap.c **/void
usertrap(void)
{
.....
.....} else if(r_scause() == 13 || r_scause() == 15) {uint64 va = r_stval();struct proc *p = myproc();if (va > MAXVA || va > p->sz) {p->killed = 1;} else {struct vma *vp = p->vmas;int found = 0;for (int i = 0; i < VMASIZE; i++) {if (vp[i].valid && va >= vp[i].addr && va < vp[i].addr + vp[i].length) {va = PGROUNDDOWN(va);uint64 pa = (uint64)kalloc();if (pa == 0)break;memset((void*)pa, 0, PGSIZE);ilock(vp[i].fp->ip);if (readi(vp[i].fp->ip, 0, pa, vp[i].offset + va - vp[i].addr, PGSIZE) < 0) {iunlock(vp[i].fp->ip);break;}iunlock(vp[i].fp->ip);int perm = PTE_U;if (vp[i].prot & PROT_READ)perm |= PTE_R;if (vp[i].prot & PROT_WRITE)perm |= PTE_W;if (vp[i].prot & PROT_EXEC)perm |= PTE_X;if (mappages(p->pagetable, va, PGSIZE, pa, perm) < 0) {kfree((void*)pa);break;}found = 1;break;}}if (!found)p->killed = 1;}
}
....

然后, 在 munmap 时,我们需要把分配的物理页释放掉,而且如果 flag 是 MAP_SHARED,直接把 unmap 的区域无脑复写回文件中,不管有没有被修改 (其实可以优化, 通过观察dirty bit来决定一个页是否需要被复写) 。

/** kernel/sysfile.c **/uint64
sys_munmap(void)
{uint64 addr;int length;argaddr(0, &addr);argint(1, &length);struct proc *p = myproc();struct vma* vma = 0;int idx = -1;// find the corresponding vmafor (int i = 0; i < VMASIZE; i++) {if (p->vmas[i].valid && addr >= p->vmas[i].addr && addr <= p->vmas[i].addr + p->vmas[i].length) {idx = i;vma = &p->vmas[i];break;}}if (idx == -1)// not in a valid VMAreturn -1;addr = PGROUNDDOWN(addr);length = PGROUNDUP(length);if (vma->flags & MAP_SHARED) {// write back 将区域复写回文件filewrite(vma->fp, addr, length);}// 删除虚拟内存映射并释放物理页uvmunmap(p->pagetable, addr, length/PGSIZE, 1);// change the mmap parameterif (addr == vma->addr && length == vma->length) {// fully unmapped 完全释放fileclose(vma->fp);vma->valid = 0;} else if (addr == vma->addr) {// cover the beginning 释放区域包括头部vma->addr += length;vma->length -= length;vma->offset += length;} else if ((addr + length) == (vma->addr + vma->length)) {// cover the end 释放区域包括尾部vma->length -= length;} else {panic("munmap neither cover beginning or end of mapped region");}return 0;
}

由于 uvmunmap 和 uvmcopy 两个函数会检查页面的 PTE_V,即页面是否加载到内存中来,这里需要跳过而不是 panic。 

/** kernel/vm.c **/void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
...if((*pte & PTE_V) == 0)continue;//panic("uvmunmap: not mapped");
...
}int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
...if((*pte & PTE_V) == 0)continue;//panic("uvmcopy: page not present");
...
}

下面就是在 fork 的时候需要复制父进程的 vmas 到子进程,并且在 exit 后需要解除映射。 

/** kernel/proc.c **/int
fork(void)
{
...acquire(&np->lock);for (int i = 0; i < VMASIZE; i++) {np->vmas[i].valid = 0;if (p->vmas[i].valid) { // 复制vma entrymemmove(&np->vmas[i], &p->vmas[i], sizeof(struct vma));filedup(p->vmas[i].fp); // 增加引用次数}}np->state = RUNNABLE;release(&np->lock);
...
}void
exit(int status)
{
...// unmap any mmapped regionfor (int i = 0; i < VMASIZE; i++) {if (p->vmas[i].valid) {if (p->vmas[i].flags & MAP_SHARED) {filewrite(p->vmas[i].fp, p->vmas[i].addr, p->vmas[i].length);}fileclose(p->vmas[i].fp);uvmunmap(p->pagetable, p->vmas[i].addr, p->vmas[i].length / PGSIZE, 1);p->vmas[i].valid = 0;}}
...
}

最后通过所有测试!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Mysql】整理

Mysql整理与总结 整理Mysql的基本内容供回顾。 参考&#xff1a; [1]. 掘金.MySQL三大日志(binlog,redolog,undolog)详解 [2]. Javaguide.MySQL三大日志(binlog、redo log和undo log)详解

c语言贪食蛇游戏

演示视频 目录 一.概述 二.游戏开始前 修改控制台程序标题和大小 Win32 API GetStdHandle函数 GetConsoleCursorInfo函数和SetConsoleCursorInfo函数 SetConsoleCursorPosition函数 游戏开篇界面处理 创建地图 蛇身节点以及食物节点初始化 蛇身的初始化 整体蛇节点…

【学习笔记】TypeScript学习笔记1 --TypeScript中的类型

文章目录 TS总的变量类型References TS总的变量类型 备注&#xff1a; 如果一个变量设置为了any 类型之后相当于变量关闭了TS的类型检测 let d: any; d 10; d hello;//unknown表示的是未知类型&#xff0c;实际是上一个安全的any,unknown类型的变量不能直接赋值给其他变量le…

【Linux】基于管道进行进程间通信

进程间通信 一、初识进程间通信1. 进程间通信概念2. 进程间通信分类 二、管道1. 管道概念2. 管道原理3. 匿名管道4. 匿名管道系统接口5. 管道的特性和情况6. 匿名管道的应用&#xff08;1&#xff09;命令行&#xff08;2&#xff09;进程池 7. 命名管道&#xff08;1&#xff…

Linux------命令行参数

目录 前言 一、main函数的参数 二、命令行控制实现计算器 三、实现touch指令 前言 当我们在命令行输入 ls -al &#xff0c;可以查看当前文件夹下所有文件的信息&#xff0c;还有其他的如rm&#xff0c;touch等指令&#xff0c;都可以帮我们完成相应的操作。 其实运行这些…

【语音合成】中文-多情感领域-16k-多发音人

模型介绍 语音合成-中文-多情感领域-16k-多发音人 框架描述 拼接法和参数法是两种Text-To-Speech(TTS)技术路线。近年来参数TTS系统获得了广泛的应用&#xff0c;故此处仅涉及参数法。 参数TTS系统可分为两大模块&#xff1a;前端和后端。 前端包含文本正则、分词、多音字预…

【C++】构造函数、初始化列表,析构函数,拷贝构造函数,运算符重载

注&#xff1a;本博客图片来源于学习笔记: 学习笔记https://gitee.com/box-he-he/learning-notes 完整思维导图请前往该博主码云下载。 目录 注&#xff1a;本博客图片来源于学习笔记: 学习笔记https://gitee.com/box-he-he/learning-notes 完整思维导图请前往该博主码云下载…

2024无参考图像的清晰度评价方法

无参考图像质量评价算法 无参考图像质量评价是指参考图像不存在的情况下&#xff0c;直接计算失真图像的视觉质量。根据无参考图像质量评价模型在计算图像视觉质量时是否需要图像的主观分数来进行训练&#xff0c;无参考图像质量评价算法可分为基于监督学习的无参考图像质量评价…

2024-02-06 TCP/UDP work

1. 画出TCP三次握手和四次挥手的示意图&#xff0c;并且总结TCP和UDP的区别 三次握手&#xff1a; 4次挥手&#xff1a; tcp/udp区别 TCP 1. 稳定&#xff0c;提供面向连接的&#xff0c;可靠的数据传输服务 2. 传输过程中&#xff0c;数据无误、数据无丢失、数据无失序、…

IDEA 配置以及一些技巧

1. IDEA设置 1.1 设置主题 1.2 设置字体和字体大小 1.3 编辑区的字体用ctrl鼠标滚轮可以控制大小 1.4 自动导包和优化多余的包 1.5 设置编码方式 1.6 配置 maven 1.7 设置方法形参参数提示 1.8 设置控制台的字体和大小 注意&#xff1a;设置控制台字体和大小后需要重启IDEA才会…

第1章 认识Flask

学习目标 了解Flask框架&#xff0c;能够说出Flask框架的发展史以及特点 熟悉隔离Python环境的创建方式&#xff0c;能够独立在计算机上创建隔离的Python环境 掌握Flask的安装方式&#xff0c;能够独立在计算机上安装Flask框架 掌握PyCharm配置隔离环境的方式&#xff0c;能…

【C++】基础知识讲解(命名空间、缺省参数、重载、输入输出)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 命名空间 命名空间的定义 命名空间的使用 命名空间的嵌套使用 C输入&输出 std命名空间的使用惯例&…

C语言第二十弹---指针(四)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 指针 1、字符指针变量 2、数组指针变量 2.1、数组指针变量是什么&#xff1f; 2.2、数组指针变量怎么初始化 3、⼆维数组传参的本质 4、函数指针变量 4.1…

Rust开发WASM,浏览器运行WASM

首先需要安装wasm-pack cargo install wasm-pack 使用cargo创建工程 cargo new --lib mywasm 编辑Cargo.toml文件&#xff0c;修改lib的类型为cdylib&#xff0c;并且添加依赖wasm-bindgen [package] name "mywasm" version "0.1.0" edition "…

二进制安全虚拟机Protostar靶场(8)heap3 Fastbins unlink exploit

前言 这是一个系列文章&#xff0c;之前已经介绍过一些二进制安全的基础知识&#xff0c;这里就不过多重复提及&#xff0c;不熟悉的同学可以去看看我之前写的文章 heap3 程序静态分析 https://exploit.education/protostar/heap-three/#include <stdlib.h> #include …

故障诊断 | 一文解决,TCN时间卷积神经网络模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,TCN时间卷积神经网络模型的故障诊断(Matlab) 模型描述 时间卷积神经网络(TCN)是一种用于序列数据建模和预测的深度学习模型。它通过卷积操作在时间维度上对序列数据进行特征提取,并且可以处理可变长度的输入序列。 要使用TCN进行…

3D室内虚拟灭火体验为预防火灾提供全新方案

室内火灾常见于充电器未拔、电动车、油锅起火及煤气泄露等原因&#xff0c;由于室内空间小、易燃物多&#xff0c;因此极易造成较大的人员财产损失&#xff0c;3D仿真还原技术、通过1&#xff1a;1模拟还原火灾发生全过程&#xff0c;火灾VR安全培训提供全方位、真实感强的模拟…

FlinkSql 窗口函数

Windowing TVF 以前用的是Grouped Window Functions&#xff08;分组窗口函数&#xff09;&#xff0c;但是分组窗口函数只支持窗口聚合 现在FlinkSql统一都是用的是Windowing TVFs&#xff08;窗口表值函数&#xff09;&#xff0c;Windowing TVFs更符合 SQL 标准且更加强大…

Java中JVM常用参数配置(提供配置示例)

目录 前言一、内存参数配置二、垃圾收集器配置三、GC策略配置3.1、基础通用配置3.2、Parallel 和 Parallel Old 常用参数配置3.3、CMS 常用参数配置3.4、G1 常用参数配置 四、GC日志配置五、dump 日志参数配置5.1、OutOfMemory异常时生成dump文件5.2、发生Full GC时生成dump文件…

QT安装与helloworld

文章目录 QT安装与helloworld1.概念&#xff1a;2.安装QT3.配置环境变量4.创建项目5.运行效果 QT安装与helloworld 1.概念&#xff1a; Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境。Qt Creator可带来两大关键益处&#xff1a;提供首个专为支持跨平台开发而设计的…