代码在Github上。

这一个实验是要实现最基础的mmap功能。mmap即内存映射文件,将一个文件直接映射到内存当中,之后对文件的读写就可以直接通过对内存进行读写来进行,而对文件的同步则由操作系统来负责完成。使用mmap可以避免对文件大量readwrite操作带来的内核缓冲区和用户缓冲区之间的频繁的数据拷贝。在Kafka消息队列等软件中借助mmap来实现零拷贝(zero-copy)。

首先定义vma结构体用于保存内存映射信息,并在proc结构体中加入struct vma *vma指针:

#define NVMA 16
#define VMA_START (MAXVA / 2)
struct vma{
uint64 start;
uint64 end;
uint64 length; // 0 means vma not used
uint64 off;
int permission;
int flags;
struct file *file;
struct vma *next; struct spinlock lock;
}; // Per-process state
struct proc {
...
struct vma *vma;
...
};

之后实现对vma分配的代码:

struct vma vma_list[NVMA];

struct vma* vma_alloc(){
for(int i = 0; i < NVMA; i++){
acquire(&vma_list[i].lock);
if(vma_list[i].length == 0){
return &vma_list[i];
}else{
release(&vma_list[i].lock);
}
}
panic("no enough vma");
}

实现mmap系统调用,这个函数主要就是申请一个vma,之后查找一块空闲内存,填入相关信息,将vma插入到进程的vma链表中去:

uint64
sys_mmap(void)
{
uint64 addr;
int length, prot, flags, fd, offset;
if(argaddr(0, &addr) < 0 || argint(1, &length) < 0 || argint(2, &prot) < 0 || argint(3, &flags) < 0 || argint(4, &fd) < 0 || argint(5, &offset) < 0){
return -1;
} if(addr != 0)
panic("mmap: addr not 0");
if(offset != 0)
panic("mmap: offset not 0"); struct proc *p = myproc();
struct file* f = p->ofile[fd]; int pte_flag = PTE_U;
if (prot & PROT_WRITE) {
if(!f->writable && !(flags & MAP_PRIVATE)) return -1; // map to a unwritable file with PROT_WRITE
pte_flag |= PTE_W;
}
if (prot & PROT_READ) {
if(!f->readable) return -1; // map to a unreadable file with PROT_READ
pte_flag |= PTE_R;
} struct vma* v = vma_alloc();
v->permission = pte_flag;
v->length = length;
v->off = offset;
v->file = myproc()->ofile[fd];
v->flags = flags;
filedup(f);
struct vma* pv = p->vma;
if(pv == 0){
v->start = VMA_START;
v->end = v->start + length;
p->vma = v;
}else{
while(pv->next) pv = pv->next;
v->start = PGROUNDUP(pv->end);
v->end = v->start + length;
pv->next = v;
v->next = 0;
}
addr = v->start;
printf("mmap: [%p, %p)\n", addr, v->end); release(&v->lock);
return addr;
}

接下来就可以在usertrap中对缺页中断进行处理:查找进程的vma链表,判断该地址是否为映射地址,如果不是就说明出错,直接返回;如果在vma链表中,就可以申请并映射一个页面,之后根据vma从对应的文件中读取数据:

int
mmap_handler(uint64 va, int scause)
{
struct proc *p = myproc();
struct vma* v = p->vma;
while(v != 0){
if(va >= v->start && va < v->end){
break;
}
//printf("%p\n", v);
v = v->next;
} if(v == 0) return -1; // not mmap addr
if(scause == 13 && !(v->permission & PTE_R)) return -2; // unreadable vma
if(scause == 15 && !(v->permission & PTE_W)) return -3; // unwritable vma // load page from file
va = PGROUNDDOWN(va);
char* mem = kalloc();
if (mem == 0) return -4; // kalloc failed memset(mem, 0, PGSIZE); if(mappages(p->pagetable, va, PGSIZE, (uint64)mem, v->permission) != 0){
kfree(mem);
return -5; // map page failed
} struct file *f = v->file;
ilock(f->ip);
readi(f->ip, 0, (uint64)mem, v->off + va - v->start, PGSIZE);
iunlock(f->ip);
return 0;
}

之后就是munmap的实现,同样先从链表中找到对应的vma结构体,之后根据三种不同情况(头部、尾部、整个)来写回并释放对应的页面并更新vma信息,如果整个区域都被释放就将vma和文件释放。

uint64
sys_munmap(void)
{
uint64 addr;
int length;
if(argaddr(0, &addr) < 0 || argint(1, &length) < 0){
return -1;
} struct proc *p = myproc();
struct vma *v = p->vma;
struct vma *pre = 0;
while(v != 0){
if(addr >= v->start && addr < v->end) break; // found
pre = v;
v = v->next;
} if(v == 0) return -1; // not mapped
printf("munmap: %p %d\n", addr, length);
if(addr != v->start && addr + length != v->end) panic("munmap middle of vma"); if(addr == v->start){
writeback(v, addr, length);
uvmunmap(p->pagetable, addr, length / PGSIZE, 1);
if(length == v->length){
// free all
fileclose(v->file);
if(pre == 0){
p->vma = v->next; // head
}else{
pre->next = v->next;
v->next = 0;
}
acquire(&v->lock);
v->length = 0;
release(&v->lock);
}else{
// free head
v->start -= length;
v->off += length;
v->length -= length;
}
}else{
// free tail
v->length -= length;
v->end -= length;
}
return 0;
}

写回函数先判断是否需要写回,当需要写回时就仿照filewrite的实现,将数据写回到对应的文件当中去,这里的实现是直接写回所有页面,但实际可以根据PTE_D来判断内存是否被写入,如果没有写入就不用写回:

void
writeback(struct vma* v, uint64 addr, int n)
{
if(!(v->permission & PTE_W) || (v->flags & MAP_PRIVATE)) // no need to writeback
return; if((addr % PGSIZE) != 0)
panic("unmap: not aligned"); printf("starting writeback: %p %d\n", addr, n); struct file* f = v->file; 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);
printf("%p %d %d\n",addr + i, v->off + v->start - addr, n1);
int r = writei(f->ip, 1, addr + i, v->off + v->start - addr + i, n1);
iunlock(f->ip);
end_op();
i += r;
}
}

最后就是在fork当中复制vma到子进程,在exit中当前进程的vma链表释放,在exit时要对页面进行写回:

int
fork(void)
{
...
np->state = RUNNABLE; np->vma = 0;
struct vma *pv = p->vma;
struct vma *pre = 0;
while(pv){
struct vma *vma = vma_alloc();
vma->start = pv->start;
vma->end = pv->end;
vma->off = pv->off;
vma->length = pv->length;
vma->permission = pv->permission;
vma->flags = pv->flags;
vma->file = pv->file;
filedup(vma->file);
vma->next = 0;
if(pre == 0){
np->vma = vma;
}else{
pre->next = vma;
}
pre = vma;
release(&vma->lock);
pv = pv->next;
}
...
} void
exit(int status)
{
struct proc *p = myproc(); if(p == initproc)
panic("init exiting"); // munmap all mmap vma
struct vma* v = p->vma;
struct vma* pv;
while(v){
writeback(v, v->start, v->length);
uvmunmap(p->pagetable, v->start, PGROUNDUP(v->length) / PGSIZE, 1);
fileclose(v->file);
pv = v->next;
acquire(&v->lock);
v->next = 0;
v->length = 0;
release(&v->lock);
v = pv;
}
...
}

XV6学习(15)Lab mmap: Mmap的更多相关文章

  1. Java IO学习笔记三:MMAP与RandomAccessFile

    作者:Grey 原文地址:Java IO学习笔记三:MMAP与RandomAccessFile 关于RandomAccessFile 相较于前面提到的BufferedReader/Writer和Fil ...

  2. XV6学习笔记(2) :内存管理

    XV6学习笔记(2) :内存管理 在学习笔记1中,完成了对于pc启动和加载的过程.目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页.接 ...

  3. xv6学习笔记(5) : 锁与管道与多cpu

    xv6学习笔记(5) : 锁与管道与多cpu 1. xv6锁结构 1. xv6操作系统要求在内核临界区操作时中断必须关闭. 如果此时中断开启,那么可能会出现以下死锁情况: 进程A在内核态运行并拿下了p ...

  4. Kali视频学习1-5

    Kali视频学习1-5 安装 安装Kali虚拟机 设置网络更新,使用了163的源 deb http://mirrors.163.com/debian wheezy main non-free cont ...

  5. XV6学习笔记(1) : 启动与加载

    XV6学习笔记(1) 1. 启动与加载 首先我们先来分析pc的启动.其实这个都是老生常谈了,但是还是很重要的(也不知道面试官考不考这玩意), 1. 启动的第一件事-bios 首先启动的第一件事就是运行 ...

  6. xv6学习笔记(3):中断处理和系统调用

    xv6学习笔记(3):中断处理和系统调用 1. tvinit函数 这个函数位于main函数内 表明了就是设置idt表 void tvinit(void) { int i; for(i = 0; i & ...

  7. xv6学习笔记(4) : 进程调度

    xv6学习笔记(4) : 进程 xv6所有程序都是单进程.单线程程序.要明白这个概念才好继续往下看 1. XV6中进程相关的数据结构 在XV6中,与进程有关的数据结构如下 // Per-process ...

  8. XV6学习(16)Lab net: Network stack

    最后一个实验了,代码在Github上. 这一个实验其实挺简单的,就是要实现网卡的e1000_transmit和e1000_recv函数.不过看以前的实验好像还要实现上层socket相关的代码,今年就只 ...

  9. XV6学习(1) Lab util

    正在学习MIT的6.S081,把做的实验写一写吧. 实验的代码放在了Github上. 第一个实验是Lab util,算是一个热身的实验,没有涉及到系统的底层,就是使用系统调用来完成几个用户模式的小程序 ...

随机推荐

  1. 如何构建一个多人(.io) Web 游戏,第 2 部分

    原文:How to Build a Multiplayer (.io) Web Game, Part 2 探索 .io 游戏背后的后端服务器. 上篇:如何构建一个多人(.io) Web 游戏,第 1 ...

  2. three.js cannon.js物理引擎之约束

    今天郭先生继续说cannon.js,主演内容就是点对点约束和2D坐标转3D坐标.仍然以一个案例为例,场景由一个地面.若干网格组成的约束体和一些拥有初速度的球体组成,如下图.线案例请点击博客原文. 下面 ...

  3. nokogiri Fail install on Ruby 2.3 for Windows #1456 <From github>

    Q: gem install railson nokogiri install fail with error: 'nokogiri requires Ruby version < 2.3, & ...

  4. 前端知识(二)08-Vue.js的路由-谷粒学院

    目录 一.锚点的概念 二.路由的作用 三.路由实例 1.复制js资源 2.创建 路由.html 3.引入js 4.编写html 5.编写js 一.锚点的概念 案例:百度百科 特点:单页Web应用,预先 ...

  5. jvm源码解析java对象头

    认真学习过java的同学应该都知道,java对象由三个部分组成:对象头,实例数据,对齐填充,这三大部分扛起了java的大旗对象,实例数据其实就是我们对象中的数据,对齐填充是由于为了规则分配内存空间,j ...

  6. libuv中实现tcp服务器

    目录 1.说明 2.libuv的tcp server 3.API简介 3.1.uv_tcp_init 3.2.uv_ip4_addr 3.3.uv_tcp_bind 3.4.uv_listen 3.5 ...

  7. Python虚拟环境配置应用

    Python好用,但用好却不易,其中比较头疼的就是包管理和Python不同版本的问题,为了解决这些问题,有不少发行版的Python,比如WinPython.Anaconda等,这些发行版将python ...

  8. 抛弃 .NET 经典错误:object null reference , 使用安全扩展方法? 希望对大家有帮助---Bitter.Frame 引用类型的安全转换

    还是一样,我不喜欢长篇大论,除非关乎我设计思想领域的文章.大家过来看,都是想节省时间,能用白话表达的内容,绝不长篇大论.能直接上核心代码的,绝不上混淆代码. 长期从事 .NET 工作的人都知道..NE ...

  9. 当中台遇上DDD,我们该如何设计微服务? - InfoQ https://www.infoq.cn/article/7QgXyp4Jh3-5Pk6LydWw

    当中台遇上DDD,我们该如何设计微服务? - InfoQ https://www.infoq.cn/article/7QgXyp4Jh3-5Pk6LydWw

  10. JavaScript this 关键字详解

    一.前言 this关键字是JavaScript中最复杂的机制之一.它是一个很特别的关键字,被自动定义在所有函数的作用域中.对于那些没有投入时间学习this机制的JavaScript开发者来说,this ...