MIT 6s081 lab10:mmap

Lab:mmap

给xv6添加mmap和munmap功能,将文件映射到进程的地址空间。

添加mmap和munmap的系统调用接口(和前面的实验一样)

在Makefile中添加$U/_mmaptest\

在kernel/syscall.h中添加

#define SYS_mmap   22
#define SYS_munmap 23

在kernel/syscall.c中添加函数声明:

extern uint64 sys_mmap(void);
extern uint64 sys_munmap(void);

系统调用函数指针数组中添加

[SYS_mmap]    sys_mmap,
[SYS_munmap]  sys_munmap,

在user/user.h中添加函数接口

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

在struct proc中添加字段,用于记录mmap的参数

struct vma
{uint64 addr;int length;int permissions;int flags;int fd;int valid;struct file* f;
};// Per-process state
struct proc {struct spinlock lock;struct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)struct vma VMA[16];
};

实现sys_mmap函数

首先读取函数参数,然后判断一下文件的权限和mmap的传入的权限之间的关系是否合法,提高文件的引用计数,分配对应的进程虚拟地址空间,但不分配实际的物理地址空间,并记录在进程的VMA字段中,用于后续usertrap的使用。

uint64 sys_mmap(void)
{// read paremater//void *mmap(void *addr, int length, int prot, int flags, int fd, int offset);uint64 addr;int length, prot, flags, fd, offset;if(argaddr(0, &addr) || argint(1, &length) < 0 || argint(2, &prot) < 0 || argint(3, &flags) < 0 || argint(4, &fd) < 0 || argint(5, &offset) < 0)return -1;struct proc * p = myproc();// printf("p->id = %d\n", p->pid);struct file* f;if(fd < 0 || fd >= NOFILE || (f = p->ofile[fd]) == 0)return -1;// printf("f->readable = %d\n", f->readable);if(!f->readable) {if((prot & PROT_READ)) return -1;}if(!f->writable) {if((prot & PROT_WRITE) && (flags & MAP_SHARED)) return -1;}filedup(f); // 提升文件的引用计数int found = 0;// 分配一块区域,然后放入对应的VMAaddr = p->sz; if(p->sz + PGROUNDDOWN(length) >= MAXVA) return -1;// printf("debug\n");p->sz += PGROUNDUP(length);// 在进程的VMA表格中插入,后续用于Usertrap时读取参数,用来判断对应是哪个mmap的区间for(int i = 0; i < 16; i++) {if(p->VMA[i].valid == 0) // 说明找到了{p->VMA[i].addr = addr;p->VMA[i].length = length;p->VMA[i].permissions = prot;p->VMA[i].flags = flags;p->VMA[i].fd = fd;p->VMA[i].f = f;found = 1;p->VMA[i].valid = 1;break;}}if(!found) return -1;return addr;
}

修改kernel/trap.c中的usertrap函数

读取发生page fault对应的虚拟地址va,根据这个va去查找进程的VMA数组,找到对应的mmap参数,申请一页物理内存,并从对应文件中读取一页的内容写入物理空间,并根据对应的读写权限,加入页表当中(添加映射)。

// kernel/fs.c
void my_readi(struct vma vma_cur, uint64 pa, uint64 offset, uint64 len)
{ilock(vma_cur.f->ip);// readi(ip, 0, pa, va - vma_cur.addr, PGSIZE); //从文件中读入数据给pareadi(vma_cur.f->ip, 0, pa, offset, len); //从文件中读入数据给paiunlock(vma_cur.f->ip);
}// kernel/trap.c
void
usertrap(void)
{int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.w_stvec((uint64)kernelvec);struct proc *p = myproc();// save user program counter.p->trapframe->epc = r_sepc();if(r_scause() == 8){// system callif(p->killed)exit(-1);// sepc points to the ecall instruction,// but we want to return to the next instruction.p->trapframe->epc += 4;// an interrupt will change sstatus &c registers,// so don't enable until done with those registers.intr_on();syscall();} else if((which_dev = devintr()) != 0){// ok}else if (r_scause() == 13 || r_scause() == 15){uint64 va = r_stval(); // get the virtual address that caused the page fault.// printf("page fault, va = %p\n", va); // 首先要根据va去判断,属于哪个VMA,struct proc* p = myproc();struct vma vma_cur;int found = 0;for(int i = 0; i < 16; i++) {if(va >= p->VMA[i].addr && va < p->VMA[i].addr + p->VMA[i].length){vma_cur = p->VMA[i];found = 1;break;}}if(found) {// 分配对应的物理内存char * pa = kalloc(); // alloc physial memory ,分配一页物理内存if(pa == 0){ // 申请失败p->killed = 1; // 杀死进程}memset(pa, 0, PGSIZE); //清空物理内存my_readi(vma_cur, (uint64)pa, va - vma_cur.addr, PGSIZE); // 把数据读到pa中// 物理页映射的时候要注意flaguint64 flag = PTE_U;if(vma_cur.permissions & (PROT_READ)) flag |= PTE_R;if(vma_cur.permissions & (PROT_WRITE)) flag |= PTE_W;// 注意flag if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)pa, flag) != 0){ //建立从va下取整开始一页的映射kfree(pa); // 分配失败,释放物理内存p->killed = 1;}}else {p->killed = 1;}...

实现sys_munmap函数

如果flag为MAP_SHARE,那么对于内存的修改要写回文件当中,然后调用uvmunmap,取消所有映射,并释放物理内存,如果释放的是之前mmap的全部空间,要减少文件的引用计数。

uint64 sys_munmap(void)
{// read paremater// int munmap(void* addr, int length);uint64 addr;int length;if(argaddr(0, &addr) || argint(1, &length) < 0)return -1;struct proc * p = myproc();int found = 0;for(int i = 0; i < 16; i++) {if(addr >= p->VMA[i].addr && addr < p->VMA[i].addr + p->VMA[i].length) {found = 1;// 如果有MAP_SHARE标志,那就要写回到文件if(p->VMA[i].flags & MAP_SHARED) {filewrite(p->VMA[i].f, p->VMA[i].addr, p->VMA[i].length)}// uvmunmapuvmunmap(p->pagetable, addr, PGROUNDDOWN(length) / PGSIZE, 1);// 如果munmap之前的整个mmap的空间,则要减少文件的引用计数if(addr == p->VMA[i].addr && length == p->VMA[i].length) {fileclose(p->VMA[i].f);p->VMA[i].valid = 0;}break;}}if(!found) return -1;return 0;
}

要注意修改uvmunmap函数,将panic(“uvmunmap: not mapped”)改成continue,因为此时可能有未映射的物理页。

void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{uint64 a;pte_t *pte;if((va % PGSIZE) != 0)panic("uvmunmap: not aligned");for(a = va; a < va + npages*PGSIZE; a += PGSIZE){if((pte = walk(pagetable, a, 0)) == 0)panic("uvmunmap: walk");if((*pte & PTE_V) == 0)panic("uvmunmap: not mapped");continue;// panic("uvmunmap: not mapped");if(PTE_FLAGS(*pte) == PTE_V)panic("uvmunmap: not a leaf");if(do_free){uint64 pa = PTE2PA(*pte);kfree((void*)pa);}*pte = 0;}
}

修改exit

在进程退出的时候,释放之前mmap对应的空间

void
exit(int status)
{struct proc *p = myproc();if(p == initproc)panic("init exiting");// munmap add here for(int i = 0; i < 16; i++) {if(p->VMA[i].valid) {// 如果有MAKE_SHARE标志,那就要写回到文件if(p->VMA[i].flags & (MAP_SHARED)) {filewrite(p->VMA[i].f, p->VMA[i].addr, p->VMA[i].length);}// uvmunmapuvmunmap(p->pagetable, p->VMA[i].addr, PGROUNDDOWN(p->VMA[i].length) / PGSIZE, 1);// 减少文件的引用计数if(p->VMA[i].f)fileclose(p->VMA[i].f);}}// 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;}}...
}

修改fork

拷贝父进程的VMA数组到子进程的VMA数组。

int
fork(void)
{int i, pid;struct proc *np;struct proc *p = myproc();// Allocate process.if((np = allocproc()) == 0){return -1;}// Copy user memory from parent to child.if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){freeproc(np);release(&np->lock);return -1;}np->sz = p->sz;np->parent = p;// copy VMA form parent to child, and increase the reference count for a VMAfor(int i = 0; i < 16; i++) {if(p->VMA[i].valid && p->VMA[i].f) {np->VMA[i] = p->VMA[i];filedup(np->VMA[i].f); // 提升文件的引用计数}}...
}

注意要修改uvmcopy函数,将panic(“uvmcopy: page not present”)改成continue,因为此时可能存在未映射的物理页

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{pte_t *pte;uint64 pa, i;uint flags;char *mem;for(i = 0; i < sz; i += PGSIZE){if((pte = walk(old, i, 0)) == 0)panic("uvmcopy: pte should exist");if((*pte & PTE_V) == 0)panic("uvmcopy: page not present");continue;// panic("uvmcopy: page not present");pa = PTE2PA(*pte);flags = PTE_FLAGS(*pte);if((mem = kalloc()) == 0)goto err;memmove(mem, (char*)pa, PGSIZE);if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){kfree(mem);goto err;}}return 0;err:uvmunmap(new, 0, i / PGSIZE, 1);return -1;
}

测试

$ make qemu-gdb
(3.8s) 
== 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 (94.5s) (Old xv6.out.usertests failure log removed)
== Test time == 
time: OK 
Score: 140/140

改进

[mit6.s081] 笔记 Lab10: Mmap | 文件内存映射 | Miigon’s blog

阅读了大佬的博客,发现其实自己的代码有一些小bug和改进优化的地方。

1、首先是内存分配的位置有问题,应该从高地址向下,本文使用的是从低地址开始,这样可能会和进程使用的地址空间发生冲突。(但测试过程没有这个bug,应该是因为本文修改了uvmunmap和uvmcopy,其实这导致了进程的空间(p->sz)一直在增大,而没有在munmap后减小,换成从高地址向下分配才能解决这个问题)

2、没有特判在mmap的内存中使用munmap时,传入的参数在内存中挖洞的这种错误操作(正确操作应该是内存的前一部分或后一部分),这种错误操作应该直接返回-1

3、对于munmap之前mmap空间的部分内存的情况,这种情况应该及时修改VMA数组的一些参数(start和sz)

4、对于MAP_SHARE的映射,应该在uvmunmap函数中,对PTE有D标志位的页进行判断(代表内存被修改过),并写回磁盘,而本文是粗暴的将全部空间写回磁盘(效率低)

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

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

相关文章

书生·浦语大模型--第三节课笔记--基于 InternLM 和 LangChain 搭建你的知识库

文章目录 大模型开发范式RAGLangChain框架&#xff1a;构建向量数据库构建检索问答链优化建议web 部署 实践部分 大模型开发范式 LLM的局限性&#xff1a;时效性&#xff08;最新知识&#xff09;、专业能力有限&#xff08;垂直领域&#xff09;、定制化成本高&#xff08;个…

Android 仿快手视频列表,RecyclerView与Banner联动效果

这是看到群里讨论过快手APP的一个观看他人视频列表的一个联动效果&#xff0c;但是并不是完全按照这个软件的效果来做的&#xff0c;只是参考&#xff0c;并不是完全仿照这个软件来做的&#xff0c;没时间去优化排版问题了&#xff0c;请见谅&#xff0c;如图&#xff1a; 实现…

try:创作助手-python制作一个购物网站。

本文使用创作助手。 包含了美观的页面设计、购物内容、购物车和支付界面的功能。请注意&#xff0c;这只是一个基本示例&#xff0c;您可以根据自己的需求进行修改和扩展。 index.html: <!DOCTYPE html> <html> <head><title>Shopping Website</…

如何分析测试任务及需求(附分析流程)

测试分析 确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户/平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应的输出…

Swift 周报 第四十五期

文章目录 前言新闻和社区苹果或将扩充健康版图&#xff0c;为Apple Watch X铺路更新后的《Apple Developer Program 许可协议》现已发布 提案通过的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组整理周报的第四十五期&#xff0c;每个模块已初步成型。各位…

【Linux 内核源码分析】RCU机制

RCU 基本概念 Linux内核的RCU&#xff08;Read-Copy-Update&#xff09;机制是一种用于实现高效读取和并发更新数据结构的同步机制。它在保证读操作不被阻塞的同时&#xff0c;也能够保证数据的一致性。 RCU的核心思想是通过延迟资源释放来实现无锁读取&#xff0c;并且避免了…

IOS自动化测试元素定位

一、元素属性介绍 1、元素属性 2、查看各定位方式执行效率 二、iOS常用定位方法 1、accessibility_id 2、class_name 3、Xpath 4、ios_class_chain(类型链) 5、ios_predicate(谓词) 一个页面最基本组成单元是元素&#xff0c;想要定位一个元素&#xff0c;我们需…

Linux网络服务部署yum仓库

目录 一、网络文件 1.1.存储类型 1.2.FTP 文件传输协议 1.3.传输模式 二、内网搭建yum仓库 一、网络文件 1.1.存储类型 直连式存储&#xff1a;Direct-Attached Storage&#xff0c;简称DAS 存储区域网络&#xff1a;Storage Area Network&#xff0c;简称SAN&#xff0…

01-15

#include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);//判断是否有该数据库if(!db.contains("stuInfo.db")){//说明数据库不存在 则创建dbQSqlDatabase::addDatabase("QSQLITE")…

ELK之Filebeat输出日志格式设置及输出字段过滤和修改

一、Filebeat输出日志格式设置 1.1 编辑vim filebeat.yml文件,修改输出格式设置 # output to console output.console:codec.format: string: %{[@timestamp]} %{[message]}pretty: true### 1.2 测试 执行 ./filebeat -e 可以看到/tmp/access.log(目前文件里只有140.77.188…

【LV12 DAY9 ADC实验】

电压在1501mv~1800mv时&#xff0c;LED2、LED3、LED4、LED5点亮 电压在1001mv~1500mv时&#xff0c;LED2、LED3、LED4点亮 电压在501mv~1000mv时&#xff0c;LED2、LED3点亮 电压在0mv~500mv时&#xff0c;LED2闪烁 #include "exynos_4412.h"void delay(unsigned in…

TOMCAT乱码问题solve

解决使用tomcat服务器打开网页的时候出现中文乱码问题 1.解决tomcat部署完项目后访问项目出现中文乱码问题&#xff1a; 1.1、在tomcat目录的bin文件下找到catalina.bat修改216行左右 set"JAVA_OPTS%JAVA_OPTS% %JSSE_OPTS%" 修改为 set"JAVA_OPTS%JAVA_OPTS…

大语言模型系列-总述

大语言模型发展史 研究人员发现&#xff0c;扩展预训练模型&#xff08;Pre-training Language Model&#xff0c;PLM&#xff09;&#xff0c;例如扩展模型大小或数据大小&#xff0c;通常会提高下游任务的模型性能&#xff0c;模型大小从几十亿&#xff08;1 B 10亿&#x…

Mysql判断一个表中的数据是否在另一个表存在

方式一&#xff1a; 判断A表中有多少条数据在B表中【存在】,并且显示这些数据–EXISTS语句 select A.ID, A.NAME from 表A where EXISTS(select * from 表B where A.IDB.ID) 判断A表中有多少条数据在B表中【不存在】&#xff0c;并且显示这些数据–NOT EXISTS语句 select …

使用Go语言通过API获取代理IP并使用获取到的代理IP

目录 前言 【步骤一&#xff1a;获取代理IP列表】 【步骤二&#xff1a;使用代理IP发送请求】 【完整代码】 【总结】 前言 在网络爬虫、数据抓取等场景中&#xff0c;经常需要使用代理IP来隐藏真实的IP地址&#xff0c;以及增加请求的稳定性和安全性。本文将介绍如何使用…

ubuntu22: nvtop no gpu to monitor.

解决方法&#xff1a; 重新下载nvtop sudo apt update sudo apt -y install nvtop真是逆天 &#xff0c;ubuntu系统的nvidia driver突然坏了&#xff0c;然后我重装了nvidia driver, 之后用nvtop就出现这个问题了&#xff0c;但是逆天的是我竟然没有搜到一篇中文的帖子讲这个问…

NLP论文阅读记录 - 2021 | WOS 使用 GA-HC 和 PSO-HC 改进新闻文章的文本摘要

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试 二.相关工作三.本文方法3.1 总结为两阶段学习3.1.1 基础系统 3.2 重构文本摘要 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Improved Text Summa…

云卷云舒:2023年,我眼中的十大数据库

我眼中的十大数据库&#xff0c;只要看成长性和演进速度&#xff08;个见勿怪&#xff09;。 一、五强 1、openGauss&#xff1a;生态影响力变大&#xff0c;基于高斯的产品层出不穷 2、OceanBase&#xff1a;只因霸榜&#xff0c;技术强大&#xff0c;新特性更新频繁&#x…

lv14 并发控制:上下文、中断屏蔽和原子变量

1 上下文和并发场合 执行流&#xff1a;有开始有结束总体顺序执行的一段代码 又称上下文 应用编程&#xff1a;任务上下文 内核编程&#xff1a; 任务上下文&#xff1a;五状态 可阻塞 a. 应用进程或线程运行在用户空间b. 应用进程或线程运行在内核空间&#xff08;通过调用…

MIT 6s081 lab8:locks

lab8: locks 作业地址&#xff1a;Lab: locks (mit.edu) Memory allocator (moderate) kalloc和kfree的多次调用&#xff0c;多次获取kmem锁&#xff0c;避免race-condition出现&#xff0c;但降低了内存分配的效率&#xff0c;本实验的目的&#xff1a;修改内存分配的程序&…