MIT6.828 Fall2018 筆記 - Lab 4: Preemptive Multitasking

Part A: Multiprocessor Support and Cooperative Multitasking

Exercise 1

void *
mmio_map_region(physaddr_t pa, size_t size)
{
    static uintptr_t base = MMIOBASE;

    // Your code here:
    boot_map_region(kern_pgdir, base, size, pa, PTE_PCD | PTE_PWT | PTE_W);
    uintptr_t result = base;
    base += ROUNDUP(size, PGSIZE);
    return (void*)result;
}

Exercise 2

//  2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
    //     is free.
    for (i = 1; i < npages_basemem; i++) {
        if (i == MPENTRY_PADDR >> PGSHIFT) {
            pages[i].pp_ref = 1;
            continue;
        }
        pages[i].pp_ref = 0;
        pages[i].pp_link = page_free_list;
        page_free_list = &pages[i];
    }

如今能夠看到:api

check_page_free_list() succeeded!
check_page_alloc() succeeded!
check_page() succeeded!

不過check_kern_pgdir()仍是會失敗ide

Question 1

Compare kern/mpentry.S side by side with boot/boot.S. Bearing in mind that kern/mpentry.S is compiled and linked to run above KERNBASE just like everything else in the kernel, what is the purpose of macro MPBOOTPHYS? Why is it necessary in kern/mpentry.S but not in boot/boot.S? In other words, what could go wrong if it were omitted in kern/mpentry.S?
Hint: recall the differences between the link address and the load address that we have discussed in Lab 1.函數

MPBOOTPHYS宏的目的是計算絕對地址,由於在boot_aps()memmove()kern/mpentry.S的代碼移動到了MPENTRY_PADDR,若是不使用MPBOOTPHYS宏,就會尋址到0xf0000000之上的地址,而實模式是隻能尋址1M。oop

Exercise 3

static void
mem_init_mp(void)
{
    // LAB 4: Your code here:
    uintptr_t kstack_i = KSTACKTOP - KSTKSIZE;
    for (int i = 0; i < NCPU; i++) {
        boot_map_region(kern_pgdir, kstack_i, KSTKSIZE, PADDR(percpu_kstacks[i]), PTE_W);
        kstack_i -= KSTKSIZE + KSTKGAP;
    }
}

經過了以下測試:測試

check_kern_pgdir() succeeded!
check_page_free_list() succeeded!
check_page_installed_pgdir() succeeded!

Exercise 4

void
trap_init_percpu(void)
{
    // LAB 4: Your code here:

    // Setup a TSS so that we get the right stack
    // when we trap to the kernel.
    struct Taskstate* this_ts = &thiscpu->cpu_ts;
    int cpu_id = cpunum();
    this_ts->ts_esp0 = KSTACKTOP - cpu_id * (KSTKSIZE + KSTKGAP);
    this_ts->ts_ss0 = GD_KD;
    this_ts->ts_iomb = sizeof(struct Taskstate);

    // Initialize the TSS slot of the gdt.
    gdt[(GD_TSS0 >> 3) + cpu_id] = SEG16(STS_T32A, (uint32_t)(this_ts), sizeof(struct Taskstate) - 1, 0);
    gdt[(GD_TSS0 >> 3) + cpu_id].sd_s = 0;

    // Load the TSS selector (like other segment selectors, the
    // bottom three bits are special; we leave them 0)
    ltr(GD_TSS0 + (cpu_id << 3));

    // Load the IDT
    lidt(&idt_pd);
}

Exercise 5

//i386_init
lock_kernel();
boot_aps();

//mp_main
lock_kernel();
sched_yield();

//trap
if ((tf->tf_cs & 3) == 3) {
    lock_kernel();
    assert(curenv);
    ......
}
//env_run
lcr3(PADDR(curenv->env_pgdir));
unlock_kernel();
env_pop_tf(&(curenv->env_tf));

Exercise 6

kern/syscall.c中添加:ui

case SYS_yield:
        sys_yield();
        return 0;

修改i386_init(),因爲user_primes程序中使用的fork系統調用還未實現,因此暫時註釋掉,等實現了再取消註釋:this

/*
#if defined(TEST)
    // Don't touch -- used by grading script!
    ENV_CREATE(TEST, ENV_TYPE_USER);
#else
    // Touch all you want.
    ENV_CREATE(user_primes, ENV_TYPE_USER);
#endif // TEST*
*/
    ENV_CREATE(user_yield, ENV_TYPE_USER);
    ENV_CREATE(user_yield, ENV_TYPE_USER);
    ENV_CREATE(user_yield, ENV_TYPE_USER);
    // Schedule and run the first user environment!
    sched_yield();
}
void
sched_yield(void)
{
    struct Env *idle;
    // LAB 4: Your code here.
    idle = curenv;
    size_t idx = idle != NULL ? ENVX(idle->env_id) : -1;
    for (size_t i = 0; i < NENV; i++) {
        idx = (idx + 1 == NENV) ? 0 : idx + 1;
        if (envs[idx].env_status == ENV_RUNNABLE) {
            env_run(&envs[idx]);
            return;
        }
    }
    if (idle && idle->env_status == ENV_RUNNING && idle->env_cpunum == cpunum()) {
        env_run(idle);
        return;
    }

    // sched_halt never returns
    sched_halt();
}

make qemu CPUS=2運行結果:spa

...
[00000000] new env 00001000
[00000000] new env 00001001
[00000000] new env 00001002
Hello, I am environment 00001000.
Hello, I am environment 00001001.
Back in environment 00001000, iteration 0.
Hello, I am environment 00001002.
Back in environment 00001001, iteration 0.
Back in environment 00001000, iteration 1.
Back in environment 00001001, iteration 1.
Back in environment 00001002, iteration 0.
Back in environment 00001000, iteration 2.
Back in environment 00001001, iteration 2.
Back in environment 00001002, iteration 1.
...

Exercise 7

i386_init()改回來:unix

#if defined(TEST)
    // Don't touch -- used by grading script!
    ENV_CREATE(TEST, ENV_TYPE_USER);
#else
    // Touch all you want.
    ENV_CREATE(user_primes, ENV_TYPE_USER);
#endif // TEST*

    // Schedule and run the first user environment!
    sched_yield();
}

而後實現5個系統調用:rest

static envid_t
sys_exofork(void)
{
    // LAB 4: Your code here.
    struct Env* child_env;
    int result = env_alloc(&child_env, curenv->env_id);
    if (result != 0) // 若是alloc失敗
        return result;
    child_env->env_status = ENV_NOT_RUNNABLE;
    child_env->env_tf = curenv->env_tf;    // 複製父進程的trapframe
    child_env->env_tf.tf_regs.reg_eax = 0; // 子進程的返回值
    return child_env->env_id;
}
static int
sys_env_set_status(envid_t envid, int status)
{
    // LAB 4: Your code here.
    struct Env* e;
    if (envid2env(envid, &e, 1) != 0)
        return -E_BAD_ENV;
    if (status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE)
        return -E_INVAL;
    e->env_status = status;
    return 0;
}
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
    // LAB 4: Your code here.
    struct Env* e;
    if (envid2env(envid, &e, 1) != 0)
        return -E_BAD_ENV;
    if ((uintptr_t)va >= UTOP || PGOFF(va) != 0 || perm < (PTE_U | PTE_P) || (perm & ~PTE_SYSCALL) != 0)
        return -E_INVAL;
    struct PageInfo* pp = page_alloc(ALLOC_ZERO);
    if (pp == NULL || page_insert(e->env_pgdir, pp, va, perm) != 0)
        return -E_NO_MEM;
    return 0;
}
static int
sys_page_map(envid_t srcenvid, void *srcva,
         envid_t dstenvid, void *dstva, int perm)
{
    // LAB 4: Your code here.
    struct Env *srcenv, *dstenv;
    if ((envid2env(srcenvid, &srcenv, 1) != 0) || (envid2env(dstenvid, &dstenv, 1) != 0))
        return -E_BAD_ENV;
    if ((uintptr_t)srcva >= UTOP || PGOFF(srcva) != 0 || (uintptr_t)dstva >= UTOP || PGOFF(dstva) != 0 || perm < (PTE_U | PTE_P) || (perm & ~PTE_SYSCALL) != 0)
        return -E_INVAL;
    pte_t* src_pte;
    struct PageInfo* pp = page_lookup(srcenv->env_pgdir, srcva, &src_pte);
    if ((perm & PTE_W) && (*src_pte & PTE_W) == 0)
        return -E_INVAL;
    if (page_insert(dstenv->env_pgdir, pp, dstva, perm) != 0)
        return -E_NO_MEM;
    return 0;
}
static int
sys_page_unmap(envid_t envid, void *va)
{
    // LAB 4: Your code here.
    struct Env* e;
    if ((envid2env(envid, &e, 1) != 0))
        return -E_BAD_ENV;
    if ((uintptr_t)va >= UTOP || PGOFF(va) != 0)
        return -E_INVAL;
    page_remove(e->env_pgdir, va);
    return 0;
}

make grade運行結果:

dumbfork: OK (6.8s)
Part A score: 5/5

faultread: OK (5.2s)
faultwrite: OK (5.1s)
...

Part B: Copy-on-Write Fork

fork複製父進程的地址空間開銷很大,因此最新的unix使用了寫時複製的方法,即容許父進程和子進程共享內存映射直到一個進程實際上修改那部份內存的時候再複製

Exercise 8

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

Exercise 9 10 11

// 設置 page fault 處理函數
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
    int r;

    if (_pgfault_handler == 0) {
        // First time through!
        // LAB 4: Your code here.
        envid_t eid = sys_getenvid();
        r = sys_page_alloc(eid, (void*)(UXSTACKTOP - PGSIZE), PTE_U | PTE_P | PTE_W);
        if (r < 0) {
            panic("set_pgfault_handler: %e", r);
        }
        r = sys_env_set_pgfault_upcall(eid, _pgfault_upcall);
        if (r < 0) {
            panic("set_pgfault_handler: %e", r);
        }
        // panic("set_pgfault_handler not implemented");
    }

    // Save handler pointer for assembly to call.
    _pgfault_handler = handler;
}
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.
    // 若是是在kernel中page fault
    if ((tf->tf_cs & 0x3) == 0) {
        panic("page fault in kernel mode!");
    }

    // LAB 4: Your code here.

    if (curenv->env_pgfault_upcall != 0) {
        struct UTrapframe* utf;
        if (tf->tf_esp >= UXSTACKTOP - PGSIZE && tf->tf_esp < UXSTACKTOP) {
            // 從 user 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_W);
        utf->utf_eflags = tf->tf_eflags;
        utf->utf_eip = tf->tf_eip;
        utf->utf_err = tf->tf_err;
        utf->utf_esp = tf->tf_esp;
        utf->utf_fault_va = fault_va;
        utf->utf_regs = tf->tf_regs;
        tf->tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
        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);
}
.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			// pop function argument

    // LAB 4: Your code here.
    movl 48(%esp), %ebp
    subl $4, %ebp 			// ebp 的值爲utf_esp - 4
    movl %ebp, 48(%esp) 	// 更改UTrapframe的esp爲utf_esp - 4
    movl 40(%esp), %eax
    movl %eax, (%ebp) 		// 將eip存進 utf_esp-4 處

    // Restore the trap-time registers.  After you do this, you
    // can no longer modify any general-purpose registers.
    // LAB 4: Your code here.
    addl $8, %esp
    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.
    addl $4, %esp
    popfl

    // Switch back to the adjusted trap-time stack.
    // LAB 4: Your code here.
    popl %esp
    // 此時esp爲 utf_esp - 4,指向的內容爲ip

    // Return to re-execute the instruction that faulted.
    // LAB 4: Your code here.
    // ret 即 pop eip,以後esp就等於utf_esp了
    ret

make grade結果:

faultread: OK (7.4s)
faultwrite: OK (7.8s)
faultdie: OK (7.7s)
faultregs: OK (7.6s)
faultalloc: OK (7.6s)
faultallocbad: OK (8.0s)
faultnostack: OK (9.6s)
faultbadhandler: OK (8.9s)
faultevilhandler: OK (7.7s)

Exercise 12

inc/memlayout.h中添加:

#define JOS_USER 1

這樣就可使用uvpt和uvpd了

fork.c

envid_t
fork(void)
{
    // LAB 4: Your code here.
    set_pgfault_handler(pgfault);
    envid_t envid = sys_exofork();
    if (envid < 0)
        panic("sys_exofork: %e", envid);
    if (envid == 0) {
        // 在子進程
        thisenv = &envs[ENVX(sys_getenvid())];
        return 0;
    }
    // 在父進程
    for (uintptr_t addr = UTEXT; addr < USTACKTOP; addr += PGSIZE)
        if (uvpd[PDX(addr)] & PTE_P && uvpt[PGNUM(addr)] & PTE_P)
            duppage(envid, PGNUM(addr));
    int r;
    // 給子進程分配 exception stack page
    if ((r = sys_page_alloc(envid, (void*)(UXSTACKTOP - PGSIZE), PTE_P | PTE_U | PTE_W)) < 0)
        panic("sys_page_alloc: %e", r);
    extern void _pgfault_upcall(void);
    if ((r = sys_env_set_pgfault_upcall(envid, _pgfault_upcall)) < 0)
        panic("sys_env_set_pgfault_upcall: %e", r);
    if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
        panic("sys_env_set_status: %e", r);
    return envid;
}
static int
duppage(envid_t envid, unsigned pn)
{
    int r;

    // LAB 4: Your code here.
    void* va = (void*)(pn << PGSHIFT);
    int perm = uvpt[pn] & 0xFFF;
    if ((perm & PTE_W) || (perm & PTE_COW)) {
        perm |= PTE_COW; // 增長 PTE_COW
        perm &= ~PTE_W;  // 減去 PTE_W
    }
    perm &= PTE_SYSCALL;
    if ((r = sys_page_map(0, va, envid, va, perm)) < 0)
        panic("sys_page_map: %e", r);
    // 不知爲什麼,這個放前面會出現亂碼
    if ((r = sys_page_map(0, va, 0, va, perm)) < 0)
        panic("sys_page_map: %e", r);
    return r;
}
static void
pgfault(struct UTrapframe *utf)
{
    void *addr = (void *) utf->utf_fault_va;
    uint32_t err = utf->utf_err;
    int r;

    // LAB 4: Your code here.
    if ((err & FEC_WR) == 0 || (uvpt[PGNUM(addr)] & PTE_COW) == 0)
        panic("pgfault: check faluting access failed");

    // LAB 4: Your code here.
    if ((r = sys_page_alloc(0, PFTEMP, PTE_P | PTE_U | PTE_W)) < 0)
        panic("sys_page_alloc: %e", r);
    addr = ROUNDDOWN(addr, PGSIZE);
    memcpy(PFTEMP, addr, PGSIZE);
    if ((r = sys_page_map(0, PFTEMP, 0, addr, PTE_P | PTE_U | PTE_W)) < 0)
        panic("sys_page_map: %e", r);
    if ((r = sys_page_unmap(0, PFTEMP)) < 0)
        panic("sys_page_unmap: %e", r);
}

make grade,part B 經過

Part C: Preemptive Multitasking and Inter-Process communication (IPC)

Exercise 13

kern/env.c: env_alloc()中:

// Enable interrupts while in user mode.
    // LAB 4: Your code here.
    e->env_tf.tf_eflags |= FL_IF;

取消kern/sched.csti的註釋:

void
sched_halt(void)
{
// 省略...
    asm volatile (
        "movl $0, %%ebp\n"
        "movl %0, %%esp\n"
        "pushl $0\n"
        "pushl $0\n"
        // Uncomment the following line after completing exercise 13
        "sti\n"
        "1:\n"
        "hlt\n"
        "jmp 1b\n"
    : : "a" (thiscpu->cpu_ts.ts_esp0));

kern/trapentry.S

TRAPHANDLER_NOEC(vector32, IRQ_OFFSET + IRQ_TIMER)
TRAPHANDLER_NOEC(vector33, IRQ_OFFSET + IRQ_KBD)
TRAPHANDLER_NOEC(vector36, IRQ_OFFSET + IRQ_SERIAL)
TRAPHANDLER_NOEC(vector39, IRQ_OFFSET + IRQ_SPURIOUS)
TRAPHANDLER_NOEC(vector46, IRQ_OFFSET + IRQ_IDE)
TRAPHANDLER_NOEC(vector51, IRQ_OFFSET + IRQ_ERROR)

kern/trap.c

void
trap_init(void)
{
// 省略...
    void vector32();
    void vector33();
    void vector36();
    void vector39();
    void vector46();
    void vector51();
// 省略...
    SETGATE(idt[IRQ_OFFSET + IRQ_TIMER], 0, GD_KT, vector32, 3)
    SETGATE(idt[IRQ_OFFSET + IRQ_KBD], 0, GD_KT, vector33, 3)
    SETGATE(idt[IRQ_OFFSET + IRQ_SERIAL], 0, GD_KT, vector36, 3)
    SETGATE(idt[IRQ_OFFSET + IRQ_SPURIOUS], 0, GD_KT, vector39, 3)
    SETGATE(idt[IRQ_OFFSET + IRQ_IDE], 0, GD_KT, vector46, 3)
    SETGATE(idt[IRQ_OFFSET + IRQ_ERROR], 0, GD_KT, vector51, 3)

Exercise 14

kern/trap.c

static void
trap_dispatch(struct Trapframe *tf)
{
// 省略...
    // LAB 4: Your code here.
    if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER) {
        lapic_eoi();
        sched_yield();
        return;
    }

Exercise 15

lib/ipc.c

void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
    // LAB 4: Your code here.
    int r = -E_IPC_NOT_RECV;
    pg = pg == NULL ? (void*)UTOP : pg;
    while (r == -E_IPC_NOT_RECV) {
        r = sys_ipc_try_send(to_env, val, pg, perm);
        if (r == 0) {
            sys_yield();
            return;
        }
    }
    panic("ipc_send faild: %e", r);
}
int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
    // LAB 4: Your code here.
    int r = sys_ipc_recv(pg == NULL ? (void*)UTOP : pg);
    if (from_env_store != NULL)
        *from_env_store = r == 0 ? thisenv->env_ipc_from : 0;
    if (perm_store != NULL)
        *perm_store = r == 0 ? thisenv->env_ipc_perm : 0;
    return r == 0 ? thisenv->env_ipc_value : r;
}

kern/syscall.c

// 使 sys_page_map 調用 envid2env 時暫時不檢查 perm
// 注:使用該值時,將其做爲參數,但不要實際改變pte的值
#define PTE_NOT_CHECK 0x200
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
    // LAB 4: Your code here.
    struct Env* env;
    int r;
    if ((r = envid2env(envid, &env, 0)) != 0)
        return r;
    if (env->env_ipc_recving == 0) // 若是接受方沒準備好接收數據
        return -E_IPC_NOT_RECV;
    // 若是接收方準備好接收了,那就傳遞數據
    env->env_status = ENV_RUNNABLE;
    env->env_ipc_recving = 0;
    env->env_ipc_from = curenv->env_id;
    env->env_ipc_value = value;
    env->env_ipc_perm = 0;
    env->env_tf.tf_regs.reg_eax = 0;
    // 映射頁面
    if (srcva != NULL && (uintptr_t)srcva < UTOP && env->env_ipc_dstva != NULL) {
        r = sys_page_map(0, srcva, envid, env->env_ipc_dstva, perm | PTE_NOT_CHECK);
        env->env_ipc_perm = perm;
    }
    return r;
}
static int
sys_ipc_recv(void *dstva)
{
    // LAB 4: Your code here.
    if ((uint32_t)dstva < UTOP && PGOFF(dstva) != 0) {
        return -E_INVAL;
    }
    curenv->env_ipc_recving = 1;
    curenv->env_ipc_dstva = dstva;
    curenv->env_status = ENV_NOT_RUNNABLE;
    sys_yield(); // 注:yield後,要經過tf的eax改返回值
    return 0;
}

修改sys_page_map部份內容以支持envid2env時不檢查perm的狀況:

static int
sys_page_map(envid_t srcenvid, void *srcva,
         envid_t dstenvid, void *dstva, int perm)
{
    // LAB 4: Your code here.
    struct Env *srcenv, *dstenv;
    bool chekperm = !(perm & PTE_NOT_CHECK);
    if ((envid2env(srcenvid, &srcenv, chekperm) != 0) || (envid2env(dstenvid, &dstenv, chekperm) != 0))
        return -E_BAD_ENV;
    perm &= ~PTE_NOT_CHECK; // 取消 PTE_NOT_CHECK

make grade,所有經過

相關文章
相關標籤/搜索