6.s081 Lab5 lazy
Lab: xv6 lazy page allocation
sbrk
是xv6提供的系統呼叫, 通過sbrk
, 使用者程式可以擴大自己的heap
.
一開始時, sbrk
指向heap
底端, 同時也是stack
的頂端(p->sz
).
當呼叫sbrk
, 會擴充套件heap
上界, 核心會分配一些實體記憶體, 並將這些實體記憶體對映到heap
中.
xv6實現的是eager allocation, 一旦呼叫sbrk
就會立即分配實體記憶體. 這樣會使記憶體消耗增加(應用程式不知道自己需要多少實體記憶體, 會申請多於自己所需的記憶體).
但通過虛擬記憶體和page fault handler可以實現lazy alloction. lazy allocation的核心思想是: sbrk
p->sz
(將p->sz
增加n, n是所需要分配的實體記憶體的page數), 但並不實際分配實體記憶體. 當使用相應的那部分記憶體時, 會觸發page fault(因為沒有分配新的記憶體到page table). 在handler中處理, 並分配新的實體記憶體, 將記憶體對映到page table中, 再重新執行指令.
Eliminate allocation from sbrk() (easy)
第一個任務是修改sys_sbrk()函式, 使其並不實際分配實體記憶體.
uint64 sys_sbrk(void) { int addr; int n; if(argint(0, &n) < 0) return -1; addr = myproc()->sz; myproc()->sz += n; // if(growproc(n) < 0) // return -1; return addr; }
由於不再分配實體記憶體, 只要把growproc
註釋掉, 但程序的size需要增加n.
此時執行echo hi
會得到一個page fault. 因為shell中執行程式時, shell會先fork
一個子程序, 子程序執行exec
來執行echo
. shell
fork
時會申請一些記憶體, 所以呼叫了sys_sbrk
, sys_sbrk
不實際分配實體記憶體所以就page fault了.
usertrap
中會列印一些資訊:
-
scause
暫存器中的內容, 值是15. -
程序id是3, 該
pid
是shell的pid
. -
sepc
中的值是0x12ac. 在sh.asm
的該位置處, 可以看到是一個store
-
stval
中的值是0x4008, 該值是出錯的虛擬記憶體的地址. xv6中的shell有4個page, 在第5個page也就是0x4008出錯.
Lazy allocation (moderate)
修改在trap.c中的程式碼來處理page fault, 之後返回到指令出錯位置來重新執行指令.
-
先在
trap.c
中的usertrap
函式中處理. 當r_scause
為13或15時, 通過r_stval
獲取stval
中的值, 也就是虛擬地址, 然後呼叫kalloc
分配實體記憶體, 當無法分配實體記憶體時, 殺死程序, 否則就將實體地址對映到虛擬地址.else if (r_scause() == 15 || r_scause() == 13) { uint64 va = r_stval(); 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; } } }
-
此時執行
echo hi
會panic
, 是uvmunmap
在報錯, 在unmap page時, page並不存在, 因為之前lazy allocation並沒有實際分配記憶體. 所以當遇到標誌位V為0的page時, 是一個預期行為, 只需要continue.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) panic("uvmunmap: walk"); if((*pte & PTE_V) == 0) //panic("uvmunmap: not mapped"); continue; if(PTE_FLAGS(*pte) == PTE_V) panic("uvmunmap: not a leaf"); if(do_free){ uint64 pa = PTE2PA(*pte); kfree((void*)pa); } *pte = 0; } }
Lazytests and Usertests (moderate)
需要通過lazytests和usertests測試.
-
處理
sbrk
函式, 當引數為負數時, 直接呼叫growproc
.uint64 sys_sbrk(void) { int addr; int n; if(argint(0, &n) < 0) return -1; addr = myproc()->sz; if (n >= 0) { myproc()->sz += n; } else if (n < 0) { if (growproc(n) < 0) { return -1; } } // if(growproc(n) < 0) // return -1; return addr; }
-
當虛擬記憶體地址大於
p->sz
或小於user stack時, 殺死程序, 在殺死程序前, 記得釋放之前分配的實體記憶體. 需要修改trap.c
中的usertrap
函式, 對va
的值判定.else if (r_scause() == 15 || r_scause() == 13) { uint64 va = r_stval(); uint64 ka = (uint64) kalloc(); if (ka == 0) { p->killed = 1; } else if (va >= p->sz || PGROUNDDOWN(va) < PGROUNDDOWN(p->trapframe->sp)) { kfree((void *) ka); 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; } } }
-
由於沒有實際分配實體記憶體,
fork
函式會出現一些問題, 原因是呼叫了uvmcopy
,uvmcopy
會呼叫walk和判定父程序的pte是否有效, 為了能夠正常工作, 當沒有找到pte和pte無效時, continue.int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) { pte_t *pte; uint64 pa, i; uint flags; char *mem; for(i = 0; i < sz; i += PGSIZE){ if((pte = walk(old, i, 0)) == 0) //panic("uvmcopy: pte should exist"); continue; if((*pte & PTE_V) == 0) //panic("uvmcopy: page not present"); continue; pa = PTE2PA(*pte); flags = PTE_FLAGS(*pte); if((mem = kalloc()) == 0) goto err; memmove(mem, (char*)pa, PGSIZE); if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ kfree(mem); goto err; } } }
-
當
read/write
等系統呼叫時, 傳入的虛擬地址沒有分配實際的實體記憶體時, 由於系統會先系統呼叫, 此時頁表被替換為kernel pagetable, 就無法直接訪問虛擬地址, 需要通過walkaddr
將虛擬地址翻譯為實體地址, 如果這時候虛擬地址沒有被對映實體地址, 就會panic
. 需要處理在uvmcopy
中continue的過程, 手動分配一個實體地址並對映到相應的虛擬地址處.uint64 walkaddr(pagetable_t pagetable, uint64 va) { pte_t *pte; uint64 pa; struct proc *p = myproc(); if(va >= MAXVA) return 0; pte = walk(pagetable, va, 0); /* if(pte == 0) return 0; if((*pte & PTE_V) == 0) return 0; */ if (pte == 0 || (*pte & PTE_V) == 0) { pa = (uint64) kalloc(); if (pa == 0) { p->killed = 1; return 0; } else if (PGROUNDDOWN(va) > p->sz || PGROUNDDOWN(va) < PGROUNDDOWN(p->trapframe->sp)) { kfree((void *) pa); return 0; } else { memset((void *) ka, 0, PGSIZE); va = PGROUNDDOWN(va); if (mappages(pagetable, va, PGSIZE, pa, PTE_W | PTE_U | PTE_R) != 0) { kfree((void *) pa); return 0; } } } if((*pte & PTE_U) == 0) return 0; pa = PTE2PA(*pte); return pa; }