本文中,我们将设置可编程中断控制器,以将硬件中断正确转发到CPU。 为了处理这些中断,我们将新条目添加到中断描述符表中,就像我们对异常处理程序所做的一样。 我们将学习如何获取定期的计时器中断以及如何从键盘获取输入。

总览

中断提供了一种从连接的硬件设备通知CPU的方法。 因此,键盘不必通知内核定期检查键盘上是否有新字符(称为轮询的过程),而是可以将每次按键通知内核。 因为内核仅在发生某些事情时才采取行动,所以效率更高。 它还可以缩短响应时间,因为内核不仅可以在下一次轮询时立即做出反应。 无法将所有硬件设备直接连接到CPU。 而是由一个单独的中断控制器汇总来自所有设备的中断,然后通知CPU: 大多数中断控制器是可编程的,这意味着它们支持不同的中断优先级。 例如,这可以使计时器中断比键盘中断具有更高的优先级,以确保准确计时。 与异常不同,硬件中断是异步发生的。 这意味着它们与执行的代码完全独立,并且可以随时发生。 因此,我们的内核突然有了一种并发形式,其中包含所有潜在的与并发相关的错误。 Rust严格的所有权模型在这里为我们提供了帮助,因为它禁止了可变的全局状态。 但是,仍然有可能出现死锁,正如我们将在本文后面看到的那样。

The 8259 PIC

英特尔8259是1976年推出的可编程中断控制器(PIC)。长期以来,它已被较新的APIC取代,但由于向后兼容的原因,当前系统仍支持其接口。 8259 PIC的建立比APIC容易得多,因此在以后的文章中切换到APIC之前,我们将使用它来引入中断。 8259有8条中断线和几条用于与CPU通讯的线。 当时的典型系统配备了8259 PIC的两个实例,一个实例与一个实例PIC连接到实例之一的中断线: 该图显示了中断线的典型分配。 我们看到15条线中的大多数都有固定的映射,例如 辅助PIC的第4行分配给了鼠标。 每个控制器可以通过两个I / O端口,一个“命令”端口和一个“数据”端口进行配置。 对于主控制器,这些端口是0x20(命令)和0x21(数据)。 对于辅助控制器,它们是0xa0(命令)和0xa1(数据)。 有关如何配置PIC的更多信息,请参见osdev.org上的文章。

ip:instruction pointer 指令指针 sp:stack pointer 栈指针 ss:stack segment 栈段寄存器 cs:code segment 代码段寄存器

硬件中断处理理解

notify_end_of_interrupt方法只向中断控制器(interrupt controller)发出信号,表明我们已经处理完中断,以便它在可能发送的下一次请求。但是,中断在CPU上默认保持禁用状态,直到中断处理程序返回。因此,即使中断控制器在中断调用的notify_end_of_interrupt之后立即引发中断,在处理程序返回之前CPU也不会开始处理它。 在自定义的idt上添加一个键盘中断的中断处理函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// in src/interrupts.rs

#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
    Timer = PIC_1_OFFSET,
    Keyboard, // new 索引号
}

lazy_static! {
    static ref IDT: InterruptDescriptorTable = {
        let mut idt = InterruptDescriptorTable::new();
        idt.breakpoint.set_handler_fn(breakpoint_handler);
        []
        // new 为Keyboard索引号设置中断处理函数
        idt[InterruptIndex::Keyboard.as_usize()]
            .set_handler_fn(keyboard_interrupt_handler);

        idt
    };
}

extern "x86-interrupt" fn keyboard_interrupt_handler(
    _stack_frame: &mut InterruptStackFrame)
{
    print!("k");

    unsafe {
        PICS.lock()
            .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); //notify_end_of_interrupt告诉cpu此中断已处理完毕
    }
}