Interrupt Descriptor Table
中断描述符表(IDT-Interrupt Descriptor Table)是特定于IA-32和x86-64体系结构的二进制数据结构。 它是实模式里中断向量表(IVT)的保护模式和长模式对应物,用以告诉CPU中断服务例程 (ISR-Interrupt Service Routines) 所在的位置 (一个中断向量代表一个例程)。 它在结构上类似于全局描述符表-GDT。
IDT条目被称为门(gate)。 它可以包含中断门、任务门和陷阱门。
在实现IDT之前,请确保你有一个有效的GDT。
IDTR
IDT的位置保存在IDTR(IDT Register寄存器)中。 这是使用LIDT汇编指令加载的,该指令的参数为IDTR:
79 (64-bit Mode) 48 (32-bit Mode) 16 |
15 0 |
---|---|
Offset 63 (64-bit Mode) 31 (32-bit Mode) 0 |
Size 15 0 |
- Size: 比“IDT”的大小(字节)小一。
- Offset: Interrupt Descriptor Table(中断描述符表)的线性地址(不是物理地址,应用分页)。
请注意,LIDT加载的数据量在32位和64位模式下有所不同,Offset在32位模式下为4字节长,在64位模式下为8字节长。
这与GDT类似,只是:
- 第一个条目(偏移量为零)可用于IDT。(译者注:GDT的第一个条目为空无用条目)
- 有256个中断向量 (0..255),因此 IDT应该具有256个条目,每个条目对应于特定的中断向量。
- 虽然IDT可以包含256个以上的条目,但它们会被忽略。
- 虽然IDT可以包含少于256个条目,但任何不存在的条目(由于此或其他原因)将在尝试访问它们时生成一个一般保护异常(General Protection Fault)。 理想情况下,IDT 应该包含足够的条目,以便可以处理此故障 (它本身是一个中断向量)。
有关更多信息,请参阅《英特尔软件开发人员手册》第3-A卷的Section 2.4.3: IDTR Interrupt Descriptor Table Register和Figure 2-6: Memory Management Registers。
IA-32上的结构
表 Table
在32位处理器上,IDT 中的条目长度为8个字节,并形成如下表:
地址 | 内容 |
---|---|
IDTR Offset + 0 | Entry 0 |
IDTR Offset + 8 | Entry 1 |
IDTR Offset + 16 | Entry 2 |
... | ... |
IDTR Offset + 2040 | Entry 255 |
给定中断向量的对应项在内存中被指向,方法是将向量缩放8,并将其添加到IDTR的Offset字段中的值。
门描述符(Gate Descriptor)
表中的每个条目都有一个复杂的结构:
63 48 | 47 | 46 45 | 44 | 43 40 | 39 32 |
---|---|---|---|---|---|
Offset 31 16 |
P | DPL 1 0 |
0 | Gate Type 3 0 |
Reserved |
31 16 | 15 0 | ||||
Segment Selector 15 0 |
Offset 15 0 |
- Offset:一个32位的值,分为两部分。它表示中断服务例程入口点的地址。
- Selector: 段选择器具有多个字段,这些字段必须指向GDT中的有效代码段。
- Gate Type: 一个4位值,它定义了此中断描述符的门类型。有五个有效的类型值:
- 0b0101 or 0x5: 任务门(Task Gate),请注意,在这种情况下,Offset 值未使用,应设置为零。
- 0b0110 or 0x6: 16位中断门
- 0b0111 or 0x7: 16位陷阱门
- 0b1110 or 0xE: 32位中断门
- 0b1111 or 0xF: 32位陷阱门
- DPL: 一个2位值,用于定义CPU特权级别,允许通过INT指令访问此中断。 硬件中断会忽略此机制。
- P: Present位。必须设置(1)才能使描述符有效。
有关更多信息,请参阅《英特尔软件开发人员手册》 (第3-A卷) 的 Section 6.11: IDT Descriptors 和 Figure 6-2: IDT Gate Descriptors。
示例代码
C结构体:
struct InterruptDescriptor32 {
uint16_t offset_1; // offset bits 0..15
uint16_t selector; // a code segment selector in GDT or LDT
uint8_t zero; // unused, set to 0
uint8_t type_attributes; // gate type, dpl, and p fields
uint16_t offset_2; // offset bits 16..31
};
人们可能会使用的示例 type_attributes 值(假设DPL为0):
- 32-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
- 32-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)
- Task Gate: 0x85 (p=1, dpl=0b00, type=0b0101 => type_attributes=0b1000_0101=0x85)
x86-64上的结构
表 Table
在64位处理器上,IDT中的条目为16字节长,形成如下表格:
Address | Content |
---|---|
IDTR Offset + 0 | Entry 0 |
IDTR Offset + 16 | Entry 1 |
IDTR Offset + 32 | Entry 2 |
... | ... |
IDTR Offset + 4080 | Entry 255 |
通过将给定的中断向量缩放16,并将其添加到IDTR的Offset字段中的值,可以在内存中指向该中断向量的相应条目。
门描述符(Gate Descriptor)
表中的每个条目都有一个复杂的结构:
127 96 | ||||||
---|---|---|---|---|---|---|
Reserved | ||||||
95 64 | ||||||
Offset 63 32 | ||||||
63 48 | 47 | 46 45 | 44 | 43 40 | 39 35 | 34 32 |
Offset 31 16 |
P | DPL 1 0 |
0 | Gate Type 3 0 |
Reserved | IST 2 0 |
31 16 | 15 0 | |||||
Segment Selector 15 0 |
Offset 15 0 |
- Offset:一个64位的值,分为三部分。 它表示中断服务例程入口点的地址。
- Selector: 一个 段选择器,具有多个字段,这些字段必须指向你的 GDT 中的有效代码段。
- IST: 一个3位值,它是Interrupt Stack Table的偏移量,存储在Task State Segment中。 如果所有位都设置为零,则不使用Interrupt Stack Table。
- Gate Type: 一个4位值,它定义了此 Interrupt Descriptor 代表的门的类型。 在长模式下,有两个有效的类型值:
- 0b1110或 0xE:64位中断门
- 0b1111 或 0xF: 64位陷阱门
- DPL: 2位值,定义允许通过INT指令访问此中断的CPU特权级别。 硬件中断忽略了这种机制。
- P: Present 位 必须设置 (1),描述符才有效。
在你的中断服务例程中,请记住使用IRETQ指令而不是IRET指令从中断返回,因为汇编器不会为你转换该指令。 论坛上许多与64位IDT相关的问题都是由缺少 'Q' 引起的。 别让这种事发生在你身上。
有关详细信息,请参阅《英特尔软件开发人员手册》第3-A卷的Section 6.14.1: 64-Bit Mode IDT和Figure 6-8: 64-Bit IDT Gate Descriptors。
示例代码
C结构体:
struct InterruptDescriptor64 {
uint16_t offset_1; // offset bits 0..15
uint16_t selector; // a code segment selector in GDT or LDT
uint8_t ist; // bits 0..2 holds Interrupt Stack Table offset, rest of bits zero.
uint8_t type_attributes; // gate type, dpl, and p fields
uint16_t offset_2; // offset bits 16..31
uint32_t offset_3; // offset bits 32..63
uint32_t zero; // reserved
};
人们可能会使用的示例 type_tributes值(假设DPL为0):
- 64-bit Interrupt Gate: 0x8E (p=1, dpl=0b00, type=0b1110 => type_attributes=0b1000_1110=0x8E)
- 64-bit Trap Gate: 0x8F (p=1, dpl=0b00, type=0b1111 => type_attributes=1000_1111b=0x8F)
门类型
基本上有两种中断: 当代码执行由于错误代码而遇到Exception时发生的事件,或者处理与当前正在执行的代码无关的事件时发生的事件。 在第一种情况下,有必要保存当前执行指令的地址,以便可以重试,这些称为 “陷阱(Traps)”。 在第二种情况下,保存下一条指令的地址是必要的,这样就可以在中断的地方继续执行。 这可能是由IRQ或其他硬件事件或使用INT指令引起的。 需要注意的另一个区别是,使用Traps时,在服务例程期间可能会发生新的中断, 但当CPU为IRQ提供服务时,进一步的中断会被屏蔽,直到发出“中断结束(End of Interrupt)”信号。 如何处理某个中断取决于你在IDT条目中放置的门的类型。
中断门(Interrupt Gate)
中断门用于指定一个中断服务例程。 例如,当在保护模式下运行时执行汇编指令INT 50时,CPU将查找IDT中的第50个条目(位于50*8)。 然后加载中断门的 选择器(Selector); 和 偏移(Offset)值。 选择器(Selector)和偏移量(Offset)用于调用中断服务例程。 当执行IRET指令时,CPU从中断返回。 如果CPU以32位模式运行,并且指定的选择器是16位门,则在调用ISR后,CPU将进入16位受保护模式。 在这种情况下,要返回,应该使用O32 IRET指令,否则CPU将不知道它应该执行32位返回(从堆栈读取32位值,而不是16位)。
陷阱门(Trap Gate)
陷阱门应该用来处理异常。 当一个异常发生时,有时会在堆栈上放置一个错误代码,应该在中断返回之前弹出该代码。
陷阱门和中断门是相似的,它们的描述符在结构上是相同的,只是在门类型字段中有所不同。 不同之处在于,对于 中断门,中断在进入时会自动禁用,并在 IRET 时重新启用,而对于 陷阱门 则不会发生。
任务门(Task Gate)
任务门是特定于IA-32的门类型,用于硬件任务切换。 对于任务门,选择器值应指向GDT中的位置,该位置指定 任务状态段,而不是代码段, 偏移量值未使用,应设置为零。 当CPU处理该中断时,它将执行到指定任务的硬件任务切换,而不是跳转到服务例程。 返回被中断的任务的指针将存储在TSS 的Task Link(任务链接)字段中。
"*注释* 因为IA-32任务不是可重入(re-entrant)的,所以中断处理程序任务必须在其完成处理中断时和执行IRET指令之间禁用中断。 此操作可防止在中断任务的TSS仍被标记为忙时发生另一个中断,这将导致常规保护 (#GP-general-protection) 异常。"
—英特尔软件开发人员手册 |
这种类型的门不经常使用,因为硬件任务切换很慢,并且在现代处理器上几乎没有优化。 同样,它在 x86-64上已被完全删除。