操作系统MIT 6.S081 xv6内核(六):Lazy Page Allocation实验
Eliminate allocation from sbrk()实验
没有实验,只谈现象
这是Fans教授在课上提到的现象,在基础理论一文的Lazy Allocation一节我也具体剖析过,看完不难理解:只增加了虚拟内存,uvmunmap释放了虚拟内存指向的不存在的物理内存。
Lazy allocation实验
实验目的
实现一个简单的Lazy
allocation,要求进程使用sbrk进行内存分配时不要直接满足,引入懒分配策略等到进程使用到了再申请物理内存,如果设计是成功的,那么shell能够正常运行echo hi
命令。
具体实现
这是一个简单的Lazy
allocation实现,在基础理论已经实现并且做了剖析讨论,看过基础理论或者课程的可以直接跳到第三个实验。具体步骤是在kernel/sysproc.c
中sbrk函数增加虚拟内存,取消物理内存分配
1
2
3 myproc()->sz=myproc()->sz+n;//new
//if(growproc(n) < 0)
// return -1;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15else if(r_scause()==15||r_scause()==13){ //pagefault或者load page fault
uint64 va=r_stval();
printf("page fault stval=%p\n",va);
uint64 pa=(uint64)kalloc(); //触发page fault才分配物理内存
if(pa==0){
p->killed=1;
}
else{
memset((void*)pa,0,PGSIZE);
if(mappages(p->pagetable,PGROUNDDOWN(va),PGSIZE,pa,PTE_U|PTE_R|PTE_W)!=0){
kfree((void*)pa);
p->killed=1;
}
}
}kernel/vm.c
的uvmunmap
函数:
1
2
3if(PTE_FLAGS(*pte) == PTE_V)
//panic("uvmunmap: not mapped");
continue;
Lazytests and Usertests实验
实验目的
本节主要在第二节的基础上,解决以下问题: 1.
处理sbrk()
参数为负的情况。
2.
如果某个进程在高于sbrk()
分配的任何虚拟内存地址上出现页错误,则终止该进程。
3. 在fork()
中正确处理父到子内存拷贝。
4.
处理这种情形:进程从sbrk()
向系统调用(如read
或write
)传递有效地址,但尚未分配该地址的内存。
5. 处理用户栈下面的无效页面上发生的错误。
具体实现
针对五个问题逐步实现:
正数增加虚拟内存,负数仍然和原来一致即可,即调用
growproc
函数进行解除映射;kernel/sysproc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
if(n<0){ //缩减内存
if(myproc()->sz+n<0||growproc(n)<0) //缩减完不能是负数
return -1;
}
else
myproc()->sz=myproc()->sz+n;//虚拟内存增加
//if(growproc(n) < 0)
// return -1;
return addr;
}将第二个和第五个问题一起讨论:有两种页面错误不应该是页懒分配引起的,页懒分配处理的page fault发生的位置一定是用户进程的进程空间,也即内核分配的虚拟地址空间,包括代码段、数据段、BSS段、堆栈段、trapframe、trampoline等。 注意这个图是整个内存的用户空间分布,一个段可能被多个用户进程使用。
因此一个进程中,页懒分配的虚拟地址的下边界应该
guard page
以上,具体而言应该是栈顶指针sp
以上。上边界是MAXVA
,但因为进程维护了sz
大小字段,因此每个进程不超过p->sz
;因此在kernel/trap.c
增加判断:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20else if(r_scause()==15||r_scause()==13){
uint64 va=r_stval();
printf("page fault stval=%p\n",va);
if(va>p->sz||va<PGROUNDDOWN(p->trapframe->sp)){ //新增:for address higher than lazy allocation or lower than stack
//printf("Not Suit pagefault\n");
exit(-1);
}
//printf("page fault stval=%p\n",va);
uint64 pa=(uint64)kalloc();
if(pa==0){
p->killed=1;
}
else{
memset((void*)pa,0,PGSIZE);
if(mappages(p->pagetable,PGROUNDDOWN(va),PGSIZE,pa,PTE_U|PTE_R|PTE_W)!=0){
kfree((void*)pa);
p->killed=1;
}
}
}对于第三个
fork
的拷贝问题,实际上就是kernel/sysproc.c
的fork
函数中,uvmcopy
函数会尝试调用walk
去解析页表,而在页懒分配中出现页表项无效、不存在均是合理的,因此不应该报panic,uvmcopy
修改为:对应的,1
2
3
4
5
6if((pte = walk(old, i, 0)) == 0)
//panic("uvmcopy: pte should exist");
continue;
if((*pte & PTE_V) == 0)
continue;
//panic("uvmcopy: page not present");uvmunmap
也修改:在实验2只注释了if((*pte & PTE_V) == 0)
的情况,为了通过test3在这里调用也是需要continue的。
1
2
3if((pte = walk(pagetable, a, 0)) == 0)
//panic("uvmunmap: walk");
continue;最后是一些系统调用的影响,例如
write
和read
。write
和read
作为系统调用,和其他系统调用不同的是,其他系统调用没有从用户端向内核传递参数,回想我们剖析的那个sysinfo
调用,用户态调用函数,向内核通过寄存器a7传递了系统调用号,内核陷入找到对应的系统调用函数就开始处理了,最后系统将内核获取到的sysinfo
参数通过copyout
传递给用户空间;write
和read
多了一步,它需要告诉内核需要从哪里读什么数据,或者写什么数据到哪里,因此还会涉及copyin
。用户传递参数给内核,但是内核并不能通过页表找到它的物理内存(因为用户态没有分配),因此内核要完成这样一部分工作:遇到没有分配内存、分配标志位有效位PTE_V
为0的情况需要对其分配内存。因此可以知道,copyin、copyout中的页表索引也需要修改,它们都是通过函数
workaddr
将虚拟地址va转换成物理地址,但是·解析到pte == 0
以及(*pte & PTE_V) == 0
都是直接return了,因此这里需要修改成分配内存:其中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
28uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
pte_t *pte;
uint64 pa;
if(va >= MAXVA)
return 0;
if(pte == 0){
if(lazy_alloc(va,pagetable)<0){
return 0;
}
}
//return 0;
if((*pte & PTE_V) == 0){
if(lazy_alloc(va,pagetable)<0){
return 0;
}
}
//return 0;
if((*pte & PTE_U) == 0)
return 0;
pa = PTE2PA(*pte);
return pa+off;
}lazy_alloc
函数实现,不要忘记在kernel/defs.h
添加声明:1
2
3
4
5
6
7
8
9
10
11
12
13
14int lazy_alloc(uint64 va,pagetable_t pagetable){
if(va>PGROUNDDOWN(myproc()->trapframe->sp)&&va<myproc()->sz){
uint64 pa = (uint64)kalloc();
if(pa==0)
return -1;
memset((void*)pa,0,PGSIZE);
if(mappages(pagetable,PGROUNDDOWN(va),PGSIZE,pa,PTE_U|PTE_R|PTE_W)<0){
kfree((void*)pa);
return -1;
}
return 0;
}
return -1;
}
至此,整个lab也做完了,运行: 1
2lazytests
usertests
完整commit请参考:Lazy test3 Done