Exercise 3. 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. 修改kern/syscall.c文件,添加注册页故障处理函数的功能: 1、修改sys_env_set_pgfault_upcall()函数
在代码中有两行被注释掉了,刚开始的时候我是用这两行代码检测传入的func是否合法,但是评分程序过不了才知道这里不要求验证func的有效性。不过我还是觉得在这里验证最好,毕竟往内核传入指针了阿。
// Set the page fault upcall for 'envid' by modifying the corresponding struct // Env's 'env_pgfault_upcall' field. When 'envid' causes a page fault, the // kernel will push a fault record onto the exception stack, then branch to // 'func'. // // Returns 0 on success, < 0 on error. Errors are: // -E_BAD_ENV if environment envid doesn't currently exist, // or the caller doesn't have permission to change envid. static int sys_env_set_pgfault_upcall(envid_t envid, void *func) { struct Env *e; if (envid2env(envid, &e, 1) <0) return -E_BAD_ENV; //if (user_mem_check(curenv, func, sizeof(void *), PTE_U|PTE_P) <0) // return -E_FAULT; e->env_pgfault_upcall = func; return 0; }
2、修改syscal函数,把sys_env_set_pgfault_upcall()注册给适当的调用号。
Exercise 4. 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?)
修改kern/trap.c,在异常处理中加入调用用户注册的页错误处理函数的功能
注意检查用户页错误处理函数是否注册,用户异常栈是否分配,是否溢出,是否页错误嵌套。并注意给新的UTrapframe和嵌套返回值保留空间。
//page fault handler void pf_hdl(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 == GD_KT) { print_trapframe(tf); panic("Page fault in kernel"); } // We've already handled kernel-mode exceptions, so if we get here, // the page fault happened in user mode. // Call the environment's page fault upcall, if one exists. Set up a // page fault stack frame on the user exception stack (below // UXSTACKTOP), then branch to curenv->env_pgfault_upcall. // // The page fault upcall might cause another page fault, in which case // we branch to the page fault upcall recursively, pushing another // page fault stack frame on top of the user exception stack. // // The trap handler needs one word of scratch space at the top of the // trap-time stack in order to return. In the non-recursive case, we // don't have to worry about this because the top of the regular user // stack is free. In the recursive case, this means we have to leave // an extra word between the current top of the exception stack and // the new stack frame because the exception stack _is_ the trap-time // stack. // // If there's no 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. //check page fault user handler && ux stack alloced && ux stack overflow if ( (curenv->env_pgfault_upcall != NULL) && ( (tf->tf_esp >=UXSTACKEND+sizeof(struct UTrapframe)+sizeof(int)) || (tf->tf_esp< USTACKTOP))) { user_mem_assert(curenv, curenv->env_pgfault_upcall, 1, PTE_U|PTE_P) ; user_mem_assert(curenv, (void *)UXSTACKEND, PGSIZE, PTE_U|PTE_W|PTE_P) ; struct UTrapframe *utf; //is a trap from ux stack ? if(tf->tf_esp< UXSTACKTOP && tf->tf_esp>=UXSTACKEND) {//yes utf = (struct UTrapframe *)(tf->tf_esp - sizeof(struct UTrapframe) - sizeof(int)); } else { utf = (struct UTrapframe *)(UXSTACKTOP - sizeof(struct UTrapframe)); } //prepare UTrapframe utf->utf_fault_va = fault_va; utf->utf_err = tf->tf_err; utf->utf_regs = tf->tf_regs; utf->utf_eip = tf->tf_eip; utf->utf_eflags = tf->tf_eflags; utf->utf_esp = tf->tf_esp; //set user pf handler entry tf->tf_eip = (uint32_t)curenv->env_pgfault_upcall; tf->tf_esp = (uint32_t)utf; // run user pf handler; env_run(curenv); } // Destroy the environment that caused the fault. cprintf("[x] user fault va x ip x/n", curenv->env_id, fault_va, tf->tf_eip); print_trapframe(tf); //monitor(tf); env_destroy(curenv); }
Exercise 5. 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.
修改lib/pfentry.S文件,在用户层加入用户页错误处理函数的入口功能。
step 0是调用用户注册的用户页错误处理函数。step 1 是用来设置返回到发生页错误(原始)的地址。 step 2是用来弹出通用寄存器。 step 3 是用来弹出状态寄存器。 step 4用来切换esp到原始栈。 step 5就是返回到原始地址。 各种注意事项代码的注释中已有说明,一定得认真看了再去写代码。
// Page fault upcall entrypoint. // This is where we ask the kernel to redirect us to whenever we cause // a page fault in user space (see the call to sys_set_pgfault_handler // in pgfault.c). // // When a page fault actually occurs, the kernel switches our ESP to // point to the user exception stack if we're not already on the user // exception stack, and then it pushes a UTrapframe onto our user // exception stack: // // trap-time esp // trap-time eflags // trap-time eip // utf_regs.reg_eax // ... // utf_regs.reg_esi // utf_regs.reg_edi // utf_err (error code) // utf_fault_va <-- %esp // // If this is a recursive fault, the kernel will reserve for us a // blank word above the trap-time esp for scratch work when we unwind // the recursive call. // // We then have call up to the appropriate page fault handler in C // code, pointed to by the global variable '_pgfault_handler'. .text .globl _pgfault_upcall _pgfault_upcall: // Call the C page fault handler. // Step 0: pushl %esp // function argument: pointer to UTF movl _pgfault_handler,