Interrupt Descriptor Table

来自osdev
(重定向自IDT
跳到导航 跳到搜索

中断描述符表(IDT-Interrupt Descriptor Table)是特定于IA-32x86-64体系结构的二进制数据结构。 它是实模式里中断向量表(IVT)的保护模式和长模式对应物,用以告诉CPU中断服务例程 (ISR-Interrupt Service Routines) 所在的位置 (一个中断向量代表一个例程)。 它在结构上类似于全局描述符表-GDT

IDT条目被称为门(gate)。 它可以包含中断门、任务门和陷阱门。

在实现IDT之前,请确保你有一个有效的GDT。

IDTR

IDT的位置保存在IDTRIDT Register寄存器)中。 这是使用LIDT汇编指令加载的,该指令的参数为IDTR

IDT Descriptor (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 RegisterFigure 2-6: Memory Management Registers

IA-32上的结构

表 Table

在32位处理器上,IDT 中的条目长度为8个字节,并形成如下表:

Interrupt Descriptor Table (32-bit)
地址 内容
IDTR Offset + 0 Entry 0
IDTR Offset + 8 Entry 1
IDTR Offset + 16 Entry 2
... ...
IDTR Offset + 2040 Entry 255

给定中断向量的对应项在内存中被指向,方法是将向量缩放8,并将其添加到IDTROffset字段中的值。

门描述符(Gate Descriptor)

表中的每个条目都有一个复杂的结构:

Gate Descriptor (32-bit):
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 DescriptorsFigure 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字节长,形成如下表格:

Interrupt Descriptor Table (64-bit)
Address Content
IDTR Offset + 0 Entry 0
IDTR Offset + 16 Entry 1
IDTR Offset + 32 Entry 2
... ...
IDTR Offset + 4080 Entry 255

通过将给定的中断向量缩放16,并将其添加到IDTROffset字段中的值,可以在内存中指向该中断向量的相应条目。

门描述符(Gate Descriptor)

表中的每个条目都有一个复杂的结构:

Gate Descriptor (64-bit):
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 代表的门的类型。 在长模式下,有两个有效的类型值:
    • 0b11100xE:64位中断门
    • 0b11110xF: 64位陷阱门
  • DPL: 2位值,定义允许通过INT指令访问此中断的CPU特权级别。 硬件中断忽略了这种机制。
  • P: Present 位 必须设置 (1),描述符才有效。

在你的中断服务例程中,请记住使用IRETQ指令而不是IRET指令从中断返回,因为汇编器不会为你转换该指令。 论坛上许多与64位IDT相关的问题都是由缺少 'Q' 引起的。 别让这种事发生在你身上。

有关详细信息,请参阅《英特尔软件开发人员手册》第3-A卷的Section 6.14.1: 64-Bit Mode IDTFigure 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处理该中断时,它将执行到指定任务的硬件任务切换,而不是跳转到服务例程。 返回被中断的任务的指针将存储在TSSTask Link(任务链接)字段中。

"*注释* 因为IA-32任务不是可重入(re-entrant)的,所以中断处理程序任务必须在其完成处理中断时和执行IRET指令之间禁用中断。 此操作可防止在中断任务的TSS仍被标记为忙时发生另一个中断,这将导致常规保护 (#GP-general-protection) 异常。"

—英特尔软件开发人员手册

这种类型的门不经常使用,因为硬件任务切换很慢,并且在现代处理器上几乎没有优化。 同样,它在 x86-64上已被完全删除。

另见

文章

外部链接

de:Interrupt_Descriptor_Table