實驗總結
- 本次實驗用時約八個小時。
- 收獲是對線性地址的理解更深入了。
遇到的困難包括:
- 懶。
- xv6-riscv 默認開了 kpti(內核和用戶態頁表分離) ,故需要在各種系統調用頭部手動模擬 traverse 頁表的過程,以及模擬處理缺頁異常。(我現在覺得這不是一個很好的設計)
測試結果:
$ make grade
running lazytests:
(3.7s)
lazy: pte printout: OK
lazy: map: OK
lazy: unmap: OK
usertests:
(95.8s)
usertests: pgbug: OK
usertests: sbrkbugs: OK
usertests: argptest: OK
usertests: sbrkmuch: OK
usertests: sbrkfail: OK
usertests: sbrkarg: OK
usertests: stacktest: OK
usertests: all tests: OK
Score: 100/100
0. 實驗准備
上來直接:
$ cd xv6-riscv-fall19
$ git checkout lazy
實驗分為四個子任務(實際更多個):
- 設計一個輸出頁表的調試程序
vmprint(pagetable_t)
。 - 實現不立即分配內存的
sbrk
調用。 - 冒煙。
- 各種修復。
1. vmprint
void printwalk(pagetable_t pagetable, int depth)
{
// there are 2^9 = 512 PTEs in a page table.
for (int i = 0; i < 512; i++)
{
pte_t pte = pagetable[i];
if (pte & PTE_V)
{
for (int j = 0; j < depth; j++)
printf(" ..");
// printf("%d: pte %p pa %p %s%s%s\n", i, pte, PTE2PA(pte), (pte) & PTE_R ? "r" : "-", (pte) & PTE_W ? "w" : "-", (pte) & PTE_X ? "x" : "-");
printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));
}
if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0)
{
// this PTE points to a lower-level page table.
uint64 child = PTE2PA(pte);
printwalk((pagetable_t)child, depth + 1);
}
}
}
void vmprint(pagetable_t t)
{
printf("page table %p\n", t);
printwalk(t, 1);
}
2. 實現 lazy sbrk
非常好改,但通過 sbrk
輸入負數來歸還線性空間時,需要及時 demalloc
。
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
// if(growproc(n) < 0)
// return -1;
myproc()->sz += n;
if(n < 0)
uvmdealloc(myproc()->pagetable, addr, myproc()->sz);
return addr;
}
3 & 4. 冒煙 & 修復
首先需要正確處理缺頁異常,完成必要的檢查(訪問非法的線性地址 和 棧溢出):
//
// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
//
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->tf->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->tf->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) {
// ------------------------------------- here -------------------------------------
// 13: Page load fault, 15: Page store fault
// printf("usertrap(): page fault, scause %p pid=%d\n", r_scause(), p->pid);
// printf(" sepc=%p vaddr=%p\n", r_sepc(), r_stval());
struct proc *p = myproc();
pagetable_t pagetable = p->pagetable;
uint64 fault_vaddr = (uint64) r_stval();
uint64 vpage_base = PGROUNDDOWN(fault_vaddr);
if(fault_vaddr >= p->sz) {
// printf("usertrap(): page fault: invalid memory access to vaddr %p\n", fault_vaddr);
p->killed = 1;
goto end;
}
if(fault_vaddr < p->ustack) {
// printf("usertrap(): page fault: segfault on vaddr %p below stack %p\n", fault_vaddr, p->ustack);
p->killed = 1;
goto end;
}
char *mem = kalloc();
if(mem == 0) {
// printf("usertrap(): page fault: no more physical page available, killing process due to a OOM\n");
p->killed = 1;
goto end;
}
memset(mem, 0, PGSIZE);
if(mappages(pagetable, vpage_base, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
// printf("usertrap(): page fault: cannot map a page");
kfree(mem);
p->killed = 1;
goto end;
}
} 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;
}
end:
if(p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
usertrapret();
}
注意到為了能夠正確找到棧空間頂部的位置,我給 proc
數據結構加了一個新的 entry 叫做 ustack
。
對應需要修改 fork 和 exec 的代碼,因為在這里有新 proc 結構的生成,故需要維護這個屬性。
模擬頁表和缺頁的檢查代碼實現如下:
(注意到需要特判 pid = 1 的情況,是因為 xv6 的 initcode 中引用了大於用戶線性空間最大值 )proc->sz
(這個屬性由 sbrk 維護) 的內存地址,會觸發一個誤判,此為特例
經過與萬呆呆討論,無需特判,但我很懶我不改了。
int uvmchkaddr(pagetable_t pagetable, uint64 addr, uint64 size) {
struct proc* p = myproc();
char *mem;
pte_t *pte;
uint64 a, end;
if(p->pid > 1) {
if(addr >= p->sz) {
printf("uvmchkaddr(): page fault: invalid memory access to vaddr %p\n", addr);
return -1;
}
if(addr < p->ustack && addr >= p->ustack - PGSIZE) {
printf("uvmchkaddr(): page fault: segfault on vaddr %p on stack guard\n", addr);
return -1;
}
}
a = PGROUNDDOWN(addr);
end = PGROUNDUP(a + size);
for(; a < end; a += PGSIZE) {
if((pte = walk(pagetable, a, 1)) != 0 && (*pte & PTE_V))
continue;
mem = kalloc();
if(mem == 0)
return -1;
memset(mem, 0, PGSIZE);
if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
kfree(mem);
return -1;
}
}
return 0;
}