内存映射文件(Memory-Mapped File)是一种将文件内容映射到进程的虚拟地址空间的技术,使得文件的内容可以像内存一样被访问。
通过内存映射文件,可以高效地访问和操作文件内容
首先切换到mmap分支
git checkout mmap make clean
-
在Makefile中添加 $U/_mmaptest,同时添加mmap和munmap系统调用的声明定义,包括
kernel/syscall.h
,kernel/syscall.c
,user/usys.pl
和user/user.h
.
2.在 kernel/proc.h
中定义VMA结构体及数组
VMA存储了mmap-ed的信息,当一个进程调用mmap后,kernel会将已经mmap的信息记录到进程的一个VMA数组中(一次mmap对应一个VMA),strcut proc中有一个vma_pool字段存储所有的VMA,是一个固定大小的数组,当在mmap需要存储一个VMA时,就是从vma_pool中找一个空闲的位置并将VMA放进去
MAX_VMA_POOL定义为16
VMA结构体字段含义:
-
used:表明VMA是否已经使用,用于在vma_pool中寻空闲位置
-
addr:在内存中映射的内存地址
-
length:映射的大小
-
prot:mmap参数中的权限
-
flags:mmap参数中的flags
-
offset:mmap参数中的offset,一般恒为0
-
f:要映射的文件
3.在kernel/proc.c中的allocproc函数(分配新的进程结构体)中增加对vma_pool的初始化
4.在kernel/proc.c中实现vma_pool中分配和释放VMA的逻辑vma_alloc和vma_fre,同时将函数放到defs.h中
5. 实现sys_mmap()逻辑(有关mmap的系统调用已经在最开始设置好)
调用system call传入参数,检查权限,然后从vma_pool中分配一个空的vma位置,将其填充mmap的相关信息
6.实现sys_munmap()逻辑
解析调用system call的参数,在vma_pool中找到munmap的内存地址所对应的VMA,根据VMA的信息,将修改数据回写到文件中,然后更新VMA的信息,同时如果发现当初mmap的内存全部munmap,那就释放VMA
int
munmap_impl(uint64 addr, int length)
{
struct proc *p = myproc();
// 在 vma pool 中找到 addr 对应的 vma
struct VMA *vma = 0;
int i;
for (i = 0; i < MAX_VMA_POOL; i++) {
vma = p->vma_pool + i;
if (vma->used == 1 && addr >= vma->addr && (addr + length) < (vma->addr + vma->length)) {
break;
}
}
if (i > MAX_VMA_POOL) {
return -1;
}
// 根据 vma 的信息,将数据回写入文件中
uint64 begin_addr = addr;
uint64 end_addr = addr + length;
if (vma->flags == MAP_SHARED && vma->f->writable) {
uint64 cur_addr = begin_addr;
while (cur_addr < end_addr) {
int sz = end_addr - cur_addr >= PGSIZE? PGSIZE: end_addr - cur_addr;
begin_op();
ilock(vma->f->ip);
if (writei(vma->f->ip, 1, cur_addr, cur_addr - vma->addr, sz) != sz) {
return -1;
}
iunlock(vma->f->ip);
end_op();
uvmunmap(p->pagetable, cur_addr, 1, 1);
cur_addr += PGSIZE;
}
}
// 完成回写后,更新 vma 中的信息
//如果头部的取消映射,那么前面length地址长度的空间都无效了,所以需要+length
if (addr == vma->addr) { // 说明 addr 是 mmap 内存的头部
vma->addr += length;
vma->length -= length;
//如果不是头部而是尾部,那其实对vma的起始位置没有影响,直接长度减减即可
} else if (addr + length == vma->addr + vma->length) { // 说明 addr 是 mmap 的尾部
vma->length -= length;
}
// 如果 mmap 的内存全部被 munmmap,那需要释放 vma 以及对 file 的引用
if (vma->length == 0 && vma->used == 1) {
filedup(vma->f);
vma->used = 0;
}
return 0;
}
uint64 sys_munmap(void){
uint64 addr;
int length;
if(argaddr(0,&addr) < 0 || argint(1,&length) < 0){
return -1;
}
return (uint64) munmap_impl(addr,length);
}
7.在kernel/trap.c中的usertrap增加对page fault的处理,因为mmap只是分配了虚拟内存,并没有分配物理内存,当用户访问mmap的内存时会产生page fault,所以需要分配实际的物理内存
同时因为实现了COW,也就是mmap的内存是lazy allocation的,那么虚拟内存不一定有对应的物理内存,就需要修改kernel/vm.c中的uvmcopy
和uvmunmap
:
8.修改exit(kernel/proc.c)和fork(kernel/proc.c)
exit应当释放当前进程全部的mmap-ed的内存区域
fork应当让子进程拥有与父进程相同的mmaped
可以发现这里exit使用了munmap_impl函数,那么需要将该函数放到kernel/defs.h当中
测试