这一次我们来对中断中的外中断进行讲解,先给下中断的分类和中断号分配把。
前面对异常进行了讲解,这次对外中断进行说明。我们下面以时钟中断举例,首先我们要知道的是,时钟中断是操作系统自己发生的,比如我们在执行一段代码时候,可能不知道啥时候,就会触发时钟中断,我们为了体现触发了时钟中断,我们就打印一句话就行。
同样我们给每个外中断号定义一个入口函数
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,打开中断。
下面是实验的结果图,第一张图是没有设置send_eoi
,可以看到只接收到了一次中断
下面是设置了send_eoi
。
我们下面来尝试下键盘中断,将outb(PIC_M_DATA, 0b11111110);
设置为outb(PIC_M_DATA, 0b11111101);
,开启程序,我们按下键盘,可以看到,当按下键盘触发,但是我们再次按下,却发现没有用,这是因为针对键盘中断,我们要编写对应的处理函数,比如处理收到的字符,然后编写对应的中断结束函数,才能再次接受中断