中断类型
保护模式下中断分为三种
- 错误Fault: ⼀种可被更正的异常, ⼀旦被更正, 程序可以不失连续性地继续执⾏, 中断程序返回地址为产⽣ Fault 的指令
- 陷阱Trap: 发⽣ Trap 的指令执⾏之后⽴刻被报告的异常, 也允许程序不失连续性地继续执⾏, 但中断程序返回地址是产⽣ Trap 之后的那条指令
- 终止Abort: Abort 异常不总是精确报告发⽣异常的位置, 它不允许程序继续执⾏, ⽽是⽤来报告严重错误
IDT
IDT(Interuption Descriptor Table) ,类似于中断向量表,但比中断向量表有更丰富的信息。
在保护模式下,每个中断(Exception,Interrupt,Software Interrupt)都由⼀个 8-Bits 的向量来标识,其为中断向量,8-Bits表示⼀共有256个中断向量;与 256 个中断向量对应,IDT 中存有 256 个
表项,表项称为⻔描述符(Gate Descriptor),每个描述符占 8 个字节。
⻔描述符可以分为3种
- Task Gate,Intel设计⽤于任务切换,现代操作系统中⼀般不使⽤
- Interrupt Gate,跳转执⾏该中断对应的处理程序时, EFLAGS 中的 IF 位会被硬件置为 0,即关中断,以避免嵌套中断的发⽣
- Trap Gate,跳转执⾏该中断对应的处理程序时, EFLAGS 中的 IF 位不会置为 0,也就是说,不关中断
下图展示了 Interrupt Gate 8个字节表示的信息
其中SELECTOR是段描述符(16位),OFFSET是地址偏移量(要加载的EIP的),DPL表示权限。下图展示了Trap Gate 8个字节表示的信息
二者的区别就是第39位的值。
机制
中断到来之后,基于中断向量,IA-32硬件利⽤IDT与GDT这两张表寻找到对应的中断处理程序,并从当前程序跳转执⾏。
若中断源为int 等指令产⽣的软中断,IA-32硬件处理该中断时还会⽐较产⽣该中断的程序的CPL与该中断对应的⻔描述符的DPL字段,若CPL数值上⼤于DPL,则会产⽣General Protect Fault,即#GP异常
实现
这里用结构体来表示门描述符
struct GateDescriptor {
uint32_t offset_15_0 : 16;
uint32_t segment : 16;
uint32_t pad0 : 8;
uint32_t type : 4;
uint32_t system : 1;
uint32_t privilege_level : 2;
uint32_t present : 1;
uint32_t offset_31_16 : 16;
};
然后表示一个门描述符需要具体给出三个值:SELECTOR,OFFSET和DPL:
#define SEG_KCODE 1 // Kernel code
#define INTERRUPT_GATE_32 0xE
#define TRAP_GATE_32 0xF
/* 初始化一个中断门(interrupt gate) */
static void setIntr(struct GateDescriptor *ptr, uint32_t selector, uint32_t offset, uint32_t dpl) {
ptr->offset_15_0 = offset & 0xFFFF;
ptr->segment = selector << 3;
ptr->pad0 = 0;
ptr->type = INTERRUPT_GATE_32;
ptr->system = FALSE;
ptr->privilege_level = dpl;
ptr->present = TRUE;
ptr->offset_31_16 = (offset >> 16) & 0xFFFF;
}
/* 初始化一个陷阱门(trap gate) */
static void setTrap(struct GateDescriptor *ptr, uint32_t selector, uint32_t offset, uint32_t dpl) {
ptr->offset_15_0 = offset & 0xFFFF;
ptr->segment = selector << 3;
ptr->pad0 = 0;
ptr->type = TRAP_GATE_32;
ptr->system = FALSE;
ptr->privilege_level = dpl;
ptr->present = TRUE;
ptr->offset_31_16 = (offset >> 16) & 0xFFFF;
}
这样我们就可以定义具体的IDT了,比如定义中断向量为0x21的键盘中断和中断向量为0x80的软件中断。
#define NR_IRQ 256
/* IDT表的内容 */
struct GateDescriptor idt[NR_IRQ];
void irqEmpty();
void irqSyscall();
void irqKeyboard();
void initIdt() {
int i;
/* 为了防止系统异常终止,所有irq都有处理函数(irqEmpty)。 */
for (i = 0; i < NR_IRQ; i ++) {
setTrap(idt + i, SEG_KCODE, (uint32_t)irqEmpty, DPL_KERN);
}
/*
* init your idt here
* 初始化 IDT 表, 为中断设置中断处理函数
*/
/* Exceptions with error code */
// ...
setIntr(idt + 0x21, SEG_KCODE, (uint32_t)irqKeyboard, DPL_KERN);
setIntr(idt + 0x80, SEG_KCODE, (uint32_t)irqSyscall, DPL_USER); // for int 0x80, interrupt vector is 0x80, Interruption is disabled
/* 写入IDT */
saveIdt(idt, sizeof(idt));
}