Lab Mmap
前置知识
lab lazy, lab file
Task: mmap
这个task难度较大,要求我们将文件映射到用户地址,让用户程序可以直接读写操作文件。
- 首先定义结构体mvma,将它添加到proc结构体中并初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// proc.h
struct mvma {
uint64 addr; // 起始地址
int len; // 文件长度
int offset; // 偏移量(在这个lab中始终为0)
int flags; // 标志位
int protection; // 权限
int fd; // 文件描述符
struct file *fl; // 文件
int used; // 是否正在被使用
};
...
char name[16]; // Process name (debugging)
struct mvma vma[NVMA]; //记得去宏定义
···
// allocproc()
found:
...
memset(&p->vma, 0, sizeof (p->vma)); - 添加syscall mmap, munmap(在sysfile中),注意在user.h中的函数声明形式
1
2
3// user.h
void* mmap(void*, int, int, int, int, int);
int munmap(void*, int); - 为sys_mmap添加内容。具体做法类似lazy,每次寻找一个可用的vma,储存参数,增加文件引用次数,增加page计数但不分配物理页。
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
44uint64
sys_mmap(void)
{
struct proc*p = myproc();
int len, offset, prot, flags, fd;
uint64 addr;
struct file *f;
if (argaddr(0, &addr) < 0 || argint(1, &len) < 0 || argint(2, &prot) < 0 || argint(3, &flags) < 0 || argfd(4, &fd, &f) < 0 || argint(5, &offset) < 0)
goto bad;
len = PGROUNDUP(len);
if(addr != 0 || offset != 0 || len < 0) // 根据实验手册要求进行检查
goto bad;
if (!f->writable && (prot & PROT_WRITE) && (flags == MAP_SHARED))
goto bad;
if (MAXVA - len < p->sz) // 越界
goto bad;
if (!f->readable && (prot & PROT_READ)) // 可读性
goto bad;
for (int i = 0; i < NVMA; i++)
{
if (p->vma[i].used == 0) {
struct mvma* crvma = &p->vma[i];
crvma->used = 1;
crvma->addr = p->sz; // 注意顺序!!!
p->sz += len; // 注意顺序!!!
crvma->len = len;
crvma->flags = flags;
crvma->protection = prot;
crvma->fd = fd;
crvma->offset = offset;
crvma->fl = f;
filedup(f); // 增加引用计数
return crvma->addr;
}
}
goto bad;
bad: return 0xffffffffffffffff;
} - 类似lazy,因为我们没有分配物理页,因此当进程访问这个文件时会发生pagefault。此时进入usertrap,我们需要在里面进行物理页分配。
注意:在usertrap中非常容易出现错误。比如,任意环节出现需要kill进程的时候设置完p->killed = 1后不能向后执行,否则sbrkfail不过。再比如,小心使用goto,因为在这里面出现问题后并不是直接返回,而是设置killed位后统一调用exit。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
50
51
52
53
54
55
56
57
58// trap.c
else if (r_scause() == 13 || r_scause() == 15) {
uint64 va = r_stval();
struct proc *p = myproc();
if (va > MAXVA || va > p->sz) {
p->killed = 1;
}
else {
struct mvma* crvma = 0;
int found = 0;
for (int i = 0; i < NVMA; i++) //通过比对地址与长度来寻找特定的mvma
{
crvma = &p->vma[i];
if (crvma->used && va >= crvma->addr && va < crvma->addr + crvma->len) {
found = 1;
break;
}
}
if (!found){
p->killed = 1;
goto bad;
}
va = PGROUNDDOWN(va); // 地址对齐
uint64 pa = (uint64)kalloc();
if (pa == 0) {
p->killed = 1;
goto bad;
}
memset((void*)pa, 0, PGSIZE);
ilock(crvma->fl->ip);
if (readi(crvma->fl->ip, 0, pa, crvma->offset + va - crvma->addr, PGSIZE) < 0) {
iunlock(crvma->fl->ip);
p->killed = 1;
goto bad;
} // 读取文件内容
iunlock(crvma->fl->ip); // 对文件操作需要锁
int flag = PTE_U; // 设立标志位
if (crvma->protection & PROT_READ)
flag |= PTE_R;
if (crvma->protection & PROT_WRITE)
flag |= PTE_W;
if (crvma->protection & PROT_EXEC)
flag |= PTE_X;
if (mappages(p->pagetable, va, PGSIZE, pa, flag) < 0) { // 添加映射
kfree((void*)pa);
p->killed = 1;
goto bad;
}
}
bad:;
//printf("mmap trap failed");
} - 完成munmap。这里面除了取消映射,写回文件以外,还需要根据munmap操作的文件位置分类操作。当操作的是整个文件的时候,记得减少引用计数,当操作的是部分文件的时候(头部或尾部,中间部分不在lab范围内),需要对mvma的地址,长度等进行相应修改。
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
50
51
52
53uint64
sys_munmap(void)
{
struct proc *p = myproc();
uint64 addr;
int len;
struct mvma *crvma;
if (argaddr(0, &addr) < 0 || argint(1, &len) < 0)
return -1;
for (int i = 0; i <= NVMA; i++)
{
if (i == NVMA){
return -1;
}
crvma = &p->vma[i];
if (crvma->used && addr >= crvma->addr && addr <= crvma->addr + crvma->len) {
break;
}
}
addr = PGROUNDDOWN(addr);
len = PGROUNDUP(len); // 长度取整
if (crvma->flags & MAP_SHARED) {
if (filewrite(crvma->fl, addr, len) < 0){
//printf("filewrite failed");
//return -1; // why can't go error?
}
}
uvmunmap(p->pagetable, addr, len / PGSIZE, 1);
if (addr == crvma->addr && len == crvma->len) { // all pages
fileclose(crvma->fl);
crvma->used = 0;
}
else if(addr == crvma->addr) { // start pages
crvma->addr += len;
crvma->len -= len;
crvma->offset += len;
}
else if((addr + len) == (crvma->addr + crvma->len)) { // end pages
crvma->len -= len;
}
else {
printf("Not allowed in this lab");
return -1;
}
return 0;
} - 回忆lazy lab,需要将uvmunmap和uvmcopy中的panic continue掉。
1
2
3
4// uvmunmap(), uvmcopy
if((*pte & PTE_V) == 0)
continue;
// panic("uvmunmap: not mapped"); - 修改exit,取消对应映射并写回文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// proc.c
// unmap all the mmap pages
for (int i = 0; i < NVMA; i++)
{
if (p->vma[i].used)
{
if (p->vma[i].flags & MAP_SHARED)
filewrite(p->vma[i].fl, p->vma[i].addr, p->vma[i].len);
fileclose(p->vma[i].fl);
uvmunmap(p->pagetable, p->vma[i].addr, p->vma[i].len / PGSIZE, 1);
p->vma[i].used = 0;
}
}
begin_op(); - 修改fork,使子进程copy父进程的mvma。
1
2
3
4
5
6
7
8for (int i = 0; i < NVMA; i++)
{
if (p->vma[i].used)
{
memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
filedup(p->vma[i].fl);
}
}