持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
在上篇文章中,我们有提到 用户级页面错误处理 和 设置页面错误处理程序,这节就来讲讲,调用用户页错误处理程序,这段还是有不少的代码内容的
调用用户页错误处理程序
现在,您需要在 kern/trap.c 中更改页面错误处理代码,以便从用户模式处理页面错误,如下所示。我们将发生故障时用户环境的状态称为陷阱时间状态。
如果没有注册页面错误处理程序,JOS 内核将像以前一样使用消息销毁用户环境。否则,内核会在异常堆栈上设置一个陷阱帧,该帧看起来像来自 inc/trap.h:struct UTrapframe
<-- UXSTACKTOP
trap-time esp
trap-time eflags
trap-time eip
trap-time eax start of struct PushRegs
trap-time ecx
trap-time edx
trap-time ebx
trap-time esp
trap-time ebp
trap-time esi
trap-time edi end of struct PushRegs
tf_err (error code)
fault_va <-- %esp when handler is run
然后,内核安排用户环境恢复执行,页面错误处理程序在具有此堆栈帧的异常堆栈上运行;你必须弄清楚如何做到这一点。fault_va是导致页面错误的虚拟地址。
如果发生异常时用户环境已在用户异常堆栈上运行,则页面错误处理程序本身已出错。在这种情况下,您应该在当前堆栈帧tf->tf_esp的正下方启动新堆栈帧,而不是在UXSTACKTOP处启动。您应该首先压入一个空的 32 位字,然后压入一个 struct UTrapframe.
要测试tf->tf_esp是否已在用户异常堆栈上,请检查它是否在 UXSTACKTOP-PGSIZE和 UXSTACKTOP-1(包括)之间的范围。
练习 9.在 kern/trap.c 中的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) == 0) {
panic("page_fault_handler: system page fault.\n");
}
// LAB 4: Your code here.
if (curenv->env_pgfault_upcall) {
size_t stacktop = UXSTACKTOP;
if (tf->tf_esp >= UXSTACKTOP - PGSIZE && tf->tf_esp < UXSTACKTOP) {
stacktop = tf->tf_esp - sizeof(size_t);
}
struct UTrapframe *utf = (struct UTrapframe *) (stacktop - sizeof(struct UTrapframe));
user_mem_assert(curenv, (void *) utf, sizeof(struct UTrapframe), PTE_W | PTE_U | PTE_P);
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;
curenv->env_tf.tf_esp = (uintptr_t) utf;
curenv->env_tf.tf_eip = (uintptr_t) curenv->env_pgfault_upcall;
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);
}
这里如果触发了页面异常,我们应该设置新的栈顶的位置为UXSTACKTOP,然后通过结构体UTrapframe去进行记录其中的信息。这里注意使用user_mem_assert函数去检查内存是否安全,将异常信息记录完毕后,更新tf_esp和tf_eip,其中tf_eip就是我们新添加的页面异常的错误句柄。最后通过env_run重新启动用户异常的环境。
特别注意,当在错误异常处理时如果又发生了异常,处理是一个递归的过程。我们只需要再次设置异常栈的信息并且将要更新的环境设置为当前出错的异常栈即可。但是特别注意我们需要留出一个空白字的位置(4字节),以下一次进行恢复错误栈。
如果异常栈溢出怎么办?我们分配给异常栈的大小只有一页,如果产生溢出那么就会触发内核的页面错误(很明显这个溢出我们暂时没有办法解决),然后直接触发panic。
到目前为止我们已经接触了三个栈内容:
[KSTACKTOP,KSTACKTOP-KSTKSIZE] 内核栈
[USTACKTOP,UTEXT] 用户栈
[UXSTACKTOP,UXSTACKTOP-PGSIZE] 用户异常栈(至于怎么触发到这里的过程 再回去看看trap_init_percpu吧)
所以在正常的运行过程中,触发页面错误我们的处理过程是:
用户运行栈->内核栈->异常栈
如果错误处理时产生了递归的错误,那么就是:
异常栈->内核栈->异常栈