Part B: 寫(xiě)時(shí)拷貝的 Fork
在 Part A 中,我們通過(guò)把父進(jìn)程的所有內(nèi)存數(shù)據(jù)拷貝到子進(jìn)程實(shí)現(xiàn)了 fork()
氓润,這也是 Unix 系統(tǒng)早期的實(shí)現(xiàn)璧帝。這個(gè)拷貝到過(guò)程是 fork()
時(shí)最昂貴的操作叔锐。
然而,調(diào)用了 fork()
之后往往立即就會(huì)在子進(jìn)程中調(diào)用 exec()
紧帕,將子進(jìn)程的內(nèi)存更換為新的程序盔然,例如 shell 經(jīng)常干的(HW:Shell)。這樣是嗜,復(fù)制父進(jìn)程的內(nèi)存這個(gè)操作就完全浪費(fèi)了愈案。
因此,后來(lái)的 Unix 系統(tǒng)讓父鹅搪、子進(jìn)程共享同一片物理內(nèi)存站绪,直到某個(gè)進(jìn)程修改了內(nèi)存。這被稱(chēng)作 copy-on-write涩嚣。為了實(shí)現(xiàn)它崇众,fork()
時(shí)內(nèi)核只拷貝頁(yè)面的映射關(guān)系,而不拷貝其內(nèi)容航厚,同時(shí)將共享的頁(yè)面標(biāo)記為只讀 (read-only)顷歌。當(dāng)父子進(jìn)程中任一方向內(nèi)存中寫(xiě)入數(shù)據(jù)時(shí),就會(huì)觸發(fā) page fault幔睬。此時(shí)眯漩,Unix 就知道應(yīng)該分配一個(gè)私有的可寫(xiě)內(nèi)存給這個(gè)進(jìn)程。這個(gè)優(yōu)化使得 fork()
+ exec()
連續(xù)操作變得非常廉價(jià)。在執(zhí)行 exec()
之前赦抖,只需要拷貝一個(gè)頁(yè)面舱卡,即當(dāng)前的棧。
在 Part B 中队萤,我們將實(shí)現(xiàn)上述更佳實(shí)現(xiàn)方式的 fork()
轮锥。
用戶級(jí)別的頁(yè)錯(cuò)誤處理
內(nèi)核必須要記錄進(jìn)程不同區(qū)域出現(xiàn)頁(yè)面錯(cuò)誤時(shí)的處理方法。例如要尔,一個(gè)棧區(qū)域的 page fault 會(huì)分配并映射一個(gè)新的頁(yè)舍杜。一個(gè) BSS 區(qū)域(用于存放程序中未初始化的全局變量、靜態(tài)變量)的頁(yè)錯(cuò)誤會(huì)分配一個(gè)新的頁(yè)面赵辕,初始化為0既绩,再映射。
用戶級(jí)別的頁(yè)錯(cuò)誤處理流程為:
- 頁(yè)錯(cuò)誤異常还惠,陷入內(nèi)核
- 內(nèi)核修改
%esp
切換到進(jìn)程的異常棧饲握,修改%eip
讓進(jìn)程運(yùn)行 _pgfault_upcall - _pgfault_upcall 將運(yùn)行 page fault handler,此后不通過(guò)內(nèi)核切換回正常棧
設(shè)置頁(yè)錯(cuò)誤處理函數(shù)
為處理自己的頁(yè)錯(cuò)誤蚕键,進(jìn)程需要在 JOS 注冊(cè)一個(gè) page fault handler entrypoint救欧。進(jìn)程通過(guò) sys_env_set_pgfault_upcall
注冊(cè)自己的 entrypoint,并在 Env
結(jié)構(gòu)體中新增 env_pgfault_upcall
來(lái)記錄該信息嚎幸。
Exercise 8.
Implement thesys_env_set_pgfault_upcall
system call. Be sure to enable permission checking when looking up the environment ID of the target environment, since this is a "dangerous" system call.
進(jìn)程的正常棧和異常棧
正常運(yùn)行時(shí)颜矿,JOS 的進(jìn)程會(huì)運(yùn)行在正常棧上,ESP
從USTACKTOP
開(kāi)始往下生長(zhǎng)嫉晶,棧上的數(shù)據(jù)存放在 [USTACKTOP-PGSIZE, USTACKTOP-1]
上。當(dāng)出現(xiàn)頁(yè)錯(cuò)誤時(shí)田篇,內(nèi)核會(huì)把進(jìn)程在一個(gè)新的棧(異常棧)上面重啟替废,運(yùn)行指定的用戶級(jí)別頁(yè)錯(cuò)誤處理函數(shù)。也就是說(shuō)完成了一次進(jìn)程內(nèi)的棧切換泊柬。這個(gè)過(guò)程與 trap 的過(guò)程很相似椎镣。
JOS 的異常棧也只有一個(gè)物理頁(yè)大小,并且它的棧頂定義在虛擬內(nèi)存 UXSTACKTOP
處兽赁。當(dāng)運(yùn)行在這個(gè)棧上時(shí)状答,用戶級(jí)別頁(yè)錯(cuò)誤處理函數(shù)可以使用 JOS 的系統(tǒng)調(diào)用來(lái)映射新的頁(yè),以修復(fù)頁(yè)錯(cuò)誤刀崖。
每個(gè)需要支持用戶級(jí)頁(yè)錯(cuò)誤處理的函數(shù)都需要分配自己的異常棧惊科。可以使用 sys_page_alloc()
這個(gè)系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)亮钦。
用戶頁(yè)錯(cuò)誤處理函數(shù)
現(xiàn)在我們需要修改 kern/trap.c
以支持用戶級(jí)別的頁(yè)錯(cuò)誤處理馆截。
如果沒(méi)有注冊(cè) page fault handler,JOS內(nèi)核就直接銷(xiāo)毀進(jìn)程。否則蜡娶,內(nèi)核就會(huì)初始化一個(gè) trap frame 記錄寄存器狀態(tài)混卵,在異常棧上處理頁(yè)錯(cuò)誤,恢復(fù)進(jìn)程的執(zhí)行窖张。UTrapframe
在異常棧棧上如下所示幕随。
<-- UXSTACKTOP
trap-time esp
trap-time eflags
trap-time eip
trap-time eax start of struct PushRegs
trap-time ecx
trap-time edx
trap-time ebx
trap-time esp
trap-time ebp
trap-time esi
trap-time edi end of struct PushRegs
tf_err (error code)
fault_va <-- %esp when handler is run
相比 trap 時(shí)使用的 Trapframe
,多了記錄錯(cuò)誤位置的 fault_va
宿接,少了段選擇器%cs, %ds, %ss
合陵。這反映了兩者最大的不同:是否發(fā)生了進(jìn)程的切換。
如果異常發(fā)生時(shí)澄阳,進(jìn)程已經(jīng)在異常棧上運(yùn)行了拥知,這就說(shuō)明 page fault handler 本身出現(xiàn)了問(wèn)題。這時(shí)碎赢,我們就應(yīng)該在 tf->tf_esp
處分配新的棧低剔,而不是在 UXSTACKTOP
。首先需要 push 一個(gè)空的 32bit word 作為占位符肮塞,然后是一個(gè) UTrapframe
結(jié)構(gòu)體襟齿。
為檢查 tf->tf_esp
是否已經(jīng)在異常棧上了,只要檢查它是否在區(qū)間 [UXSTACKTOP-PGSIZE, UXSTACKTOP-1]
上即可枕赵。
以下9猜欺,10,11三個(gè)練習(xí)拷窜,建議按照調(diào)用順序來(lái)看开皿,即 11(設(shè)置handler)->9(切換到異常棧)->10(運(yùn)行handler,切換回正常棧)篮昧。
Exercise 9.
Implement the code inpage_fault_handler
inkern/trap.c
required to dispatch page faults to the user-mode handler. Be sure to take appropriate precautions when writing into the exception stack. (What happens if the user environment runs out of space on the exception stack?)
可參考 Exercise 10 的 lib/pfentry.S
中的注釋
較有難度的一個(gè)練習(xí)赋荆。首先需要理解用戶級(jí)別的頁(yè)錯(cuò)誤處理的步驟是:
進(jìn)程A(正常棧) -> 內(nèi)核 -> 進(jìn)程A(異常棧) -> 進(jìn)程A(正常棧)
那么內(nèi)核的工作就是修改進(jìn)程 A 的某些寄存器,并初始化異常棧懊昨,確保能順利切換到異常棧運(yùn)行窄潭。需要注意的是,由于修改了eip酵颁, env_run()
是不會(huì)返回的嫉你,因此不會(huì)繼續(xù)運(yùn)行后面銷(xiāo)毀進(jìn)程的代碼。
值得注意的是躏惋,如果是嵌套的頁(yè)錯(cuò)誤幽污,為了能實(shí)現(xiàn)遞歸處理,棧留出 32bit 的空位其掂,直接向下生長(zhǎng)油挥。
void
page_fault_handler(struct Trapframe *tf)
{
uint32_t fault_va;
// Read processor's CR2 register to find the faulting address
fault_va = rcr2();
// Handle kernel-mode page faults.
// LAB 3: Your code here.
if ((tf->tf_cs & 3) == 0) panic("Page fault in kernel-mode");
// LAB 4: Your code here.
if (curenv->env_pgfault_upcall) {
// 初始化異常棧
struct UTrapframe *utf;
if (tf->tf_esp >= UXSTACKTOP-PGSIZE && tf->tf_esp < UXSTACKTOP) {
// from exception stack
utf = (struct UTrapframe *)(tf->tf_esp - 4 - sizeof(struct UTrapframe));
} else {
utf = (struct UTrapframe *)(UXSTACKTOP - sizeof(struct UTrapframe));
}
user_mem_assert(curenv, (void *)utf, sizeof(struct UTrapframe), PTE_U | PTE_W | PTE_P);
utf->utf_fault_va = fault_va;
utf->utf_err = tf->tf_trapno;
utf->utf_regs = tf->tf_regs;
utf->utf_eip = tf->tf_eip;
utf->utf_eflags = tf->tf_eflags;
utf->utf_esp = tf->tf_esp;
// 修改 esp 完成棧切換,修改 eip 運(yùn)行 handler
tf->tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
// tf->esp = (uintptr_t)utf - 1; 不需要減1
tf->tf_esp = (uintptr_t)utf;
env_run(curenv);
}
// Destroy the environment that caused the fault.
cprintf("[%08x] user fault va %08x ip %08x\n",
curenv->env_id, fault_va, tf->tf_eip);
print_trapframe(tf);
env_destroy(curenv);
}
Question
What happens if the user environment runs out of space on the exception stack?
在 inc/memlayout.h
中可以找到:
#define UXSTACKTOP UTOP
// Next page left invalid to guard against exception stack overflow;
下面一頁(yè)是空頁(yè),內(nèi)核和用戶訪問(wèn)都會(huì)報(bào)錯(cuò)深寥。
用戶模式頁(yè)錯(cuò)誤入口
在處理完頁(yè)錯(cuò)誤之后攘乒,現(xiàn)在我們需要編寫(xiě)匯編語(yǔ)句實(shí)現(xiàn)從異常棧到正常棧的切換。
Exercise 10.
Implement the_pgfault_upcall
routine inlib/pfentry.S
. The interesting part is returning to the original point in the user code that caused the page fault. You'll return directly there, without going back through the kernel. The hard part is simultaneously switching stacks and re-loading the EIP.
匯編苦手惋鹅,寫(xiě)的很艱難则酝,最終還是參考了別人的答案。
.text
.globl _pgfault_upcall
_pgfault_upcall:
// 調(diào)用用戶定義的頁(yè)錯(cuò)誤處理函數(shù)
// Call the C page fault handler.
pushl %esp // function argument: pointer to UTF
movl _pgfault_handler, %eax
call *%eax
addl $4, %esp // pop function argument
// LAB 4: Your code here.
movl 48(%esp), %ebp
subl $4, %ebp
movl %ebp, 48(%esp)
movl 40(%esp), %eax
movl %eax, (%ebp)
// Restore the trap-time registers. After you do this, you
// can no longer modify any general-purpose registers.
// LAB 4: Your code here.
// 跳過(guò) utf_err 以及 utf_fault_va
addl $8, %esp
// popal 同時(shí) esp 會(huì)增加闰集,執(zhí)行結(jié)束后 %esp 指向 utf_eip
popal
// Restore eflags from the stack. After you do this, you can
// no longer use arithmetic operations or anything else that
// modifies eflags.
// LAB 4: Your code here.
// 跳過(guò) utf_eip
addl $4, %esp
// 恢復(fù) eflags
popfl
// Switch back to the adjusted trap-time stack.
// LAB 4: Your code here.
// 恢復(fù) trap-time 的棧頂
popl %esp
// Return to re-execute the instruction that faulted.
// LAB 4: Your code here.
// ret 指令相當(dāng)于 popl %eip
ret
首先必須要理解異常棧的結(jié)構(gòu)沽讹,下圖所示的是嵌套異常時(shí)的情況。其中左邊表示內(nèi)容武鲁,右邊表示地址爽雄。需要注意的是,上一次異常的棧頂之下間隔 4byte沐鼠,就是一個(gè)新的異常挚瘟。
最難理解的是這一部分:
movl 48(%esp), %ebp // 使 %ebp 指向 utf_esp
subl $4, %ebp
movl %ebp, 48(%esp) // 更新 utf_esp 值為 utf_esp-4
movl 40(%esp), %eax
movl %eax, (%ebp) // 將 utf_esp-4 地址的內(nèi)容改為 utf_eip
經(jīng)過(guò)這一部分的修改,異常棧更新為(紅字標(biāo)出):
此后就是恢復(fù)各寄存器饲梭,最后的 ret
指令相當(dāng)于 popl %eip
乘盖,指令寄存器的值修改為 utf_eip
,達(dá)到了返回的效果憔涉。
Exercise 11.
Finishset_pgfault_handler()
inlib/pgfault.c
.
該練習(xí)是用戶用來(lái)指定缺頁(yè)異常處理方式的函數(shù)订框。代碼比較簡(jiǎn)單,但是需要區(qū)分清楚 handler
兜叨,_pgfault_handler
穿扳,_pgfault_upcall
三個(gè)變量。
-
handler
是傳入的用戶自定義頁(yè)錯(cuò)誤處理函數(shù)指針浪腐。 -
_pgfault_upcall
是一個(gè)全局變量纵揍,在lib/pfentry.S
中完成的初始化。它是頁(yè)錯(cuò)誤處理的總?cè)肟谝榻郑?yè)錯(cuò)誤除了運(yùn)行 page fault handler,還需要切換回正常棧璧榄。 -
_pgfault_handler
被賦值為handler特漩,會(huì)在_pgfault_upcall
中被調(diào)用,是頁(yè)錯(cuò)誤處理的一部分骨杂。具體代碼是:
.text
.globl _pgfault_upcall
_pgfault_upcall:
// Call the C page fault handler.
pushl %esp // function argument: pointer to UTF
movl _pgfault_handler, %eax
call *%eax
addl $4, %esp
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
int r;
if (_pgfault_handler == 0) {
// First time through!
// LAB 4: Your code here.
// panic("set_pgfault_handler not implemented");
envid_t e_id = sys_getenvid();
r = sys_page_alloc(e_id, (void *)(UXSTACKTOP-PGSIZE), PTE_U | PTE_W | PTE_P);
if (r < 0) {
panic("pgfault_handler: %e", r);
}
// r = sys_env_set_pgfault_upcall(e_id, handler);
r = sys_env_set_pgfault_upcall(e_id, _pgfault_upcall);
if (r < 0) {
panic("pgfault_handler: %e", r);
}
}
// Save handler pointer for assembly to call.
_pgfault_handler = handler;
}
若是第一次調(diào)用涂身,需要首先分配一個(gè)頁(yè)面作為異常棧,并且將該進(jìn)程的 upcall 設(shè)置為 Exercise 10 中的程序搓蚪。此后如果需要改變handler蛤售,不需要再重復(fù)這個(gè)工作。
最后直接通過(guò) make grade
測(cè)試,滿足要求悴能。
Question
Whyuser/faultalloc
anduser/faultallocbad
behave differently?
兩者的 page fault handler 一樣揣钦,但是一個(gè)使用 cprintf()
輸出,另一個(gè)使用 sys_cput()
輸出漠酿。
sys_cput()
直接通過(guò) lib/syscall.c
發(fā)起系統(tǒng)調(diào)用冯凹,其實(shí)現(xiàn)在 kern/syscall.c
中:
static void
sys_cputs(const char *s, size_t len)
{
// Check that the user has permission to read memory [s, s+len).
// Destroy the environment if not.
// LAB 3: Your code here.
user_mem_assert(curenv, s, len, PTE_U);
// Print the string supplied by the user.
cprintf("%.*s", len, s);
}
它檢查了內(nèi)存,因此在這里 panic 了炒嘲。中途沒(méi)有觸發(fā)過(guò)頁(yè)錯(cuò)誤宇姚。
而 cprintf()
的實(shí)現(xiàn)可以在 lib/printf.c
中找到:
int
vcprintf(const char *fmt, va_list ap)
{
struct printbuf b;
b.idx = 0;
b.cnt = 0;
vprintfmt((void*)putch, &b, fmt, ap);
sys_cputs(b.buf, b.idx);
return b.cnt;
}
int
cprintf(const char *fmt, ...)
{
va_list ap;
int cnt;
va_start(ap, fmt);
cnt = vcprintf(fmt, ap);
va_end(ap);
return cnt;
}
它在調(diào)用 sys_cputs()
之前,首先在用戶態(tài)執(zhí)行了 vprintfmt()
將要輸出的字符串存入結(jié)構(gòu)體 b
中夫凸。在此過(guò)程中試圖訪問(wèn) 0xdeadbeef
地址浑劳,觸發(fā)并處理了頁(yè)錯(cuò)誤(其處理方式是在錯(cuò)誤位置處分配一個(gè)字符串,內(nèi)容是 "this string was faulted in at ..."
)夭拌,因此在繼續(xù)調(diào)用 sys_cputs()
時(shí)不會(huì)出現(xiàn) panic魔熏。
實(shí)現(xiàn) Copy-on-Write Fork
現(xiàn)在我們已經(jīng)具備了在用戶空間實(shí)現(xiàn) copy-on-write fork()
的條件。
如同 dumbfork()
一樣啼止,fork()
也要?jiǎng)?chuàng)建一個(gè)新進(jìn)程道逗,并且在新進(jìn)程中建立與父進(jìn)程同樣的內(nèi)存映射。關(guān)鍵的不同點(diǎn)是献烦,dumbfork()
拷貝了物理頁(yè)的內(nèi)容滓窍,而 fork()
僅拷貝了映射關(guān)系,僅在某個(gè)進(jìn)程需要改寫(xiě)某一頁(yè)的內(nèi)容時(shí)巩那,才拷貝這一頁(yè)的內(nèi)容吏夯。其基本流程如下:
- 父進(jìn)程使用
set_pgfault_handler
將pgfault()
設(shè)為 page fault handler - 父進(jìn)程使用
sys_exofork()
建立一個(gè)子進(jìn)程 - 對(duì)每個(gè)在
UTOP
之下可寫(xiě)頁(yè)面以及 COW 頁(yè)面(用PTE_COW
標(biāo)識(shí)),父進(jìn)程調(diào)用duppage
將其“映射”到子進(jìn)程即横,同時(shí)將其權(quán)限改為只讀噪生,并用PTE_COW
位來(lái)與一般只讀頁(yè)面區(qū)別
異常棧的分配方式與此不同,需要在子進(jìn)程中分配一個(gè)新頁(yè)面东囚。因?yàn)?page fault handler 會(huì)實(shí)實(shí)在在地向異常棧寫(xiě)入內(nèi)容跺嗽,并在異常棧上運(yùn)行。如果異常棧頁(yè)面都用 COW 機(jī)制页藻,那就沒(méi)有能夠執(zhí)行拷貝這個(gè)過(guò)程的載體了 - 父進(jìn)程會(huì)為子進(jìn)程設(shè)置 user page fault entrypoint
- 子進(jìn)程已經(jīng)就緒桨嫁,父進(jìn)程將其設(shè)為 runnable
進(jìn)程第一次往一個(gè) COW page 寫(xiě)入內(nèi)容時(shí),會(huì)發(fā)生 page fault份帐,其流程為:
- 內(nèi)核將 page fault 傳遞至
_pgfault_upcall
璃吧,它會(huì)調(diào)用pgfault()
handler -
pgfault()
檢查錯(cuò)誤類(lèi)型,以及頁(yè)面是否標(biāo)記為PTE_COW
-
pgfault()
分配一個(gè)新的頁(yè)面并將 fault page 的內(nèi)容拷貝進(jìn)去废境,然后將舊的映射覆蓋畜挨,使其映射到該新頁(yè)面筒繁。
Exercise 12.
Implementfork
,duppage
andpgfault
inlib/fork.c
.
Test your code with theforktree
program.
非常難的一個(gè)練習(xí)。
fork() 函數(shù)
首先從主函數(shù) fork()
入手巴元,其大體結(jié)構(gòu)可以仿造 user/dumbfork.c
寫(xiě)毡咏,但是有關(guān)鍵幾處不同:
設(shè)置 page fault handler,即 page fault upcall 調(diào)用的函數(shù)
duppage 的范圍不同务冕,
fork()
不需要復(fù)制內(nèi)核區(qū)域的映射為子進(jìn)程設(shè)置 page fault upcall血当,之所以這么做,是因?yàn)?
sys_exofork()
并不會(huì)復(fù)制父進(jìn)程的e->env_pgfault_upcall
給子進(jìn)程禀忆。
envid_t
fork(void)
{
// LAB 4: Your code here.
// panic("fork not implemented");
set_pgfault_handler(pgfault);
envid_t e_id = sys_exofork();
if (e_id < 0) panic("fork: %e", e_id);
if (e_id == 0) {
// child
thisenv = &envs[ENVX(sys_getenvid())];
return 0;
}
// parent
// extern unsigned char end[];
// for ((uint8_t *) addr = UTEXT; addr < end; addr += PGSIZE)
for (uintptr_t addr = UTEXT; addr < USTACKTOP; addr += PGSIZE) {
if ( (uvpd[PDX(addr)] & PTE_P) && (uvpt[PGNUM(addr)] & PTE_P) ) {
// dup page to child
duppage(e_id, PGNUM(addr));
}
}
// alloc page for exception stack
int r = sys_page_alloc(e_id, (void *)(UXSTACKTOP-PGSIZE), PTE_U | PTE_W | PTE_P);
if (r < 0) panic("fork: %e",r);
// DO NOT FORGET
extern void _pgfault_upcall();
r = sys_env_set_pgfault_upcall(e_id, _pgfault_upcall);
if (r < 0) panic("fork: set upcall for child fail, %e", r);
// mark the child environment runnable
if ((r = sys_env_set_status(e_id, ENV_RUNNABLE)) < 0)
panic("sys_env_set_status: %e", r);
return e_id;
}
duppage() 函數(shù)
該函數(shù)的作用是復(fù)制父臊旭、子進(jìn)程的頁(yè)面映射。尤其注意一個(gè)權(quán)限問(wèn)題箩退。由于 sys_page_map()
頁(yè)面的權(quán)限有硬性要求离熏,因此必須要修正一下權(quán)限。之前沒(méi)有修正導(dǎo)致一直報(bào)錯(cuò)戴涝,后來(lái)發(fā)現(xiàn)頁(yè)面權(quán)限為 0x865
滋戳,不符合 sys_page_map()
要求。
static int
duppage(envid_t envid, unsigned pn)
{
int r;
// LAB 4: Your code here.
// panic("duppage not implemented");
envid_t this_env_id = sys_getenvid();
void * va = (void *)(pn * PGSIZE);
int perm = uvpt[pn] & 0xFFF;
if ( (perm & PTE_W) || (perm & PTE_COW) ) {
// marked as COW and read-only
perm |= PTE_COW;
perm &= ~PTE_W;
}
// IMPORTANT: adjust permission to the syscall
perm &= PTE_SYSCALL;
// cprintf("fromenvid = %x, toenvid = %x, dup page %d, addr = %08p, perm = %03x\n",this_env_id, envid, pn, va, perm);
if((r = sys_page_map(this_env_id, va, envid, va, perm)) < 0)
panic("duppage: %e",r);
if((r = sys_page_map(this_env_id, va, this_env_id, va, perm)) < 0)
panic("duppage: %e",r);
return 0;
}
pgfault() 函數(shù)
這是 _pgfault_upcall 中調(diào)用的頁(yè)錯(cuò)誤處理函數(shù)啥刻。在調(diào)用之前奸鸯,父子進(jìn)程的頁(yè)錯(cuò)誤地址都引用同一頁(yè)物理內(nèi)存,該函數(shù)作用是分配一個(gè)物理頁(yè)面使得兩者獨(dú)立可帽。
首先娄涩,它分配一個(gè)頁(yè)面,映射到了交換區(qū) PFTEMP
這個(gè)虛擬地址映跟,然后通過(guò) memmove()
函數(shù)將 addr
所在頁(yè)面拷貝至 PFTEMP
蓄拣,此時(shí)有兩個(gè)物理頁(yè)保存了同樣的內(nèi)容。再將 addr
也映射到 PFTEMP
對(duì)應(yīng)的物理頁(yè)努隙,最后解除了 PFTEMP
的映射球恤,此時(shí)就只有 addr
指向新分配的物理頁(yè)了,如此就完成了錯(cuò)誤處理荸镊。
static void
pgfault(struct UTrapframe *utf)
{
void *addr = (void *) utf->utf_fault_va;
uint32_t err = utf->utf_err;
int r;
// Check that the faulting access was (1) a write, and (2) to a
// copy-on-write page. If not, panic.
// Hint:
// Use the read-only page table mappings at uvpt
// (see <inc/memlayout.h>).
// LAB 4: Your code here.
if ((err & FEC_WR)==0 || (uvpt[PGNUM(addr)] & PTE_COW)==0) {
panic("pgfault: invalid user trap frame");
}
// Allocate a new page, map it at a temporary location (PFTEMP),
// copy the data from the old page to the new page, then move the new
// page to the old page's address.
// Hint:
// You should make three system calls.
// LAB 4: Your code here.
// panic("pgfault not implemented");
envid_t envid = sys_getenvid();
if ((r = sys_page_alloc(envid, (void *)PFTEMP, PTE_P | PTE_W | PTE_U)) < 0)
panic("pgfault: page allocation failed %e", r);
addr = ROUNDDOWN(addr, PGSIZE);
memmove(PFTEMP, addr, PGSIZE);
if ((r = sys_page_unmap(envid, addr)) < 0)
panic("pgfault: page unmap failed (%e)", r);
if ((r = sys_page_map(envid, PFTEMP, envid, addr, PTE_P | PTE_W |PTE_U)) < 0)
panic("pgfault: page map failed (%e)", r);
if ((r = sys_page_unmap(envid, PFTEMP)) < 0)
panic("pgfault: page unmap failed (%e)", r);
}
可以通過(guò) make run-forktree
驗(yàn)證結(jié)果咽斧。