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 "); } // 这里我们是处理用户模式下的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 ", 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! "); if (sys_env_set_pgfault_upcall(thisenv->env_id, _pgfault_upcall)) panic("fail to set pgfault upcall! "); //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. "); //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 ", 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 ", 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 ", 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 ", 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 ", 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 ", r); return who; //return 0 我居然在fork里一直返回0??? panic("fork not implemented"); //panic("fork not implemented");` }