6.828 操作系統(tǒng) lab4 實(shí)驗(yàn)報(bào)告:Part B

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ò)誤處理流程為:

  1. 頁(yè)錯(cuò)誤異常还惠,陷入內(nèi)核
  2. 內(nèi)核修改 %esp 切換到進(jìn)程的異常棧饲握,修改 %eip 讓進(jìn)程運(yùn)行 _pgfault_upcall
  3. _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 the sys_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)行在正常棧上,ESPUSTACKTOP開(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 in page_fault_handler in kern/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;
memlayout.png

下面一頁(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 in lib/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è)新的異常挚瘟。

uxstack.png

最難理解的是這一部分:

    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)出):

uxstack_new.png

此后就是恢復(fù)各寄存器饲梭,最后的 ret 指令相當(dāng)于 popl %eip乘盖,指令寄存器的值修改為 utf_eip,達(dá)到了返回的效果憔涉。

Exercise 11.
Finish set_pgfault_handler() in lib/pgfault.c.

該練習(xí)是用戶用來(lái)指定缺頁(yè)異常處理方式的函數(shù)订框。代碼比較簡(jiǎn)單,但是需要區(qū)分清楚 handler兜叨,_pgfault_handler穿扳,_pgfault_upcall 三個(gè)變量。

  1. handler 是傳入的用戶自定義頁(yè)錯(cuò)誤處理函數(shù)指針浪腐。
  2. _pgfault_upcall 是一個(gè)全局變量纵揍,在 lib/pfentry.S 中完成的初始化。它是頁(yè)錯(cuò)誤處理的總?cè)肟谝榻郑?yè)錯(cuò)誤除了運(yùn)行 page fault handler,還需要切換回正常棧璧榄。
  3. _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
Why user/faultalloc and user/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)容吏夯。其基本流程如下:

  1. 父進(jìn)程使用 set_pgfault_handlerpgfault() 設(shè)為 page fault handler
  2. 父進(jìn)程使用 sys_exofork() 建立一個(gè)子進(jìn)程
  3. 對(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ò)程的載體了
  4. 父進(jìn)程會(huì)為子進(jìn)程設(shè)置 user page fault entrypoint
  5. 子進(jìn)程已經(jīng)就緒桨嫁,父進(jìn)程將其設(shè)為 runnable

進(jìn)程第一次往一個(gè) COW page 寫(xiě)入內(nèi)容時(shí),會(huì)發(fā)生 page fault份帐,其流程為:

  1. 內(nèi)核將 page fault 傳遞至 _pgfault_upcall璃吧,它會(huì)調(diào)用 pgfault() handler
  2. pgfault() 檢查錯(cuò)誤類(lèi)型,以及頁(yè)面是否標(biāo)記為PTE_COW
  3. pgfault() 分配一個(gè)新的頁(yè)面并將 fault page 的內(nèi)容拷貝進(jìn)去废境,然后將舊的映射覆蓋畜挨,使其映射到該新頁(yè)面筒繁。

Exercise 12.
Implement fork, duppage and pgfault in lib/fork.c.
Test your code with the forktree 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é)果咽斧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躬存,隨后出現(xiàn)的幾起案子收厨,更是在濱河造成了極大的恐慌,老刑警劉巖优构,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雁竞,居然都是意外死亡钦椭,警方通過(guò)查閱死者的電腦和手機(jī)拧额,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)彪腔,“玉大人侥锦,你說(shuō)我怎么就攤上這事〉抡酰” “怎么了恭垦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)格嗅。 經(jīng)常有香客問(wèn)我番挺,道長(zhǎng),這世上最難降的妖魔是什么屯掖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任玄柏,我火速辦了婚禮,結(jié)果婚禮上贴铜,老公的妹妹穿的比我還像新娘粪摘。我一直安慰自己,他們只是感情好绍坝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布徘意。 她就那樣靜靜地躺著,像睡著了一般轩褐。 火紅的嫁衣襯著肌膚如雪椎咧。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天灾挨,我揣著相機(jī)與錄音邑退,去河邊找鬼。 笑死劳澄,一個(gè)胖子當(dāng)著我的面吹牛地技,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秒拔,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼莫矗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了砂缩?” 一聲冷哼從身側(cè)響起作谚,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庵芭,沒(méi)想到半個(gè)月后妹懒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡双吆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年眨唬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了会前。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匾竿,死狀恐怖瓦宜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岭妖,我是刑警寧澤临庇,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站昵慌,受9級(jí)特大地震影響假夺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜废离,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一侄泽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜻韭,春花似錦悼尾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至俯画,卻和暖如春析桥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背艰垂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工泡仗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猜憎。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓娩怎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親胰柑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子截亦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容