intel x86体系结构中断向量表中包含256个中断向量,编号为0-255。这256个中断可分为两大类:异常、中断。
异常
异常是CPU内部的中断,异常分为故障(fault)、陷阱(trap)、夭折(abort),它们的共同特点是不使用中断控制器、也不能被屏蔽。
中断
通常是由外部设备产生的,分为可屏蔽中断和非屏蔽中断。
其中非屏蔽中断和异常的向量号是固定的,而屏蔽中断的向量号可以通过对中断控制器的编程来实现。
如上图所示,外设通过中断线连接到中断控制器,中断控制器连接到CPU的中断引脚。外设如要中断CPU,需要通过中断线申请,如键盘可以通过IRQ1申请中断。因为IDT表的前一部分用于处理异常,故而这里IRQ申请的向量号从32开始;但是可以通过编程中断控制器来修改。
IDT
在实模式下,内存最开始的1K字节存储中断向量表。每个表项都有4个字节,前两个字节表示中断服务程序的段基址,后两个字节表示偏移量。
在保护模式下,中断向量表中的表项由8个字节组成,中断向量表也改称中断描述符表(Interrupt Descriptor Table);其中的每个表项称为一个门描述符。
同时,在保护模式下,中断描述符表也不需要在地址为0的地方开始,可以常驻于内存的任何地方。
为查询IDT的起始地址,在CPU中专门设置了一个IDTR——中断描述符表寄存器,如下:
神似GDTR,这是一个48位寄存器,高32位保存了IDT的基地址,低16位保存了中断描述符表的大小。
初始化
内核在启用中断机制之前,必须把IDT表的起始地址载入IDTR寄存器,并初始化表中的每一个表项。
IDT被初始化两次。第一次是在BIOS程序中,此时CPU还运行在实模式下,IDT被初始化并由bootloader程序使用。一旦Linux启动,IDT会被搬运到RAM的受保护区域并被第二次初始化。
IDT结构被存储在idt_table表中,包含256项。idt_descr变量存储IDT的大小和它的地址,在系统的初始化阶段,内核用来设置IDTR寄存器,专用汇编指令是lidt。
异常及中断处理
当CPU执行完当前指令后,需要判断是否发生了异常或者中断。如果确实发生,则:
1、确定所发生的异常或者中断在IDT表中的id(0-255)
2、通过IDTR寄存器加载IDT表的基地址,并读取对应id的表项
CPU通过相应表项的门描述符的段选择子,跳转到异常或者中断处理程序中执行。
但要注意下面两点:
1、权限检查:检查CPU的当前权限CPL与IDT表中相应表项的DPL,权限低的代码可以访问权限级别高的代码
2、检查是否发生了特权级的变化。若中断发生时CPU运行在用户空间 ,而中断处理程序运行在内核态,特权级发生了变化,会引起堆栈的更换,即从用户堆栈切换到内核堆栈。而当中断发生在内核态时,即CPU 在内核中运行时,则不会更换堆栈。