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; }
// 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
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
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!
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); }
//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));
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. ...
將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) ...
fork複製父進程的地址空間開銷很大,因此最新的unix使用了寫時複製的方法,即容許父進程和子進程共享內存映射直到一個進程實際上修改那部份內存的時候再複製
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"); }
// 設置 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)
在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 經過
在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.c
中sti
的註釋:
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)
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; }
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
,所有經過