MIT 6.828 操作系统工程 lab4B:Copy-on-Write Fork
这篇是我自己探索实现 MIT 6.828 lab 的笔记记录,会包含一部分代码注释和要求的翻译记录,以及踩过的坑/个人的解决方案
这里是我实现的完整代码仓库,也包含其他笔记等等:https://github.com/yunwei37/6.828-2018-labs
如前所述,Unix 提供fork()系统调用作为其主要的进程创建原语。该fork()系统调用将调用进程的地址空间(父)创建一个新的进程(孩子)。
在本实验的下一部分中,您将实现一个“正确的”类 Unix fork() 和写时复制,作为用户空间库例程。
用户级页面错误处理
用户级写时复制fork()需要了解写保护页面上的页面错误,因此这是您首先要实现的。写时复制只是用户级页面错误处理的众多可能用途之一。
为了处理自己的页面错误,用户环境需要向JOS 内核注册一个页面错误处理程序入口点。用户环境通过新的sys_env_set_pgfault_upcall系统调用注册其页面错误入口点。
练习 8. 实现sys_env_set_pgfault_upcall系统调用
// 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)
{
// LAB 4: Your code here.
struct Env* new_env;
int result;
if ((result = envid2env(envid, &new_env, 1)) < 0){
return result;
}
new_env->env_pgfault_upcall = func;
return 0;
}
用户环境中的正常和异常堆栈
在正常执行过程中,JOS用户环境将在运行正常的用户堆栈:它的ESP注册开始了在指向USTACKTOP且堆栈数据之间是推动在页面上驻留USTACKTOP-PGSIZE和USTACKTOP-1包容性。然而,当在用户模式下发生页面错误时,内核将重新启动用户环境,在不同的堆栈上运行指定的用户级页面错误处理程序,即用户异常堆栈。本质上,我们将让 JOS 内核代表用户环境实现自动“堆栈切换”,这与 x86处理器 在从用户模式转换到内核模式时已经代表 JOS 实现堆栈切换非常相似!
JOS 用户异常栈也是一页大小,其顶部定义为虚拟地址UXSTACKTOP.
练习 9.page_fault_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.
if ((tf->tf_cs & 3) != 3) {
panic("[%08x] kernel fault va %08x ip %08x\n",
curenv->env_id, fault_va, tf->tf_eip);
}
// 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.
//
// It is convenient for our code which returns from a page fault
// (lib/pfentry.S) to have one word of scratch space at the top of the
// trap-time stack; it allows us to more easily restore the eip/esp. 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.
//
// 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.
if (curenv->env_pgfault_upcall) {
struct UTrapframe *utf;
user_mem_assert(curenv, curenv->env_pgfault_upcall, 1, PTE_P|PTE_U);
if (curenv->env_tf.tf_esp <= UXSTACKTOP-1 && curenv->env_tf.tf_esp >= UXSTACKTOP - PGSIZE) {
utf = (struct UTrapframe*)(curenv->env_tf.tf_esp - sizeof(size_t) - sizeof(*utf));
if (utf < (struct UTrapframe*)(UXSTACKTOP - PGSIZE)) {
cprintf("the exception stack overflows.");
goto out;
}
} else {
utf = (struct UTrapframe*)(UXSTACKTOP - sizeof(*utf));
}
user_mem_assert(curenv, utf, sizeof(*utf), PTE_P|PTE_U|PTE_W);
utf->utf_regs = curenv->env_tf.tf_regs;
utf->utf_esp = curenv->env_tf.tf_esp;
utf->utf_eflags