IDT(中断描述表)和中断

优质
小牛编辑
116浏览
2023-12-01

中断(Interrupt)是指处理器接收到来自硬件或软件的信号,提示发生了某个事件,应该被注意,这种情况就称为中断。

中断分为3种:

  • 硬件中断: 由外部设备发送给处理器,比如 键盘,鼠标,硬盘等。硬件中断是一种在轮询循环,等待外部事件方面避免浪费处理器的宝贵时间的方式。
  • 软件中断: 是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。
  • 异常: 程序运行期间不能自身处理的错误和事件,比如页中断,被零除等。

键盘示例

当用户按下键盘上的一个键时,键盘控制器将产生中断信号至中断控制器。如果中断不被屏蔽,控制器将中断发送给处理器,处理器将执行一个中断 服务子程序来处理中断(键按压或释放键),这个中断处理可能是获取按键并输出到屏幕。一旦字符处理程序完成时,被中断的作业就可以恢复。

什么是PIC?

PIC (Programmable interrupt controller,可编程中断控制器 ) 可编程中断控制器是微处理器与外设之间的中断处理的桥梁。当有多个中断输出发生,将根据它们的优先级响应。

最为典型的PIC是Intel的 8259A,一个8259A芯片的可以接最多8个中断源,但由于可以将2个或多个8259A芯片级连(cascade),并且最多可以级连到9个,所以最多可以接64个中断源。如今绝大多数的PC都拥有两个8259A,这样 最多可以接收15个中断源(原文中只说明可以管理14个设备的中断,这个说法应该是有问题的,没有考虑到级连)。

考虑到有理解冲突,原文附带,读者可参考理解:

The best known PIC is the 8259A, each 8259A can handle 8 devices but most computers have two controllers: one master and one slave, this allows the computer to manage interrupts from 14 devices.

在本章中,我们将需要编程来初始化该控制器和屏蔽中断。

IDT是什么?

中断描述表 是一个X86架构下实现中断向量表的数据结构。IDT被处理器用于对中断及异常作出应答决策。

我们的内核将使用IDT来定义中断发生时调用的不同程序。

GDT 类似,IDT 使用 LIDTL 汇编指令来加载。一个IDT的位置结构如下描述:

struct idtr {
    u16 limite;
    u32 base;
} __attribute__ ((packed));

IDT表由如下结构的IDT段所构成:

struct idtdesc {
    u16 offset0_15;
    u16 select;
    u16 type;
    u16 offset16_31;
} __attribute__ ((packed));

注意: __attribute__ ((packed)) 意义请参考 GDT 中的描述.

现在需要定义我们的IDT,并使用LIDTL来载入它。IDT表可以存储在内存中的任何位置,CPU就根据IDTR寄存器中的内容作为IDT的入口来访问IDT。

以下是一个通用的中断表(Maskable hardware interrupt,IRQ,可屏蔽硬件信号):

IRQDescription
0Programmable Interrupt Timer Interrupt
1Keyboard Interrupt
2Cascade (used internally by the two PICs. never raised)
3COM2 (if enabled)
4COM1 (if enabled)
5LPT2 (if enabled)
6Floppy Disk
7LPT1
8CMOS real-time clock (if enabled)
9Free for peripherals / legacy SCSI / NIC
10Free for peripherals / SCSI / NIC
11Free for peripherals / SCSI / NIC
12PS2 Mouse
13FPU / Coprocessor / Inter-processor
14Primary ATA Hard Disk
15Secondary ATA Hard Disk

如何初始化中断?

以下是初始化IDT段的简单的样例:

void init_idt_desc(u16 select, u32 offset, u16 type, struct idtdesc *desc)
{
    desc->offset0_15 = (offset & 0xffff);
    desc->select = select;
    desc->type = type;
    desc->offset16_31 = (offset & 0xffff0000) >> 16;
    return;
}

以下是初始化中断的方法:

#define IDTBASE    0x00000000
#define IDTSIZE 0xFF
idtr kidtr;
void init_idt(void)
{
    /* Init irq */
    int i;
    for (i = 0; i < IDTSIZE; i++)
        init_idt_desc(0x08, (u32)_asm_schedule, INTGATE, &kidt[i]); //

    /* Vectors  0 -> 31 are for exceptions */
    init_idt_desc(0x08, (u32) _asm_exc_GP, INTGATE, &kidt[13]);        /* #GP */
    init_idt_desc(0x08, (u32) _asm_exc_PF, INTGATE, &kidt[14]);     /* #PF */

    init_idt_desc(0x08, (u32) _asm_schedule, INTGATE, &kidt[32]);
    init_idt_desc(0x08, (u32) _asm_int_1, INTGATE, &kidt[33]);

    init_idt_desc(0x08, (u32) _asm_syscalls, TRAPGATE, &kidt[48]);
    init_idt_desc(0x08, (u32) _asm_syscalls, TRAPGATE, &kidt[128]); //48

    kidtr.limite = IDTSIZE * 8;
    kidtr.base = IDTBASE;


    /* Copy the IDT to the memory */
    memcpy((char *) kidtr.base, (char *) kidt, kidtr.limite);

    /* Load the IDTR registry */
    asm("lidtl (kidtr)");
}

当IDT被初始化后,我们需要通过配置 PIC 来激活中断, 以下的函数通过处理器的输出口 io.outb 来写入对应的寄存器,从而配置2个PIC。

使用以下两个端口来配置:

  • Master PIC: 0x20 and 0x21
  • Slave PIC: 0xA0 and 0xA1

PIC有两种寄存器:

  • 初始化命令字(ICW): 重设(初始化)控制器
  • 操作命令字(OCW): 配置已被初始化控制器(屏蔽或取消中断屏蔽)
void init_pic(void)
{
    /* Initialization of ICW1 */
    io.outb(0x20, 0x11);
    io.outb(0xA0, 0x11);

    /* Initialization of ICW2 */
    io.outb(0x21, 0x20);    /* start vector = 32 */
    io.outb(0xA1, 0x70);    /* start vector = 96 */

    /* Initialization of ICW3 */
    io.outb(0x21, 0x04);
    io.outb(0xA1, 0x02);

    /* Initialization of ICW4 */
    io.outb(0x21, 0x01);
    io.outb(0xA1, 0x01);

    /* mask interrupts */
    io.outb(0x21, 0x0);
    io.outb(0xA1, 0x0);
}

详述 PIC ICW

寄存器应按序配置:

ICW1 (port 0x20 / port 0xA0)

|0|0|0|1|x|0|x|x|
         |   | +--- with ICW4 (1) or without (0)
         |   +----- one controller (1), or cascade (0)
         +--------- triggering by level (level) (1) or by edge (edge) (0)

ICW2 (port 0x21 / port 0xA1)

|x|x|x|x|x|0|0|0|
 | | | | |
 +----------------- base address for interrupts vectors

ICW2 (port 0x21 / port 0xA1)

主控制器:

|x|x|x|x|x|x|x|x|
 | | | | | | | |
 +------------------ slave controller connected to the port yes (1), or no (0)

从控制器:

|0|0|0|0|0|x|x|x|  pour l'esclave
           | | |
           +-------- Slave ID which is equal to the master port

ICW4 (port 0x21 / port 0xA1)

用来定义控制器运模式。

|0|0|0|x|x|x|x|1|
       | | | +------ mode "automatic end of interrupt" AEOI (1)
       | | +-------- mode buffered slave (0) or master (1)
       | +---------- mode buffered (1)
       +------------ mode "fully nested" (1)

Why do idt segments offset our ASM functions?

You should have noticed that when I'm initializing our IDT segments, I'm using offsets to segment the code in Assembly. The different functions are defined in x86int.asm and are of the following scheme:

%macro    SAVE_REGS 0
    pushad
    push ds
    push es
    push fs
    push gs
    push ebx
    mov bx,0x10
    mov ds,bx
    pop ebx
%endmacro

%macro    RESTORE_REGS 0
    pop gs
    pop fs
    pop es
    pop ds
    popad
%endmacro

%macro    INTERRUPT 1
global _asm_int_%1
_asm_int_%1:
    SAVE_REGS
    push %1
    call isr_default_int
    pop eax    ;;a enlever sinon
    mov al,0x20
    out 0x20,al
    RESTORE_REGS
    iret
%endmacro

These macros will be used to define the interrupt segment that will prevent corruption of the different registries, it will be very useful for multitasking.

译者注: