1. 程式人生 > 實用技巧 >mit 6.828學習筆記不知道幾——lab4 partb

mit 6.828學習筆記不知道幾——lab4 partb

exercise 8:

首先實現sys_env_set_pgfault_upcall

// kernl/syscall.c
static int
sys_env_set_pgfault_upcall(envid_t envid, void* func)
{
  // LAB 4: Your code here.
  struct Env *e;
  int r =envid2env(envid, &e, 1);
  if(r != 0)
    return r;
  e->env_pgfault_upcall = func;
  return 0;
//panic("sys_env_set_pgfault_upcall not implemented");
}

新增syscall

case (SYS_env_set_pgfault_upcall):
        return sys_env_set_pgfault_upcall(a1, (void*) a2);

exercise 9:

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\n"); } // 這裡我們是處理使用者模式下的page fault //如果當前環境下有page fault upcall,就呼叫它,在user exception stack(below UXSTACKTOP) //上設定一個page fault stack frame,然後branch to curenv->env_pgfault_upcall.
// // page fault upcall可能會造成另外的page fault,這時候我們 // branch to the page fault upcall recursively, pushing another // page fault stack frame on top of the user exception stack. // //對於從頁面錯誤(lib/pfentry.S)返回的程式碼來說,在trap-time堆疊的頂部 //空出一個位元組空間能讓我們更容易地恢復eip/esp。在非遞迴的情況下, //我們不需要擔心這個問題,因為常規使用者棧的頂部是空閒的。 //在遞迴的情況下,這意味著我們必須在異常堆疊的當前頂部和新的堆疊幀之間留下一個額外的單word //,因為異常堆疊就是trap-time堆疊。 // 如果沒有page fault upcall, the environment didn't allocate a // page for its exception stack or can't write to it, or the exception // stack overflows, then destroy the environment that caused the fault. // Note that the grade script assumes you will first check for the page // fault upcall and print the "user fault va" message below if there is // none. The remaining three checks can be combined into a single test. // // Hints: // user_mem_assert() and env_run() are useful here. // To change what the user environment runs, modify 'curenv->env_tf' // (the 'tf' variable points at 'curenv->env_tf'). // LAB 4: Your code here. struct UTrapframe* utf; if (curenv->env_pgfault_upcall != NULL) { //看當前環境下有沒有page fault upcall ////檢查他是不是已經在使用者異常堆疊上執行, //發生異常時,使用者環境已經在使用者異常堆疊上執行,應該在當前tf->tf_esp下啟動新的堆疊幀 //您應該首先推送一個空的32位word,然後是struct UTrapframe if (tf->tf_esp <= UXSTACKTOP - 1 && tf->tf_esp >= UXSTACKTOP - PGSIZE) //如果是,應該在tf-> tf_esp下,先壓入一個空的32位字,再壓棧幀 //就是相當於壓入的地址在 tf-> tf_esp - 4 //這的utf是棧頂 utf = (struct UTrapframe*)(tf->tf_esp - 4 - sizeof(struct UTrapframe)); else //使用者在使用者的正常stack下,應該直接在使用者異常棧UXSTACKTOP下壓入棧幀 utf = (struct UTrapframe*)(UXSTACKTOP - sizeof(struct UTrapframe)); // 檢查是否the exception stack overflows user_mem_assert(curenv, (const void*)utf, sizeof(struct UTrapframe), PTE_W); // 儲存現場 utf->utf_fault_va = fault_va; utf->utf_err = tf->tf_trapno; //要區分tf_trapno與tf_err utf->utf_regs = tf->tf_regs; utf->utf_eflags = tf->tf_eflags; utf->utf_eip = tf->tf_eip; utf->utf_esp = tf->tf_esp; //堆疊指向異常棧頂 tf->tf_esp = (uintptr_t)utf; //要儲存原來程式的下一條要進行的指令, tf->tf_eip = (uintptr_t)curenv->env_pgfault_upcall; env_run(curenv); }else{ // 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); } }

exercise 10:

// Struct PushRegs size = 32 
  addl $8, %esp                 // esp+8 -> PushRegs   over utf_fault_va utf_err
    movl 0x20(%esp), %eax         // eax = (esp+0x20 -> utf_eip )
    subl $4, 0x28(%esp)           // for trap time eip 保留32bit,   esp+48 = utf_esp
    movl 0x28(%esp), %edx         // %edx = utf_esp-4  
    movl %eax, (%edx)             // %eax = eip ----> esp-4  以至於ret可以直接讀取其繼續執行的地址
    
    popal              // after popal esp->utf_eip

    addl $4, %esp      // esp+4 -> utf_eflags
    popfl

    popl %esp

    ret                   // 這裡十分巧妙, ret會讀取esp指向的第一個內容, 也就是我們第一步寫入的eip

exercise 11:

void
set_pgfault_handler(void (*handler)(struct UTrapframe* utf))
{


    if (_pgfault_handler == 0) {
        // First time through!
        // LAB 4: Your code here.

        if (sys_page_alloc(thisenv->env_id, (void*)(UXSTACKTOP - PGSIZE), PTE_W | PTE_U))
            panic("fail to alloc a page for UXSTACKTOP!\n");

        if (sys_env_set_pgfault_upcall(thisenv->env_id, _pgfault_upcall))
            panic("fail to set pgfault upcall!\n");
        //panic("set_pgfault_handler not implemented");
    }

    // Save handler pointer for assembly to call.
    _pgfault_handler = handler;

}

exercise 12:

static void
pgfault(struct UTrapframe* utf) 
{   //如果要寫的頁面是可寫頁面和copy-on-write頁面,就會觸發pgfault
    //fork的時候只是複製了對映,而沒有複製整個地址空間
    //頁面錯誤處理程式將執行實際的複製,
    void* addr = (void*)utf->utf_fault_va;
    uint32_t err = utf->utf_err;
    int r;

    // Hint:
    //   Use the read-only page table mappings at uvpt
    //   (see <inc/memlayout.h>).

    if (!((err & FEC_WR) && (uvpt[PGNUM(addr)] & (PTE_W | PTE_COW))))
        //檢查錯誤是否為寫(檢查錯誤程式碼中的FEC_WR)
        //Check that the faulting access was a write and to a copy-on-write page.  If not, panic.
        panic("faulting access was not a write, and a copy-on-write page.\n");
    //pgfault()分配一個對映到臨時位置的新頁面,並將故障頁面的內容複製到其中。
    //然後,故障處理程式將新頁面對映到具有讀/寫許可權的適當地址,以替代舊的只讀對映。
    // 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.
    //此時應該是系統呼叫???為什麼不能用thisenv
    
    envid_t envid = sys_getenvid();
    r = sys_page_alloc(envid, (void*)PFTEMP, PTE_P | PTE_W | PTE_U);//分配一個頁面
    if (r != 0)
        panic("page alloc fault: %e\n", r);
    addr = ROUNDDOWN(addr, PGSIZE); //要保證與PGSIZE對齊 涉及到頁面的都要對齊
    memcpy((void*)PFTEMP, (const void*)addr, PGSIZE);//將故障頁面的內容複製到其中。
    r = sys_page_map(envid, (void*)PFTEMP, envid, (void*)addr, PTE_P | PTE_W | PTE_U);
    //將新頁面對映到具有讀/寫許可權的報錯的地址,以替代舊的只讀對映。
    if (r != 0)
        panic("page map fault: %e\n", r);
    r = sys_page_unmap(envid, (void*)PFTEMP); //解除對映
    if (r != 0)
        panic("page unmap fault: %e", r);
    
    ////相當於 PFTEMP只是一個臨時中轉站  
    //首先要通過PFTEMP給它分配一個頁面,但是新頁面會代替舊的只讀對映,這個時候,到PFTEMP的對映就失效了
    
    //panic("pgfault not implemented");
}
static int
duppage(envid_t envid, unsigned pn)
{   //這個函式用來複制對映關係
    //對於UTOP下面地址空間中的每個可寫頁面或寫時複製頁面,
    //父類(1)要呼叫duppage, 寫時複製的頁面對映到子程序的地址空間,
    //      (2) 在自己的地址空間中重新對映寫時複製的頁面。

    int r;
    void* addr = (void*)(pn << 12);//address  pn*PGSIZE
    envid_t fu_id = sys_getenvid();
    if (uvpt[pn] & (PTE_W | PTE_COW)) {
        //  If the page is writable or copy-on-write,
        // the new mapping must be created copy-on-write,
        //父程序的地址空間對映給了子程序
        r = sys_page_map(fu_id, (void*)addr, envid, (void*)addr, PTE_COW | PTE_U);
        if (r != 0)
            return r;
        r = sys_page_map(fu_id, (void*)addr, fu_id, (void*)addr, PTE_COW | PTE_U);
        if (r != 0)
            return r;
    }
    else {
        r = sys_page_map(fu_id, (void*)addr, envid, (void*)addr, uvpt[pn] & PTE_SYSCALL);
        if (r != 0)
            return r;
    }
    

    // LAB 4: Your code here.
    //panic("duppage not implemented");
    return 0;
}
envid_t
fork(void)
{
    // LAB 4: Your code here.
    envid_t who;
    int i, r;
    //Set up our page fault handler appropriately.
    set_pgfault_handler(pgfault);
    // fork a child process
    who = sys_exofork();
    if (who < 0)
        panic("sys_exofork: %e", who);

    if (who == 0) {
        //是子程序
        // The copied value of the global variable 'thisenv'
        // is no longer valid (it refers to the parent!).
        // Fix it and return 0.
        thisenv = &envs[ENVX(sys_getenvid())];
        return 0;
    }

    // Copy our address space to the child.
    // 父程序虛擬地址空間UTOP以下的每一頁都應該在子程序中有所對映
    // 關鍵是要找到該虛擬地址在頁表中對應的pte,才能知道許可權
    // 這是在使用者空間,page_walk是核心空間的,所以巧妙的通過uvpt、uvpd來找到pte、pde
    // 我們可以知道,uvpd是有1024個pde的一維陣列,而uvpt是有2^20個pte的一維陣列,與物理頁號剛好一一對應
    for (i = 0; i < PGNUM(USTACKTOP); i++) {
        //這裡只需要到USTACKTOP,後面只有UXSTACKTOP,不能是COW,必須分配實體記憶體
        //不然老報錯[00001000] user_mem_check assertion failure for va eebfffcc
        //報錯的位置應該是page_fault_handler()裡的user_mem_asser()檢查使用者異常棧許可權
        if ((uvpd[i / 1024] & PTE_P) && (uvpt[i] & PTE_P)) { //i跟pte一一對應,而i/1024就是該pte所在的頁表
            r = duppage(who, i); //區分是不是COW與W都放到duppage中。我之前這裡也區分也是亂了
            if (r != 0)
                panic("duppage fault:%e\n", r);
        }
    }

    //   Neither user exception stack should ever be marked copy-on-write,
    //   任何使用者異常堆疊都不應該標記為寫時複製
    //   so you must allocate a new page for the child's user exception stack.
    r = sys_page_alloc(who, (void*)(UXSTACKTOP - PGSIZE), PTE_W | PTE_U);
    if (r != 0)
        panic("page alloc fault:%e\n", r);

    //Copy page fault handler setup 
    extern void _pgfault_upcall(void);
    r = sys_env_set_pgfault_upcall(who, _pgfault_upcall);
    if (r != 0)
        panic("set pgfault upcall fault:%e\n", r);

    // Then mark the child as runnable and return.
    r = sys_env_set_status(who, ENV_RUNNABLE);
    if (r != 0)
        panic("env set status fault:%e\n", r);
    return who; //return 0 我居然在fork裡一直返回0???
    panic("fork not implemented");
    //panic("fork not implemented");`
}