MIT xv6 2020系列实验:Lab10 mmap
实现两个功能:分别是mmap与munmap,将文件映射到内存当中,并为一个线程记录他管理的文件所在的页表目录。
函数原型如下:
char* mmap( char* addr, int len, int prot, int flags,int fd, int off);
int munmap( char* addr , int len );
其中mmap参数含义分别是映射地址(为0时由内核代码决定),映射内存长度,映射文件权限(read/write),映射类型(SHARED/PRIVATE,为SHARED则将文件写入到物存中),文件描述符,偏移位置。
为了实现映射,对于一个进程我们当然需要分配一块内存区域出来供mmap使用。这里我的思路是以文件为单元对物理内存区域进行记录,因为一个进程可以打开多个文件,所以要建立一个文件结构体数组用来表示已经映射的内存区域片:
#define VMA_MAX 16
struct VMA{
int valid;
uint64 addr;
int len;
int prot;
int flags;
int off;
struct file* f;
uint64 mapcnt;
};
struct proc {
//...
struct VMA vma[VMA_MAX]; // virtual memory address field arr
//...
};
至于内存分配策略,需要考虑是从堆区向下开始分还是栈区向上。
如果是从栈区开始,那么就是和sbrk绑定在一起,但这一部分有懒空间分配,所以从堆区,heap处向下分配。
对于sys_mmap,直接找到一个当前进程未使用的位置进行进程赋值。对于分配空间,直接从maxaddr向下分配。
uint64
sys_mmap(void){
uint64 addr;
int len , prot , flags , fd , off;
if( argaddr( 0 , &addr ) < 0 || argint( 1 , &len ) < 0 || argint( 2 , &prot ) < 0 || argint( 3 , &flags ) < 0 || argint( 4 , &fd ) < 0 || argint( 5 , &off ) < 0 )
return -1;
struct proc* p = myproc();
struct file* f = p->ofile[fd];
// to ensure the prot
if( ( flags == MAP_SHARED && f->writable == 0 && (prot&PROT_WRITE)) )
return -1;
// to find a empty vma and init it
int idx;
for( idx = 0 ; idx < VMA_MAX ; idx++ )
if( p->vma[idx].valid == 0 )
break;
if( idx == VMA_MAX )
panic("no empty vma field");
struct VMA* vp = &p->vma[idx];
vp->valid = 1;
vp->len = len;
vp->flags = flags;
vp->off = off;
vp->prot = prot;
vp->f = f;
filedup( f ); //increase the ref of the file
vp->addr = (p->maxaddr-=len); // asign a useable virtual address to the vma field , and maintain the maxaddr
return vp->addr;
}
对于sys_unmap,如果页表发生了修改,就进行写回操作。
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);
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中实现缺页,这里采用逐页分配,也即读到一处mmap也只分配相应那一页,不分配文件的所有页。
else if( r_scause() == 0xd )
{
uint64 addr = r_stval();
struct VMA* vp = 0;
//to fina the target vma
for( int i=0 ; i<VMA_MAX ; i++ )
if( p->vma[i].addr <= addr && addr < p->vma[i].addr + p->vma[i].len && p->vma[i].valid == 1 )
{
vp = &p->vma[i];
break;
}
if( vp != 0 )
{
uint64 mem = (uint64)kalloc();
memset( (void*)mem , 0 , PGSIZE );
if( -1 == mappages( p->pagetable , addr, PGSIZE , mem , PTE_U | PTE_V | ( vp->prot << 1 ) ) )
panic("pagefault map error");
vp->mapcnt += PGSIZE; //maintain the mapcnt
ilock( vp->f->ip );
readi( vp->f->ip , 0 , mem , addr-vp->addr , PGSIZE); //copy a page of the file from the disk
iunlock( vp->f->ip );
}
else
{
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
}
根据提示修改exit与fork两处系统调用:
修改完毕,测试通过。