in linux
调用mmap,会申请一段内存空间(文件的内存映射部分),并且自动映射到指定的文件内存映射部分。
mmap
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr为用户指定的内存起始地址,为0时由系统分配。
length制定映射的长度,单位是字节。
prot制定映射页面的权限
- PROT_READ 映射内存可读
- PROT_WRITE 映射内存可写
- PROT_EXEC 映射内存可被执行
- PROT_NONE 映射内存不可访问
flags控制映射内存修改后的行为
- MAP_SHARED 进程共享映射内存,修改后会反映到文件中。精准控制修改时间需要用msyn。
- MAP_PRIVATE 进程私有映射内存,修改后不会反映到文件中。Copy on write
- MAP_FIXED 必须使用指定的映射内存的起始地址addr,页对齐。慎用,如果和其他映射内存重叠,会覆盖其他映射内存。
- MAP_ANONYMOUS 映射匿名,初始化为0,不与文件关联即fd和offset无效,有些实现要求强制fd为-1,及时不强制程序中最好也用-1
fd映射文件的文件描述符
offset映射文件的偏移量,即从文件的什么位置开始映射,必须是页对齐的
成功返回0;失败返回-1即MAP_FAILED,并设置errno。
open系统调用的权限必须和mmap的prot权限符合,具体为
- open必须可读
#define O_RDONLY 0x000
#define O_WRONLY 0x001
#define O_RDWR 0x002
- mmap 指定了PROT_WRITE和MAP_SHARED,open必须RDWR
进程终止内存映射需要单独调用munmap回收,映射完成后文件即可使用close,但实际并没有关闭,内存映射区域存在对文件的引用,导致并不会实际关闭。
使用场景
- 共享文件映射,使用MAP_SHARED
- 私有文件映射,使用MAP_PRIVATE,常用于加载动态共享库等程序
- 共享匿名映射,使用MAP_ANONYMOUS|MAP_SHREAD,常用于进程间通信
- 私有内存映射,用于分配较大的内存空间,供进程自身使用
munmap
int munmap(void *addr, size_t length);
指定范围内的所有页面都会被unmaped,成功返回0,失败返回-1并设置errno。除了程序内调用,进程终止时也会自动unmap。
不要求区域内页面是映射的,即使不包含任何映射页面,也不会出错。
一个例子
#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
int *p;
p=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED,-1,0);
if(fork()==0){
*p=100;
printf("child: %d\n",*p);
exit(0);
}else{
wait(NULL);
printf("parent: %d\n",*p);
}
int err=munmap(p,4);
printf("err: %d\n",err);
err = munmap(p-4096,4);
printf("err: %d\n",err);
}
// child: 100
// parent: 100
// err: 0
// err: 0
in xv6
不得不说,这个实验还是有难度的,做的时候有点面向测试用例编程的感觉了,但思路整体上应该没问题。
mmap
mmap的lab实现时,总是假设addr为0,offset为0,并且MAP_SHARED实际也可以不共享,只需要写回修改即可。
实现共享确实有点麻烦,需要考虑多个进程同时修改以及什么时候释放的问题,而且考虑到munmap可以释放部分区域,单个vma的引用计数也不足以解决问题,因此暂且不实现。一个简单的想法是在vma内同时维护原区域和实际区域,只有一个进程全部ummap时才递减引用计数,引用计数为0时才释放区域。
xv6里没有memory allocator,所以具体实现的时候简便即可,提供的方法是在进程内保存VMA数组。
具体vma里需要包含什么,其实文档说的很清楚了。
struct vma{
uint64 start;
uint64 len;
uint64 offset;
int prot;
int flags;
struct file *file;
};
mmap完成一下操作
- 检查权限是否匹配,这点文档没说,对着测试代码看的
- 找到合适的内存空间,为了避免和p->sz混淆带来的麻烦(需要修改很多函数,改变函数语义),选择从高地址TRAPFRAME下开始。并且维护一个vmastart作为内存中mmap分配的最低位置,uvmunmap时简单更新为现存映射空间最低位置。
- 找到进程中空闲的vma项,将信息存入。
sys_mmap的helper函数,用于检查权限是否匹配。
uint64 mmap(uint64 addr, uint64 len, int prot, int flags, int fd, uint64 offset){
int i;
struct vma* vmamems;
struct proc* p=myproc();
struct file* file = p->ofile[fd];
if(file == 0){
return -1;
}
if((prot & PROT_READ) && file->readable == 0){
return -1;
}
if((prot & PROT_WRITE) && p->ofile[fd]->writable == 0 && !(flags & MAP_PRIVATE)){
return -1;
}
len = PGROUNDUP(len);
if(addr == 0){
addr = p->vmastart - len;
}
vmamems=p->vmamems;
for(i=0;i<NVMA;i++){
if(vmamems[i].file == 0){
break;
}
}
if(i >= NVMA){
return -1;
}
vmamems[i].start=addr;
vmamems[i].len=len;
vmamems[i].offset=offset;
vmamems[i].prot=prot;
vmamems[i].flags=flags;
vmamems[i].file=p->ofile[fd];
p->vmastart = addr;
filedup(vmamems[i].file);
return addr;
}
munmap
在进程vma数组中找到对应项,低配版直接对比addr(实验保证了不会从中间进行ummap)。
完成写回文件和删除分配的物理内存,最后更改vma记录。
由于xv6日志一次最多修改MAXOPBLOCKS个块,得到的每次系统调用写入最多为3*BSIZE(为了省事我直接用PGSIZE了),因此需要循环写回(总感觉在本次lab下有些多余,但要写出能用的代码确实要这样,然而代码建立在不正确的假设上QAQ)。
sys_munmap的helper函数。
int munmap(uint64 addr, uint64 len){
int i;
struct proc * p = myproc();
struct vma* vmamems = p->vmamems;
for(i=0;i<NVMA;i++){
if(vmamems[i].file && vmamems[i].start == addr){
if(len > vmamems[i].len){
return -1;
}
uint64 offset = vmamems[i].offset;
uint64 finished = 0;
pte_t *pte;
while(finished < len){
if((pte = walk(p->pagetable, addr + finished, 0)) == 0){
return -1;
}
if(*pte & PTE_V)
{
uint64 r;
if((vmamems[i].flags & MAP_SHARED) &&*pte & PTE_D){
// 以下过程参考的file.c,虽然不知道这样判断的意义是什么
begin_op();
ilock(vmamems[i].file->ip);
if((r=writei(vmamems[i].file->ip, 1, addr + finished, offset, PGSIZE))>0){
offset += r;
}
iunlock(vmamems[i].file->ip);
end_op();
if(r != PGSIZE){
break;
}
}
uvmunmap(p->pagetable, addr + finished, 1, 1);
}
finished+=PGSIZE;
}
if(finished != len){
return -1;
}
vmamems[i].start += len;
vmamems[i].len -= len;
vmamems[i].offset = offset;
if(vmamems[i].len == 0){
fileclose(vmamems[i].file);
vmamems[i].file=0;
}
break;
}
}
if(i >= NVMA){
return -1;
}
uint64 vmastart = TRAPFRAME;
for(int i=0;i<NVMA;i++){
if(vmamems[i].file != 0){
vmastart = (vmastart > vmamems[i].start) ? vmamems[i].start : vmastart;
}
}
p->vmastart = vmastart;
return 0;
}
缺页处理程序,有上次写COW的经验这次写起来就很顺手了。
int MapHandler(uint64 addr){
struct proc *p = myproc();
struct vma* vmamems = p->vmamems;
uint64 va;
for(;vmamems < p->vmamems + NVMA; vmamems++){
if(vmamems->file && addr >= vmamems->start && addr < vmamems->start + vmamems->len){
break;
}
}
if(vmamems == NVMA + p->vmamems){
return -1;
}
uint64 mem = (uint64)kalloc();
if(mem == 0){
return -1;
}
memset((void*)mem, 0, PGSIZE);
va = PGROUNDDOWN(addr);
ilock(vmamems->file->ip);
readi(vmamems->file->ip, 0, mem, vmamems->offset + (va - vmamems->start), PGSIZE);
iunlock(vmamems->file->ip);
int flags = PTE_U;
if(vmamems->prot & PROT_WRITE)
flags |= PTE_W;
if(vmamems->prot & PROT_READ)
flags |= PTE_R;
if(vmamems->prot & PROT_EXEC)
flags |= PTE_X;
if(mappages(p->pagetable, va, PGSIZE, mem, flags) != 0){
kfree((void*)mem);
return -1;
}
return 0;
}
exit
for(int i=0;i<NVMA;i++){
if(p->vmamems[i].file){
munmap(p->vmamems[i].start, p->vmamems[i].len);
}
}
fork
for(int i=0;i<NVMA;i++){
np->vmamems[i] = p->vmamems[i];
if(p->vmamems[i].file){
filedup(p->vmamems[i].file);
}
}
标签:uint64,addr,映射,int,mmap,xv6,vmamems,file
From: https://www.cnblogs.com/wangerblog/p/17880021.html