这一次我们来对中断中的外中断进行讲解,先给下中断的分类和中断号分配把。

操作系统实现-外中断-LMLPHP

前面对异常进行了讲解,这次对外中断进行说明。我们下面以时钟中断举例,首先我们要知道的是,时钟中断是操作系统自己发生的,比如我们在执行一段代码时候,可能不知道啥时候,就会触发时钟中断,我们为了体现触发了时钟中断,我们就打印一句话就行。

同样我们给每个外中断号定义一个入口函数

INTERRUPT_HANDLER 0x20, 0; clock 时钟中断
INTERRUPT_HANDLER 0x21, 0; 键盘中断
INTERRUPT_HANDLER 0x22, 0
INTERRUPT_HANDLER 0x23, 0
INTERRUPT_HANDLER 0x24, 0
INTERRUPT_HANDLER 0x25, 0
INTERRUPT_HANDLER 0x26, 0
INTERRUPT_HANDLER 0x27, 0
INTERRUPT_HANDLER 0x28, 0
INTERRUPT_HANDLER 0x29, 0
INTERRUPT_HANDLER 0x2a, 0
INTERRUPT_HANDLER 0x2b, 0
INTERRUPT_HANDLER 0x2c, 0
INTERRUPT_HANDLER 0x2d, 0
INTERRUPT_HANDLER 0x2e, 0
INTERRUPT_HANDLER 0x2f, 0

在这个函数中,同样设置一个具体的函数

void idt_init()
{
    // 初始化ENTRY_SIZE个中断处理函数
    for (size_t i = 0; i < ENTRY_SIZE; i++)
    {
        gate_t *gate = &idt[i];
        // gate->offset0 = (u32)interrupt_handler & 0xffff;
        // gate->offset1 = ((u32)interrupt_handler >> 16) & 0xffff;
        handler_t handler = handler_entry_table[i];

        gate->offset0 = (u32)handler & 0xffff;
        gate->offset1 = ((u32)handler >> 16) & 0xffff;
        gate->selector = 1 << 3; // 代码段
        gate->reserved = 0;      // 保留不用
        gate->type = 0b1110;     // 中断门
        gate->segment = 0;       // 系统段
        gate->DPL = 0;           // 内核态
        gate->present = 1;       // 有效
    }
    // 异常的具体处理函数
    for (size_t i = 0; i < 0x20; i++)
    {
        handler_table[i] = exception_handler;
    }
    // 外中断的具体处理函数
    for (size_t i = 0x20; i < ENTRY_SIZE; i++)
    {
        handler_table[i] = default_handler;
    }

    idt_ptr.base = (u32)idt;
    idt_ptr.limit = sizeof(idt) - 1;

    asm volatile("lidt idt_ptr\n");
}
void default_handler(int vector)
{
    send_eoi(vector);
    LOGK("[%d] default interrupt called %d...\n", vector, counter++);
}

注意我们有一个send_eoi函数,这是发送结束中断的函数,因为我们比如时钟中断产生,那么首先是结束中断,然后才是对其进行处理,不然后续就不会再次发生中断,其代码如下

void send_eoi(int vector)
{
    if (vector >= 0x20 && vector < 0x28)
    {
        outb(PIC_M_CTRL, PIC_EOI);
    }
    if (vector >= 0x28 && vector < 0x30)
    {
        outb(PIC_M_CTRL, PIC_EOI);
        outb(PIC_S_CTRL, PIC_EOI);
    }
}

最后就是main函数的编写

void kernel_init()
{
	console_init();
    gdt_init();
    interrupt_init();

    // asm volatile(
    // "sti\n"
    // "movl %eax, %eax\n");

    asm volatile("sti");

    u32 counter = 0;
    while (true)
    {
        DEBUGK("looping in kernel init %d...\n", counter++);
        delay(100000000);
    }
}

其中最主要的就是asm volatile("sti"),这行表示打开中断,否则不会触发,还有一个关键,怎么让cpu知道是时钟中断呢,这就要中断控制器进行设置,下面是设置代码

// 初始化中断控制器
void pic_init()
{
    outb(PIC_M_CTRL, 0b00010001); // ICW1: 边沿触发, 级联 8259, 需要ICW4.
    outb(PIC_M_DATA, 0x20);       // ICW2: 起始端口号 0x20
    outb(PIC_M_DATA, 0b00000100); // ICW3: IR2接从片.
    outb(PIC_M_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI

    outb(PIC_S_CTRL, 0b00010001); // ICW1: 边沿触发, 级联 8259, 需要ICW4.
    outb(PIC_S_DATA, 0x28);       // ICW2: 起始端口号 0x28
    outb(PIC_S_DATA, 2);          // ICW3: 设置从片连接到主片的 IR2 引脚
    outb(PIC_S_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI

    outb(PIC_M_DATA, 0b11111110); // 关闭所有中断,打开时钟中断
    outb(PIC_S_DATA, 0b11111111); // 关闭所有中断
}

主要看最后两行,设置打开哪些中断,下面是支持的中断图,可以看到有支持15个中断,上面的outb(PIC_M_DATA, 0b11111110)表示打开时钟中断,当值为0,打开中断。

操作系统实现-外中断-LMLPHP

下面是实验的结果图,第一张图是没有设置send_eoi,可以看到只接收到了一次中断

操作系统实现-外中断-LMLPHP

下面是设置了send_eoi

操作系统实现-外中断-LMLPHP

我们下面来尝试下键盘中断,将outb(PIC_M_DATA, 0b11111110);设置为outb(PIC_M_DATA, 0b11111101);,开启程序,我们按下键盘,可以看到,当按下键盘触发,但是我们再次按下,却发现没有用,这是因为针对键盘中断,我们要编写对应的处理函数,比如处理收到的字符,然后编写对应的中断结束函数,才能再次接受中断

操作系统实现-外中断-LMLPHP

05-24 15:16