MIT 6.S081 操作系统 LAB5:Lazy allocation
Lab: xv6 lazy page allocation
实验的三个部分,逐步实现一个lazy allocation,我就按照自己的思路写,不分三个部分了
修改sbrk()
不直接分配物理内存,这也就是lazy allocation最本质的地方
- n>0 只改变
p->sz
- n<0 改变
p->sz
同时调用uvmdealloc()
取消对应的映射
uvmalloc()
中调用uvmunmap()
取消映射
uint64
sys_sbrk(void)
{
int addr;
int n;
struct proc* p=myproc();
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
if(p->sz+n<0)
return -1;
if(n<0)
{
uvmdealloc(p->pagetable,p->sz,p->sz+n);
}
p->sz=p->sz+n;
// if(growproc(n) < 0)
// return -1;
return addr;
}
handle page fault
判断r_scause()
的值等于13或15则发生了page fault
此时r_stval()
的值就是发生page fault的虚拟地址
分配对应的物理内存,逻辑类似于uvmalloc()
先调用kalloc()
分配内存,再调用mappages()
映射
这里如果内存资源已经用完了,则kalloc()
返回0,直接杀死进程就行
uint64 ka=(uint64)kalloc();
if(ka==0)
p->killed=1;
else{
memset((void*)ka,0,PGSIZE);
va=PGROUNDDOWN(va);
if(mappages(p->pagetable,va,PGSIZE,ka,PTE_W|PTE_U|PTE_R)!=0){
kfree((void*)ka);
p->killed=1;
}
}
modify uvmunmap() and uvmcopy()
因为lazy allocation允许了未映射的地址也是合法的
所以修改对应的两个函数,让它们在发现未映射的时候直接跳过就行了
if((pte = walk(old, i, 0)) == 0 || (*pte & PTE_V) == 0)
continue;
在fork()
里面会调用uvmcopy()
复制内存,按如上修改后就没问题了
negative sbrk() arguments
如果sbrk()
中n是负数,我们直接调用uvmunmap()
就可以了
因为我们已经修改了uvmunmap()
函数,不用管那部分地址有没有映射
如果没有映射,里面直接跳过就是了,如果映射了,就释放掉
if(n<0)
{
uvmdealloc(p->pagetable,p->sz,p->sz+n);
}
illegal address
有两种情况地址是不合法的
- va>=p->sz 超出了分配的最大空间
- 访问的是stack下面的guard page
所以在处理缺页中断的时候要先判断是否合法,如果不合法,则杀死进程
通过p->killed=1
杀死进程
if(va>=p->sz||is_guarded(p->pagetable,va))
p->killed=1;
is_guarded()
加入is_guarded()
函数判断是否是guard page
guard page有个特点,对应的虚拟地址分配了物理内存,但是pte中的flag没有设置PTE_U
就像这里的虚拟地址0x1000
一样,有对应的物理地址,flag为0x000f
所以参考walkaddr()
中的写法,如果发现一个虚拟地址,映射了对应的物理页,但没有设置PTE_U
,则是guard page
int
is_guarded(pagetable_t pagetable, uint64 va)
{
pte_t *pte;
if(va >= MAXVA)
return 0;
pte = walk(pagetable, va, 0);
if(pte == 0)
return 0;
if((*pte & PTE_V) == 0)
return 0;
if((*pte & PTE_U) == 0)
return 1;
return 0;
}
valid system call arguements
lazy allocation的一个特点是你不能让用户程序察觉到内存还未分配
因此当程序往system call传递一个合法的地址时,要分配对应内存
传递地址都要通过argaddr()
这个函数,所以在这里截胡
int
argaddr(int n, uint64 *ip)
{
*ip = argraw(n);
struct proc* p=myproc();
uint64 va=*ip,pa=walkaddr(p->pagetable,va);
if(va<p->sz&&!is_guarded(p->pagetable,va)&&pa==0)
{
uint64 ka=(uint64)kalloc();
if(ka==0)
p->killed=1;
else{
memset((void*)ka,0,PGSIZE);
va=PGROUNDDOWN(va);
if(mappages(p->pagetable,va,PGSIZE,ka,PTE_W|PTE_U|PTE_R)!=0){
kfree((void*)ka);
p->killed=1;
}
}
}
return 0;
}
只有在传递的地址合法,且对应内存未分配的情况下,我们才分配内存
通过walkaddr()
判断是否分配
这里我们只检查合法性,而不用检查非法情况
因为按原xv6代码中的注释,argaddr()
不检查合法性,交给copyin/copyout
去检查
Retrieve an argument as a pointer.
Doesn't check for legality, since
copyin/copyout will do that.