中断处理
中断处理是由CPU和操作系统协作完成的,当中断发生时,CPU会根据中断号在系统数据结构IDT
(中断描述符表)中取出中断处理例程对应的gate descriptor
(中断描述符),gate descriptor
指向GDT
(全局描述符表)中的一个段描述符。段描述符和gate descriptor
中的段选择子被载入cs
(代码段)寄存器,最后CPU执行特权级的检查,如果目标代码段特权级比当前特权级高,将会从TSS
中取出对应的栈指针rsp
,切换栈(例如用户态发生中断,中断处理程序的目标代码段处于0特权级,这时从TSS中取出内核栈rsp并切换到内核栈)。最终CPU压入一些寄存器,跳转到中断号对应的中断向量去执行。
上述过程涉及大量x86体系结构的处理细节,若不能完全看懂也没关系,知道中断发生到中断处理例程中间的部分是由CPU完成的,并且可能涉及栈的切换即可。可以宏观上理解,TSS只是内存中的一个数据结构,但是其与CPU交互,其中偏移量为4的字段存放了当前线程的内核栈位置,当发生系统调用或中断从用户态进入内核时,需要手动(系统调用)或是CPU自动(用户态发生中断)地从TSS中取出内核栈并切换栈。
内核中断处理函数
/// 中断处理入口,由汇编直接调用无需手动调用
pub extern "C" fn trap_handler(tf: &mut TrapFrame) {
handle_trap(Some(tf), None, None);
}
/// 处理用户态的中断或系统调用
/// 若是系统调用则context中的trap_num一定为100
/// 若是中断则trap_num从context中获取
pub fn handle_user_trap(thread: Arc<Thread>, context: &UserContext) {
handle_trap(None, Some(thread), Some(context));
}
在上一节我们看到,不管是用户态发生中断和系统调用还是内核态发生中断,最终都调用handle_trap
函数。而且若是用户态发生系统调用,TrapFrame
库将trap_num
设置为0x100。
/// 中断/系统调用处理函数
pub fn handle_trap(
tf: Option<&mut TrapFrame>,
thread: Option<Arc<Thread>>,
context: Option<&UserContext>,
) {
// 用户态的中断或系统调用
if let Some(context) = context {
// 系统调用
if context.trap_num == 0x100 {
let thread = thread.unwrap();
thread.do_syscall();
return;
}
}
// 处理用户态或内核态中断
let trap_num = if tf.is_some() {
// 内核中断
tf.as_ref().unwrap().trap_num
} else {
// 用户中断
context.unwrap().trap_num
};
match trap_num {
// 页错误,目前直接panic
PAGE_FAULT => {
println!("[Trap Handler]: PAGEFAULT");
panic!("page fault");
}
// 时钟中断,轮转用户线程或内核线程
TIMER => {
pic::ack();
*pic::TICKS.get() += 1;
// 用户时钟
if let Some(thread) = thread {
// 时间片轮转
thread.try_set_state(ThreadState::Suspended);
// 内核时钟
} else if let Some(_tf) = tf {
// 当前内核线程主动调度
yield_current_kthread();
} else {
panic!("Should never happen!");
}
}
_ => {
println!("[Trap Handler]: unknown trap!");
panic!("unknown trap!");
}
}
}
先判断是否是用户态的系统调用,若是则跳转到系统调用分发函数syscall
,否则则根据中断号来分别处理中断。
目前只有两种中断需要处理:
-
页错误:目前我们直接输出错误
-
时钟中断:改变系统时间并进行时间片轮转