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.