lab5
Eliminate allocation from sbrk
将sys_sbrk()中的growproc函数调用删除,因为其是给新增加的堆空间分配内存的,我们现在需要惰性分配,所以并不真正分配空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
// lazy allocation
myproc()->sz += n;
return addr;
}
|
未分配的页表比如512项中只分配了两项,那么其余的均为0x00000000000000000。
Lazy allocation
(1). 修改usertrap()
(*kernel/trap.c*)函数,使用r_scause()
判断是否为页面错误,在页面错误处理的过程中,先判断发生错误的虚拟地址(r_stval()
读取)是否位于栈空间之上,进程大小(虚拟地址从0开始,进程大小表征了进程的最高虚拟地址)之下,然后分配物理内存并添加映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
uint64 cause = r_scause();
if(cause == 8) {
...
} else if((which_dev = devintr()) != 0) {
// ok
} else if(cause == 13 || cause == 15) {
// 处理页面错误
uint64 fault_va = r_stval(); // 产生页面错误的虚拟地址
char* pa; // 分配的物理地址
if(PGROUNDUP(p->trapframe->sp) - 1 < fault_va && fault_va < p->sz &&
(pa = kalloc()) != 0) {
memset(pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(fault_va), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) {
kfree(pa);
p->killed = 1;
}
} else {
// printf("usertrap(): out of memory!\n");
p->killed = 1;
}
} else {
...
}
|
(2). 修改uvmunmap()
(*kernel/vm.c*),之所以修改这部分代码是因为lazy allocation中首先并未实际分配内存,所以当解除映射关系的时候对于这部分内存要略过,而不是使系统崩溃,这部分在课程视频中已经解答。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
...
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
panic("uvmunmap: walk");
if((*pte & PTE_V) == 0)
continue;
...
}
}
|
Lazytests and Usertests
(1). 处理sbrk()
参数为负数的情况,参考之前sbrk()
调用的growproc()
程序,如果为负数,就调用uvmdealloc()
函数,但需要限制缩减后的内存空间不能小于0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
struct proc* p = myproc();
addr = p->sz;
uint64 sz = p->sz;
if(n > 0) {
// lazy allocation
p->sz += n;
} else if(sz + n > 0) {
sz = uvmdealloc(p->pagetable, sz, sz + n);
p->sz = sz;
} else {
return -1;
}
return addr;
}
|
(2). 正确处理fork
的内存拷贝:fork
调用了uvmcopy
进行内存拷贝,所以修改uvmcopy
如下
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
...
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
continue;
if((*pte & PTE_V) == 0)
continue;
...
}
...
}
|
(3). 还需要继续修改uvmunmap
,否则会运行出错,关于为什么要使用两个continue
,请看本文最下面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
...
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
continue;
if((*pte & PTE_V) == 0)
continue;
...
}
}
|
(4). 处理通过sbrk申请内存后还未实际分配就传给系统调用使用的情况,系统调用的处理会陷入内核,scause寄存器存储的值是8,如果此时传入的地址还未实际分配,就不能走到上文usertrap中判断scause是13或15后进行内存分配的代码,syscall执行就会失败
- 系统调用流程:
- 陷入内核**==>
usertrap
中r_scause()==8
的分支==>syscall()
==>**回到用户空间
- 页面错误流程:
- 陷入内核**==>
usertrap
中r_scause()==13||r_scause()==15
的分支==>分配内存==>**回到用户空间
因此就需要找到在何时系统调用会使用这些地址,将地址传入系统调用后,会通过argaddr
函数(*kernel/syscall.c*)从寄存器中读取,因此在这里添加物理内存分配的代码
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
|
int
argaddr(int n, uint64 *ip)
{
*ip = argraw(n);
struct proc* p = myproc();
// 处理向系统调用传入lazy allocation地址的情况
if(walkaddr(p->pagetable, *ip) == 0) {
if(PGROUNDUP(p->trapframe->sp) - 1 < *ip && *ip < p->sz) {
char* pa = kalloc();
if(pa == 0)
return -1;
memset(pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(*ip), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) {
kfree(pa);
return -1;
}
} else {
return -1;
}
}
return 0;
}
|