lab10

mmap(hard)

系统调用的添加过程这里就不赘述了,首先,我们先添加vma结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct VMA{
uint64 addr; //起始地址
uint64 size; //大小
uint64 prot; //权限
int fd; //文件描述符
int used ; //引用计数
int offset; //偏移
struct file* file; //文件名
int flag; //标志
};

// Per-process state
struct proc {
struct spinlock lock;
...
struct VMA vma[16];
};

//在进程创建初始化
memset(&p->vma, 0, sizeof(p->vma));

同时,我们对mmap系统调用进行处理

  • 写回问题:当设置MAP_SHARED时要检查权限。
  • sbrk问题:我们只能在这里增加sbrk,因为mmap要求内存是连续的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
uint64
sys_mmap(void)
{
uint64 addr, err = 0xffffffffffffffff;
int len, prot, flags, fd, offset;
struct file *f;
if(argaddr(0, &addr) || argint(1, &len) || argint(2, &prot) || argint(3, &flags) ||
argfd(4, &fd, &f) || argint(5, &offset))
return err;

struct proc *p = myproc();
if(p->sz + len > MAXVA)
return err;
if(f->writable == 0 && (prot & PROT_WRITE) != 0 && flags == MAP_SHARED)
return err;


addr = p->sz;

for(int i = 0; i < MAX_MMAP_REGIONS; i++)
{
if(p->vma[i].used == 0)
{
p->vma[i].addr = addr;
p->vma[i].fd = fd;
p->vma[i].prot = prot;
p->vma[i].used = 1;
p->vma[i].size = len;
p->vma[i].offset = offset;
p->vma[i].file = f;
p->vma[i].flag = flags;
filedup(f);
break;
}
}
p->sz += len;
return addr;
}

接下来设置中断处理函数,吸取cow的教训,我们将函数实现放入vm.c实现模块化。

1
2
3
4
5
6
7
8
9
10
11
12
else if(r_scause() == 13 || r_scause() == 15)
{
uint64 va = r_stval();

if(va > p->sz || va < p->trapframe->sp)
p->killed = 1;
else if( mapfile(va)){
p->killed = 1;
}

}
else {
  1. 首先,索引对应页面,判断参数合法性
  2. 设置权限,取该虚拟地址的页表首地址,读入文件分配映射页面。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int mapfile(uint64 va)
{
int i;
int flag = PTE_U;
int prot;
char *mem;
int offset = 0;
struct proc* p = myproc();
for(i = 0; i < MAX_MMAP_REGIONS; i ++)
{
if(p->vma[i].used == 0) continue;
if(p->vma[i].addr <= va && p->vma[i].addr + p->vma[i].size - 1 >= va ) break;
}
if(i == MAX_MMAP_REGIONS)
{
return -1;
}



prot = p->vma[i].prot;
if(prot & PROT_READ) flag |= PTE_R;
if(prot & PROT_WRITE) flag |= PTE_W;
if(prot & PROT_EXEC) flag |= PTE_X;


if((mem = kalloc()) == 0)
return -1;
memset(mem, 0, PGSIZE);
offset = PGROUNDDOWN(p->vma[i].offset + va - p->vma[i].addr) ;
ilock(p->vma[i].file->ip);
if(readi(p->vma[i].file->ip, 0,(uint64)mem, offset, PGSIZE) == 0)
{
iunlock(p->vma[i].file->ip);
kfree(mem);
return -1;
}
iunlock(p->vma[i].file->ip);

if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)mem, flag) < 0)
{
kfree(mem);
return -1;
}
return 0;



}

接下来处理munmap,因为释放只会发生在映射区域首部和末尾,因此可以用size来判断文件是否已被全部解除映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
uint64
sys_munmap(void)
{
uint64 err = 0xffffffffffffffff;
uint64 addr;
int len;
int i;
struct proc *p = myproc();
if(argaddr(0, &addr) || argint(1, &len))
return err;

for(i = 0; i < MAX_MMAP_REGIONS; i ++)
{
if(p->vma[i].addr == 0) continue;
if(p->vma[i].size >= len)
{
if(addr == p->vma[i].addr)
{
p->vma[i].size -= len;
p->vma[i].addr += len;
break;
}
if(addr == p->vma[i].addr + len)
{
p->vma[i].size -= len;
break;
}

}
}
if(i == MAX_MMAP_REGIONS) return err;

if(p->vma[i].flag == MAP_SHARED &&(p->vma[i].prot & PROT_WRITE) != 0)
filewrite(p->vma[i].file, addr, len);
//这里的写回好像有点问题,
//如何处理偏移问题和crash问题。 TODO
uvmunmap(p->pagetable, addr, len/PGSIZE, 1);


if(p->vma[i].size == 0) {
fileclose(p->vma[i].file);
p->vma[i].used = 0;
}

return 0;
}

接下来是处理子进程问题,我们需要复制VMA结构体,并添加对文件的引用计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int
fork(void)
{
...

for(i = 0; i < MAX_MMAP_REGIONS; ++i) {
if(p->vma[i].used) {
memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
filedup(p->vma[i].file);
}
}

release(&np->lock);

return pid;
}

在退出时判断是否需要写入文件。由于我们的内存全部分配在堆上,所以可以由proc_freepagetable取消映射和释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
exit(int status)
{
struct proc *p = myproc();

if(p == initproc)
panic("init exiting");
...
for(int i = 0; i < MAX_MMAP_REGIONS; ++i) {
if(p->vma[i].used) {
if(p->vma[i].flag == MAP_SHARED && (p->vma[i].prot & PROT_WRITE) != 0) {
filewrite(p->vma[i].file, p->vma[i].addr, p->vma[i].size);
}
fileclose(p->vma[i].file);
p->vma[i].used = 0;
}
}