学!
课里面老师写过了…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
myproc()->sz += n;
// if(growproc(n) < 0)
// return -1;
return addr;
}
|
暂时没写 n < 0 的情况.
也是课上写了的()
这里实现就直接用 uvmalloc
了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
void
usertrap(void)
{
...
else if((which_dev = devintr()) != 0){
// ok
} else if (r_scause() == 13 || r_scause() == 15) {
uint64 va = r_stval();
if (uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0)
p->killed = 1;
} 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;
}
...
}
|
然后处理因为 lazy allocate 但是没有使用的 page 被 free 的情况. 因为没有使用 map, 所以 pte 是 0, 所以直接 continue, 在宏观层面看起来就是先 allocate 然后被 free 了.
1
2
3
4
5
6
7
8
9
10
11
12
|
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
...
if((pte = walk(pagetable, a, 0)) == 0)
// panic("uvmunmap: walk");
continue;
if((*pte & PTE_V) == 0)
// panic("uvmunmap: not mapped");
continue;
...
}
|
Lazytests and Usertests
处理 sbrk 缩小 heap
很简单, 只需要把剔除掉的 page dealloc 一下就行, 用 uvmdealloc
这个函数即可.
1
2
3
4
5
6
7
8
9
10
11
12
|
uint64
sys_sbrk(void)
{
...
argint(0, &n);
addr = p->sz;
p->sz += n;
if (n < 0)
uvmdealloc(p->pagetable, addr, p->sz);
...
}
|
处理虚拟地址不合法 (包括越界, 访问 stack guard page), 内存溢出
越界判断一下 va >= p->sz
即可, 内存溢出由于用 uvmalloc
, 失败会返回 0, 所以判断一下返回值, 是 0 就溢出了.
guard page 是没有映射的一个部分, 它检查栈溢出的机制就是利用访问非法地址造成 page fault. 在处理的时候不能当作 lazy page, 要检查一下.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void
usertrap(void)
{
...
else if (r_scause() == 13 || r_scause() == 15) {
uint64 va = r_stval();
if (va >= p->sz ||
(p->trapframe->sp - PGSIZE <= va && va < p->trapframe->sp) ||
uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0)
p->killed = 1;
}
...
}
|
处理内核态时访问未映射的页面
用户态可以通过 page falut 陷入内核, 内核在 trap 里处理. 但是内核态访问内存如 fork 需要复制所有页面 (现在还没写 cow), read 或 write 或者其他时候, 会访问一个暂时没分配的用户的 page.
fork 的情况比较简单, 因为父进程没有分配的页面一定是没有使用的, 那么子进程也不需要立马分配页面. 在 vmcopy
中把 panic 去掉即可.
1
2
3
4
5
6
7
8
9
10
11
12
|
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
...
if((pte = walk(old, i, 0)) == 0)
// panic("uvmcopy: pte should exist");
continue;
if((*pte & PTE_V) == 0)
// panic("uvmcopy: page not present");
continue;
...
}
|
read 或 wirte 系统调用访问 page 的时候, 是通过用户的页表来找对应的物理地址的. walkaddr(pagetable_t pagetable, uint64 va)
实现了这个功能.
在 walkaddr 中, 可能就会碰到 va 合法, 但是由于没有分配页面, 导致 pte 为空或者 invalid 的情况. 需要判断并分配页面. 注意需要判断合法, 即 va 是否超过 p->sz, 是否访问的是 guard page. 同时也需要判断内存溢出. 不合法返回 0, 而没内存了只能 kill 了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
...
struct proc *p = myproc();
pte = walk(pagetable, va, 0);
if(pte == 0 || (*pte & PTE_V) == 0) {
// return 0;
if (va >= p->sz || (p->trapframe->sp - PGSIZE <= va && va < p->trapframe->sp))
return 0;
if (uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0) {
p->killed = 1;
exit(-1);
}
}
// if((*pte & PTE_V) == 0)
// return 0;
...
}
|
有点麻烦, 但是和之前的选做比应该是更简单有点. 再说.