Part B: Page Faults, Breakpoints Exceptions, and System Calls
现在kernel已经有了基本的异常处理能力,我们将继续完善它的功能.
处理Page Fault
Exercise 5 要求修改trap_dispatch()
函数,把page fault错误派发给page_fault_handler()
处理,这个处理函数现在还没有完成,只是一个入口。
这个Exercise比较简单:
static void
trap_dispatch(struct Trapframe *tf)
{
// Handle processor exceptions.
// LAB 3: Your code here.
switch (tf->tf_trapno)
{
case T_PGFLT:
page_fault_handler(tf);
break;
default:
// Unexpected trap: The user process or the kernel has a bug.
print_trapframe(tf);
if (tf->tf_cs == GD_KT)
panic("unhandled trap in kernel");
else {
env_destroy(curenv);
}
break;
}
return;
}
后面会完善page_fault_handler
,在实现系统调用的时候。
断点异常
断点异常作为中断向量3 (T_BRKPT
)通常用来让调试器向程序代码中插入断点。一般会把相关的程序指令用一个特殊的1个byte长的int3
软件中断指令代替。JOS中略微泛用了这个概念,让它变成了一个任意用户环境都可以用来唤醒JOS kernel监视器的伪系统调用。如果我们把JOS kernel监视器看作是调试器的话,这种泛用也是恰当的。
Exercise 6 要求修改trap_dispatch
让断点异常唤醒kernel监视器。
这要改两个地方,一个是在switch中添加一个入口:
case T_BRKPT:
monitor(tf);
break;
另外一个是把IDT中对应的描述符的权限设成3,即用户可以引起断点异常中断。
SETGATE(idt[T_BRKPT], 0, GD_KT, &th_brkpt, 3);
Question
-
The break point test case will either generate a break point exception or a general protection fault depending on how you initialized the break point entry in the IDT (i.e., your call to SETGATE from trap_init). Why? How do you need to set it up in order to get the breakpoint exception to work as specified above and what incorrect setup would cause it to trigger a general protection fault?
Answer: Depending on how you have set DPL parameter in the IDT gates. Setting the DPL as user level privilege will get it work and as kernel level won’t. Also accessing a gate contains a null segment selector or the segment selector does not point to a code segment will also cause General Protection Fault. -
What do you think is the point of these mechanisms, particularly in light of what the
user/softint
test program does?
Answer: To restrict what user program could do and protect the kernel from being corrupted by user program.
系统调用
用户进程通过系统调用让kernel替它做一些它做不了的事情。当用户进程发起了一个系统调用,处理器和kernel一起保存用户进程状态,kernel执行代码实现系统调用,然后恢复用户进程现场。
JOS内核中,我们使用int
指令引起一个处理器中断。特别地,我们使用int $0x30
作为系统调用中断。注意中断0x30
不会由硬件产生,所以让用户代码可以产生这个中断没有歧义。
用户进程通过寄存器传递系统调用需要的调用号和调用参数。这样kernel不用去翻找用户栈或者指令流。系统调用的调用号会保存在%eax
寄存器中,最多五个调用参数则分别以此保存在%edx, %ecx, %ebx, %edi, %esi
寄存器之中。kernel把系统调用的返回值保存在%eax
寄存器中。唤起系统调用的代码已经在lib/syscall.c
中的syscall()
函数中为你写好了,仔细阅读搞明白它在做些什么。
Exercise 7 为系统调用的中断向量T_SYSCALL
添加一个中断处理程序。需要编辑kern/trapentry.S
和kern/trap.c
文件,还需要修改trap_dispatch()
通过唤起syscall()
函数处理系统调用中断,并且安排返回值从%eax
寄存器中传回给用户进程。最后需要实现kern/syscall.c
中的syscall()
函数。
补充syscall
如下:
// Dispatches to the correct kernel function, passing the arguments.
int32_t
syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
{
// Call the function corresponding to the 'syscallno' parameter.
// Return any appropriate return value.
// LAB 3: Your code here.
switch (syscallno) {
case SYS_cgetc:
return sys_cgetc();
case SYS_cputs:
sys_cputs((const char*)a1, a2);
return 0;
case SYS_env_destroy:
return sys_env_destroy(a1);
case SYS_getenvid:
return sys_getenvid();
default:
return -E_INVAL;
}
}
在trapentry.S
中添加:
TRAPHANDLER_NOEC(th_syscall, T_SYSCALL)
在trap.c
中添加:
void th_syscall();
SETGATE(idt[T_SYSCALL], 0, GD_KT, &th_syscall, 3);
以及
case T_SYSCALL:
tf->tf_regs.reg_eax = syscall(tf->tf_regs.reg_eax,tf->tf_regs.reg_edx,
tf->tf_regs.reg_ecx,tf