2020 MIT6.S081 lab5:lazy page allocation
简介
(前几次实验忘做笔记了,懒得写了(逃…))
本次lab需要完成lazy page allocation
在os中,当我们每次使用sbrk()函数增加堆空间时,要为每个page都重新初始化并分配虚拟内存。但通常情况我们用的内存小于所花费的空间,故进行lazy allocation策略,即是先让明面上的空间增加,但并不分配内存,在调用时访问虚拟内存发现page fault的时候则调用重新分配空间。
Eliminate allocation from sbrk
任务介绍:
改写sbrk()函数,返回的依然是原指针,但增加时不在此处分配内存。
在proc.h中的
s
z
sz
sz即为图中pagesize开始指向栈顶,增加内存后指针向上移动,栈为向下分配。
则注释掉原来的
g
r
o
w
p
r
o
c
growproc
growproc函数,直接修改
m
y
p
r
o
c
(
)
−
s
z
myproc()-sz
myproc()−sz即可
下面展示一些 内联代码片
。
// An highlighted block
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
myproc()->sz = myproc()->sz+n;
//if(growproc(n) < 0)
//return -1;
return addr;
}
Lazy allocation
修改usertrap中的代码,进入page fault后,对产生页面中断的页面进行页面分配
// An highlighted block
void usertrap(void)
{
int which_dev = 0;
if ((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// save user program counter.
p->trapframe->epc = r_sepc();
if (r_scause() == 8)
{
// system call
if (p->killed)
exit(-1);
// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->trapframe->epc += 4;
// an interrupt will change sstatus &c registers,
// so don't enable until done with those registers.
intr_on();
syscall();
}
else if ((which_dev = devintr()) != 0)
{
// ok
}
else if (r_scause() == 13 || r_scause() == 15)
{
uint64 va = r_stval();//产生中断的地址
if (va >= p->sz || va < PGROUNDDOWN(p->trapframe->sp))
{//判断va==p->sz的时候地址不可用,va==栈顶时可用
p->killed = 1;
}
else
{
uint64 bottom = PGROUNDDOWN(va);
//printf("%d\n", r_scause());
//printf("page fault %p\n", va);
char *mem = kalloc();
if (mem == 0)
{
p->killed = 1;
}
else
{
memset(mem, 0, PGSIZE);
if (mappages(p->pagetable, bottom, PGSIZE, (uint64)mem, PTE_X|PTE_W | PTE_U | PTE_R) != 0)
{
kfree(mem);
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;
}
if (p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if (which_dev == 2)
yield();
usertrapret();
}
这时我们再运行代码,依然会报错,因为在进程结束时,它释放内存的时候对栈0-sz的空间都进行了释放,但有些页是没有分配的。故修改uvmunmap函数
// An highlighted block
void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
uint64 a;
pte_t *pte;
if ((va % PGSIZE) != 0)
panic("uvmunmap: not aligned");
for (a = va; a < va + npages * PGSIZE; a += PGSIZE)
{//此时页面为未分配也正确
if ((pte = walk(pagetable, a, 0)) == 0)
continue;
if ((*pte & PTE_V) == 0)
continue;
if (PTE_FLAGS(*pte) == PTE_V)
panic("uvmunmap: not a leaf");
if (do_free)
{
uint64 pa = PTE2PA(*pte);
kfree((void *)pa);
}
*pte = 0;
}
}
但这还有改进的空间,目前只处理了sbrk增加的情况,减小同样需要考虑,空间减小时,我们释放内存。
// An highlighted block
uint64
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){
uvmunmap(myproc()->pagetable,0,PGROUNDUP(addr)/PGSIZE,1);
return -1;
}
else{
if(uvmdealloc(myproc()->pagetable,addr,addr+n)!=addr+n){
return -1;
}
}
}
myproc()->sz = myproc()->sz+n;
//if(growproc(n) < 0)
//return -1;
return addr;
}
fork时会调用uvmcopy函数,此函数中也有类似前者对未分配页的判定,修改。
// An highlighted block
if ((pte = walk(old, i, 0)) == 0)
continue;
if ((*pte & PTE_V) == 0)
continue;
考虑在调用walkaddr时也可能触发页面未分配(此处也是解决read和write问题的方法)。
// An highlighted block
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
pte_t *pte;
uint64 pa;
if (va >= MAXVA)
return 0;
struct proc *p = myproc();
pte = walk(pagetable, va, 0);
if (pte == 0 || (*pte & PTE_V) == 0){
if (va >= p->sz || va < PGROUNDDOWN(p->trapframe->sp)){
return 0;
}
char *mem=kalloc();
uint64 pa0=(uint64)mem;
memset(mem,0,PGSIZE);
mappages(pagetable,va,PGSIZE,pa0,PTE_X|PTE_W | PTE_U | PTE_R);
return (uint64)mem;
}
if ((*pte & PTE_U) == 0)
return 0;
pa = PTE2PA(*pte);
return pa;
}
o v e r ! over! over!