Lab: mmap
实验目标
在实验环境中实现 mmap() 系统调用
hints
-
char* mmap(void *addr, int length, int prot, int flags, int fd, int offset); int munmap(void *addr, int length);
-
mmap() 映射的页面应该是 lazy alloc 的,以保证在映射大文件时不会阻塞
-
每个进程应保持对 mmap() 映射的记录。创建一个符合 VMA 要求的结构体来保存它。每个进程的结构体数组应为16。
-
mmap() 的工作:在用户空间地址中寻找未映射的区域来映射文件,并用在进程信息中用结构体保存该映射的信息。该结构体应该保存指向被映射文件struct file 的指针,并且应该增加文件的引用次数
-
处理缺页中断。在缺页中断中读入 对应位置的4096字节 文件放入内存并映射
-
munmap() 的工作:取消特定页面的映射,并将MAP_SHARED页面写回磁盘。如果一个映射被完全取消,记得减少相对应的文件引用
-
你可以假设 munmap() 只从区域开头、结尾,或从中间开始到结尾的一整段开始,不会在中间打个洞这样子
-
在本实验中不用考虑是否为脏页
-
修改 exit() 来保证退出前所有的 mmap 已经取消
-
修改 fork() 来确保子进程也拥有正确的映射
实验实现
看过提示我们要做的工作大概有如下几点:
- 新增VMA数据结构,在struct proc 中新增VMA数据来保存 mmap() 信息
/* proc.h */
struct map_unit
{
int used;
uint64 addr;
int length;
int prot;
int flags;
int fd;
struct file *mfile;
int offset;
};
// Per-process state
struct proc {
......
struct map_unit map_addr[MAXMMAP]; // Trace map
};
- 新增mmap() 的系统调用,完成相对应的工作
/* sysfile.c */
uint64
sys_mmap(void)
{
uint64 addr;
int length;
int prot;
int flags;
int fd;
struct file *mfile;
int offset;
uint64 mmap_error = 0xffffffffffffffff;
if(argaddr(0, &addr) || argint(1, &length) || argint(2, &prot) || argint(3, &flags) || argfd(4, &fd, &mfile) || argint(5, &offset))
{
return mmap_error;
}
if(!mfile->writable && (prot & PROT_WRITE) && (flags == MAP_SHARED))
return mmap_error;
// printf("kernel flag : %d\n", fd);
struct proc *p = myproc();
if(p->sz > MAXVA - length)
return mmap_error;
for(int i = 0; i < MAXMMAP; ++i)
{
if(p->map_addr[i].used == 0)
{
p->map_addr[i].used = 1;
p->map_addr[i].addr = p->sz;
p->map_addr[i].length = length;
p->map_addr[i].prot = prot;
p->map_addr[i].flags = flags;
p->map_addr[i].fd = fd;
p->map_addr[i].mfile = mfile;
p->map_addr[i].offset = offset;
filedup(mfile);
p->sz = p->sz + length;
return p->map_addr[i].addr;
}
}
return mmap_error;
}
- 新增munmap() 的系统调用,完成相对应的工作
/* sysfile.c */
uint64
sys_munmap(void)
{
uint64 addr;
int length;
if(argaddr(0, &addr) || argint(1, &length))
return -1;
struct proc *p = myproc();
for(int i = 0; i < MAXMMAP; ++i)
{
if((p->map_addr[i].addr == addr) || (p->map_addr[i].addr + p->map_addr[i].length == addr + length))
{
if(p->map_addr[i].addr == addr) p->map_addr[i].addr += length;
p->map_addr[i].length -= length;
if((p->map_addr[i].flags & MAP_SHARED) && (p->map_addr[i].prot & PROT_WRITE))
filewrite(p->map_addr[i].mfile, addr, length);
/*
*这里没考虑addr是否页对齐,居然也过了,说明条件十分宽松了
*给来的地址都是页对齐的
*/
uvmunmap(p->pagetable, addr, length/PGSIZE, 1);
if(p->map_addr[i].length == 0)
{
/*
*
*这里写文件没有考虑偏移量也能过!
*十分不合理但也懒得完善嘿嘿
*
*/
fileclose(p->map_addr[i].mfile);
p->map_addr[i].used = 0;
}
break;
}
}
return 0;
}
- 修改 usertrap() 来处理缺页,要注意一个文件很大,由很多页组成,我们要根据缺页页地址与 VMA结构 里保存的虚拟地址的偏移量,来找到我们需要读入的部分
/* trap.c */
void
usertrap(void)
{
......
if(temp == 8)
{
......
}
else if((which_dev = devintr()) != 0)
{
......
}
else if (temp == 13 || temp == 15)
{
uint64 va = r_stval();
if(va >= p->sz || PGROUNDUP(va) == PGROUNDDOWN(p->trapframe->sp)) p->killed = 1;
else
{
for(int i = 0; i < MAXMMAP; ++i)
{
if(p->map_addr[i].used && p->map_addr[i].addr <= va && va < (p->map_addr[i].addr + p->map_addr[i].length))
{
uint64 pa = (uint64)kalloc();
if(pa == 0)
{
p->killed = 1;
break;
}
memset((void *)pa, 0, PGSIZE);
struct file *mfile = p->map_addr[i].mfile;
ilock(mfile->ip);
//计算要读取文件页面离文件开头的偏移量
int offset = PGROUNDDOWN(va - p->map_addr[i].addr);
int cnt = readi(mfile->ip, 0, pa, offset, PGSIZE);
if(cnt == 0)
{
iunlock(mfile->ip);
kfree((void *)pa);
p->killed = 1;
break;
}
iunlock(mfile->ip);
int flags = PTE_U;
if(p->map_addr[i].prot & PROT_READ) flags |= PTE_R;
if(p->map_addr[i].prot & PROT_WRITE) flags |= PTE_W;
if(p->map_addr[i].prot & PROT_EXEC) flags |= PTE_X;
if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, pa, flags) < 0)
{
kfree((void *)pa);
p->killed = 1;
}
break;
}
}
}
}
else
......
}
- 修改 fork() 来保证子进程拥有对父进程 mmap() 的记录
/* proc.c */
int
fork(void)
{
......
for(int i = 0; i < MAXMMAP; ++i)
{
if(p->map_addr[i].used)
{
memmove(&(np->map_addr[i]), &(p->map_addr[i]), sizeof(p->map_addr[i]));
filedup(p->map_addr[i].mfile);
}
}
np->state = RUNNABLE;
......
}
- 修改 exit() 来取消所有映射
/* proc.c */
void
exit(int status)
{
......
for(int i = 0; i < MAXMMAP; ++i)
{
if(p->map_addr[i].used)
{
if((p->map_addr[i].flags & MAP_SHARED) && (p->map_addr[i].prot & PROT_WRITE))
filewrite(p->map_addr[i].mfile, p->map_addr[i].addr, p->map_addr[i].length);
fileclose(p->map_addr[i].mfile);
uvmunmap(p->pagetable, p->map_addr[i].addr, p->map_addr[i].length/PGSIZE, 1);
p->map_addr[i].used = 0;
}
}
......
}