中断处理

中断处理是由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,否则则根据中断号来分别处理中断。

目前只有两种中断需要处理:

  • 页错误:目前我们直接输出错误

  • 时钟中断:改变系统时间并进行时间片轮转