diff --git a/kern/env.c b/kern/env.c index 1a5af25..df3a9cd 100644 --- a/kern/env.c +++ b/kern/env.c @@ -356,6 +356,7 @@ load_icode(struct Env *e, uint8_t *binary) // LAB 3: Your code here. // TODO validate the headers + lcr3(PADDR(e->env_pgdir)); struct Elf* elf = (struct Elf*) binary; struct Proghdr* ph = (struct Proghdr*) (binary + elf->e_phoff); struct Proghdr* phend = ph + elf->e_phnum; @@ -363,11 +364,10 @@ load_icode(struct Env *e, uint8_t *binary) if(ph->p_type != ELF_PROG_LOAD) continue; region_alloc(e, (void*) ph->p_va, ph->p_memsz); - lcr3(PADDR(e->env_pgdir)); memcpy((void*) ph->p_va, binary + ph->p_offset, ph->p_filesz); memset((void*) ph->p_va + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz); - lcr3(PADDR(kern_pgdir)); } + lcr3(PADDR(kern_pgdir)); e->env_tf.tf_eip = elf->e_entry; // Now map one page for the program's initial stack @@ -528,6 +528,7 @@ env_run(struct Env *e) e->env_status = ENV_RUNNING; e->env_runs++; lcr3(PADDR(e->env_pgdir)); + unlock_kernel(); env_pop_tf(&e->env_tf); } diff --git a/kern/init.c b/kern/init.c index 02ce70d..1d8699e 100644 --- a/kern/init.c +++ b/kern/init.c @@ -55,6 +55,7 @@ i386_init(void) // Acquire the big kernel lock before waking up APs // Your code here: + lock_kernel(); // Starting non-boot CPUs boot_aps(); @@ -64,7 +65,10 @@ i386_init(void) ENV_CREATE(TEST, ENV_TYPE_USER); #else // Touch all you want. - ENV_CREATE(user_primes, ENV_TYPE_USER); + ENV_CREATE(user_yield, ENV_TYPE_USER); + ENV_CREATE(user_yield, ENV_TYPE_USER); + ENV_CREATE(user_yield, ENV_TYPE_USER); + ENV_CREATE(user_yield, ENV_TYPE_USER); #endif // TEST* // Schedule and run the first user environment! @@ -121,6 +125,8 @@ mp_main(void) // only one CPU can enter the scheduler at a time! // // Your code here: + lock_kernel(); + sched_yield(); // Remove this after you finish Exercise 6 for (;;); diff --git a/kern/pmap.c b/kern/pmap.c index 9f49367..a294548 100644 --- a/kern/pmap.c +++ b/kern/pmap.c @@ -195,6 +195,7 @@ mem_init(void) // - the new image at UENVS -- kernel R, user R // - envs itself -- kernel RW, user NONE // LAB 3: Your code here. + cprintf("Mapping envs from %p to %p\n", UENVS, ROUNDUP(envs_size, PGSIZE)); boot_map_region(kern_pgdir, UENVS, ROUNDUP(envs_size, PGSIZE), PADDR(envs), PTE_U); @@ -276,6 +277,11 @@ mem_init_mp(void) // Permissions: kernel RW, user NONE // // LAB 4: Your code here: + for(int i = 0; i < NCPU; i++) { + uintptr_t kstacktop = KSTACKTOP - i * (KSTKSIZE + KSTKGAP); + boot_map_region(kern_pgdir, kstacktop - KSTKSIZE, + KSTKSIZE, PADDR(percpu_kstacks[i]), PTE_W); + } } @@ -289,6 +295,7 @@ is_reserved(size_t pagenum) { if(pagenum == 0) return true; if(pagenum >= PGNUM(IOPHYSMEM) && pagenum < PGNUM(PADDR(boot_alloc(0)))) return true; + if(pagenum == PGNUM(MPENTRY_PADDR)) return true; return false; } @@ -596,7 +603,15 @@ mmio_map_region(physaddr_t pa, size_t size) // Hint: The staff solution uses boot_map_region. // // Your code here: - panic("mmio_map_region not implemented"); + size = ROUNDUP(size, PGSIZE); + if((base + size) > MMIOLIM) + panic("Not enough memory-mapped IO space!"); + + boot_map_region(kern_pgdir, base, size, pa, PTE_PCD | PTE_PWT | PTE_W); + uintptr_t to_return = base; + base += size; + + return (void*) to_return; } static uintptr_t user_mem_check_addr; diff --git a/kern/sched.c b/kern/sched.c index 9b3939f..cba0cac 100644 --- a/kern/sched.c +++ b/kern/sched.c @@ -29,6 +29,23 @@ sched_yield(void) // below to halt the cpu. // LAB 4: Your code here. + struct Env* next_env = curenv ? curenv + 1 : envs; + struct Env* end_env = envs + NENV; + struct Env* to_run = NULL; + + for(int i = 0; i < NENV; i++, next_env++) { + if(next_env == end_env) next_env = envs; + if(next_env->env_status == ENV_RUNNABLE) { + to_run = next_env; + break; + } + } + + if(!to_run && curenv && curenv->env_status == ENV_RUNNING) { + to_run = curenv; + } + + if(to_run) env_run(to_run); // sched_halt never returns sched_halt(); diff --git a/kern/syscall.c b/kern/syscall.c index 2483269..ca3a35c 100644 --- a/kern/syscall.c +++ b/kern/syscall.c @@ -80,9 +80,16 @@ sys_exofork(void) // status is set to ENV_NOT_RUNNABLE, and the register set is copied // from the current environment -- but tweaked so sys_exofork // will appear to return 0. + struct Env* new_env; + int error_code; + + error_code = env_alloc(&new_env, curenv->env_id); + if(error_code < 0) return error_code; - // LAB 4: Your code here. - panic("sys_exofork not implemented"); + new_env->env_tf = curenv->env_tf; + new_env->env_tf.tf_regs.reg_eax = 0; + new_env->env_status = ENV_NOT_RUNNABLE; + return new_env->env_id; } // Set envid's env_status to status, which must be ENV_RUNNABLE @@ -100,9 +107,17 @@ sys_env_set_status(envid_t envid, int status) // You should set envid2env's third argument to 1, which will // check whether the current environment has permission to set // envid's status. + struct Env* env; + int error_code; - // LAB 4: Your code here. - panic("sys_env_set_status not implemented"); + error_code = envid2env(envid, &env, 1); + if(error_code < 0) return error_code; + + if(status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE) + return -E_INVAL; + + env->env_status = status; + return 0; } // Set the page fault upcall for 'envid' by modifying the corresponding struct @@ -116,10 +131,18 @@ sys_env_set_status(envid_t envid, int status) static int sys_env_set_pgfault_upcall(envid_t envid, void *func) { - // LAB 4: Your code here. - panic("sys_env_set_pgfault_upcall not implemented"); + struct Env* env; + int return_code; + if((return_code = envid2env(envid, &env, 1)) < 0) return return_code; + env->env_pgfault_upcall = func; + return 0; } +#define SYS_CHECKPERMS(perm) \ + ((((perm) & (PTE_P | PTE_U)) == (PTE_P | PTE_U)) && \ + (((perm) & ~(PTE_P | PTE_U | PTE_W | PTE_AVAIL)) == 0)) +#define SYS_CHECKADDR(addr) (((uintptr_t) (addr) < UTOP) && ((uintptr_t) (addr) % PGSIZE == 0)) + // Allocate a page of memory and map it at 'va' with permission // 'perm' in the address space of 'envid'. // The page's contents are set to 0. @@ -145,9 +168,22 @@ sys_page_alloc(envid_t envid, void *va, int perm) // parameters for correctness. // If page_insert() fails, remember to free the page you // allocated! + struct Env* env; + int return_code; + if((return_code = envid2env(envid, &env, 1)) < 0) return return_code; + + if(!SYS_CHECKPERMS(perm)) return -E_INVAL; + if(!SYS_CHECKADDR(va)) return -E_INVAL; - // LAB 4: Your code here. - panic("sys_page_alloc not implemented"); + struct PageInfo* page = page_alloc(1); + if(!page) return -E_NO_MEM; + + if((return_code = page_insert(env->env_pgdir, page, va, perm)) < 0) { + page_free(page); + return return_code; + } + + return 0; } // Map the page of memory at 'srcva' in srcenvid's address space @@ -176,9 +212,24 @@ sys_page_map(envid_t srcenvid, void *srcva, // parameters for correctness. // Use the third argument to page_lookup() to // check the current permissions on the page. + struct Env *srcenv, *dstenv; + pte_t* srcpte; + int return_code; - // LAB 4: Your code here. - panic("sys_page_map not implemented"); + if((return_code = envid2env(srcenvid, &srcenv, 1)) < 0) return return_code; + if((return_code = envid2env(dstenvid, &dstenv, 1)) < 0) return return_code; + + if(!SYS_CHECKADDR(srcva)) return -E_INVAL; + if(!SYS_CHECKADDR(dstva)) return -E_INVAL; + if(!SYS_CHECKPERMS(perm)) return -E_INVAL; + + struct PageInfo* page = page_lookup(srcenv->env_pgdir, srcva, &srcpte); + if(page == NULL) return -E_INVAL; + if(perm & PTE_W && !(*srcpte & PTE_W)) return -E_INVAL; + + if((return_code = page_insert(dstenv->env_pgdir, page, dstva, perm)) < 0) + return return_code; + return 0; } // Unmap the page of memory at 'va' in the address space of 'envid'. @@ -192,9 +243,14 @@ static int sys_page_unmap(envid_t envid, void *va) { // Hint: This function is a wrapper around page_remove(). + struct Env* env; + int return_code; - // LAB 4: Your code here. - panic("sys_page_unmap not implemented"); + if((return_code = envid2env(envid, &env, 1)) < 0) return return_code; + if(!SYS_CHECKADDR(va)) return -E_INVAL; + + page_remove(env->env_pgdir, va); + return 0; } // Try to send 'value' to the target env 'envid'. @@ -279,6 +335,21 @@ syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, return sys_getenvid(); case SYS_env_destroy: return sys_env_destroy(a1); + case SYS_yield: + sys_yield(); + return 0; + case SYS_exofork: + return sys_exofork(); + case SYS_env_set_status: + return sys_env_set_status(a1, a2); + case SYS_env_set_pgfault_upcall: + return sys_env_set_pgfault_upcall(a1, (void*) a2); + case SYS_page_alloc: + return sys_page_alloc(a1, (void*) a2, a3); + case SYS_page_map: + return sys_page_map(a1, (void*) a2, a3, (void*) a4, a5); + case SYS_page_unmap: + return sys_page_unmap(a1, (void*) a2); default: return -E_INVAL; } diff --git a/kern/trap.c b/kern/trap.c index 4b90526..6cf1d88 100644 --- a/kern/trap.c +++ b/kern/trap.c @@ -160,18 +160,18 @@ trap_init_percpu(void) // Setup a TSS so that we get the right stack // when we trap to the kernel. - ts.ts_esp0 = KSTACKTOP; - ts.ts_ss0 = GD_KD; - ts.ts_iomb = sizeof(struct Taskstate); + thiscpu->cpu_ts.ts_esp0 = KSTACKTOP - thiscpu->cpu_id * (KSTKSIZE + KSTKGAP); + thiscpu->cpu_ts.ts_ss0 = GD_KD; + thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate); // Initialize the TSS slot of the gdt. - gdt[GD_TSS0 >> 3] = SEG16(STS_T32A, (uint32_t) (&ts), + gdt[(GD_TSS0 >> 3) + cpunum()] = SEG16(STS_T32A, (uint32_t) (&thiscpu->cpu_ts), sizeof(struct Taskstate) - 1, 0); - gdt[GD_TSS0 >> 3].sd_s = 0; + gdt[(GD_TSS0 >> 3) + cpunum()].sd_s = 0; // Load the TSS selector (like other segment selectors, the // bottom three bits are special; we leave them 0) - ltr(GD_TSS0); + ltr(GD_TSS0 + (cpunum() << 3)); // Load the IDT lidt(&idt_pd); @@ -294,6 +294,7 @@ trap(struct Trapframe *tf) // Acquire the big kernel lock before doing any // serious kernel work. // LAB 4: Your code here. + lock_kernel(); assert(curenv); // Garbage collect if current enviroment is a zombie @@ -373,11 +374,39 @@ page_fault_handler(struct Trapframe *tf) // (the 'tf' variable points at 'curenv->env_tf'). // LAB 4: Your code here. + if(!curenv->env_pgfault_upcall) { + // 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); + } + user_mem_assert(curenv, curenv->env_pgfault_upcall, 1, PTE_U | PTE_P); + user_mem_assert(curenv, (void*) UXSTACKTOP - 1, 1, PTE_U | PTE_P | PTE_W); - // 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); + uintptr_t top_addr = UXSTACKTOP; + if(tf->tf_esp <= UXSTACKTOP && tf->tf_esp >= (UXSTACKTOP - PGSIZE)) { + top_addr = tf->tf_esp - 4; + } + + struct UTrapframe utf; + 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; + + struct UTrapframe* push_to = (struct UTrapframe*) top_addr - 1; + if((uintptr_t) push_to < USTACKTOP - PGSIZE) { + cprintf("[%08x] stack overflow in page fault handler\n", + curenv->env_id); + env_destroy(curenv); + } + + *push_to = utf; + curenv->env_tf.tf_eip = (uintptr_t) curenv->env_pgfault_upcall; + curenv->env_tf.tf_esp = (uintptr_t) push_to; + env_run(curenv); } diff --git a/lib/fork.c b/lib/fork.c index 61264da..b8dcfa3 100644 --- a/lib/fork.c +++ b/lib/fork.c @@ -23,18 +23,22 @@ pgfault(struct UTrapframe *utf) // Hint: // Use the read-only page table mappings at uvpt // (see ). - - // LAB 4: Your code here. + if(!((err & FEC_WR) && (uvpt[(uintptr_t) addr >> PGSHIFT] & PTE_COW))) + panic("page fault (addr %p)! %c", addr, (err & FEC_WR) ? 'w' : 'r'); // 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. + void* temp_addr = (void*) PFTEMP; + void* fault_addr = ROUNDDOWN(addr, PGSIZE); + if(sys_page_alloc(0, temp_addr, PTE_P | PTE_W | PTE_U) < 0) + panic("failed to allocate new page"); - // LAB 4: Your code here. - - panic("pgfault not implemented"); + memcpy(temp_addr, fault_addr, PGSIZE); + sys_page_map(0, temp_addr, 0, fault_addr, PTE_P | PTE_U | PTE_W); + sys_page_unmap(0, temp_addr); } // @@ -52,9 +56,27 @@ static int duppage(envid_t envid, unsigned pn) { int r; + bool change_own = false; + pte_t new_pte = uvpt[pn]; + pte_t perms = new_pte & (PTE_P | PTE_U | PTE_W | PTE_AVAIL); + void* addr = (void*) (pn * PGSIZE); + + // If we're writable, remove write permission + if((new_pte & PTE_W) || (new_pte & PTE_COW)) { + perms = (perms & ~PTE_W) | PTE_COW; + change_own = true; + } + + // Map either with the same permissions or with COW. + if((r = sys_page_map(0, addr, envid, addr, perms)) < 0) + return r; + + // Update our own permissions if necessary + if(change_own) { + if((r = sys_page_map(0, addr, 0, addr, perms)) < 0) + return r; + } - // LAB 4: Your code here. - panic("duppage not implemented"); return 0; } @@ -77,8 +99,43 @@ duppage(envid_t envid, unsigned pn) envid_t fork(void) { - // LAB 4: Your code here. - panic("fork not implemented"); + set_pgfault_handler(pgfault); + + int return_code; + envid_t forked; + + forked = sys_exofork(); + if(forked < 0) return forked; + if(forked == 0) { thisenv = &envs[ENVX(sys_getenvid())]; return 0; } + + // Map all accessible page directory entries + for(int pde_i = 0; pde_i < PDX(UTOP); pde_i++) { + pde_t pde = uvpd[pde_i]; + if(!(pde & PTE_P)) continue; + + // For each PDE, map all the underlying PTEs + for(int pte_i = 0; pte_i < NPTENTRIES; pte_i++) { + int pn = pde_i * NPTENTRIES + pte_i; + pte_t pte = uvpt[pn]; + if(!(pte & PTE_P)) continue; + + // Do not map user exception stack, though + if(pn == ((UXSTACKTOP - PGSIZE) >> PGSHIFT)) continue; + + if((return_code = duppage(forked, pn)) < 0) return return_code; + } + } + + // Allocate new page for the exception stack + return_code = sys_page_alloc(forked, (void*) UXSTACKTOP - PGSIZE, + PTE_P | PTE_U | PTE_W); + if(return_code < 0) return return_code; + + // Set the upcall entry point + sys_env_set_pgfault_upcall(forked, thisenv->env_pgfault_upcall); + sys_env_set_status(forked, ENV_RUNNABLE); + + return forked; } // Challenge! diff --git a/lib/pfentry.S b/lib/pfentry.S index f40aeeb..082a00a 100644 --- a/lib/pfentry.S +++ b/lib/pfentry.S @@ -65,18 +65,29 @@ _pgfault_upcall: // ways as registers become unavailable as scratch space. // // LAB 4: Your code here. + mov 40(%esp), %eax // Take the EIP from memory + mov 48(%esp), %ebp // Take the ESP from memory + sub $4, %ebp // Push onto trap-time ESP + mov %eax, (%ebp) + mov %ebp, 48(%esp) // Put ESP back // Restore the trap-time registers. After you do this, you // can no longer modify any general-purpose registers. // LAB 4: Your code here. + add $0x8, %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. + add $0x4, %esp + popfl // Switch back to the adjusted trap-time stack. // LAB 4: Your code here. + pop %esp // Return to re-execute the instruction that faulted. // LAB 4: Your code here. + ret diff --git a/lib/pgfault.c b/lib/pgfault.c index a975518..ed378b8 100644 --- a/lib/pgfault.c +++ b/lib/pgfault.c @@ -27,9 +27,9 @@ 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"); + if(sys_page_alloc(0, (void*) UXSTACKTOP - PGSIZE, PTE_U | PTE_P | PTE_W) < 0) + panic("set_pgfault_handler"); + sys_env_set_pgfault_upcall(0, _pgfault_upcall); } // Save handler pointer for assembly to call.