x86 汇编/高级中断
在关于中断的章节中,我们提到了软件中断的存在,并且它们可以由系统安装。本页将深入探讨这个过程,并讨论如何安装 ISR、系统如何找到 ISR 以及处理器实际上如何执行中断。
当发生中断时调用的实际代码称为中断服务例程 (ISR)。当发生异常时,程序调用中断,或者硬件引发中断,处理器使用几种方法之一(将在后面讨论)将控制权转移到 ISR,同时允许 ISR 在执行完成后安全地将控制权转移回它所中断的内容。至少, FLAGS 和 CS:IP 会被保存,ISR 的 CS:IP 会被加载;但是,一些机制会导致在 ISR 开始之前发生完整的任务切换(并在其结束时发生另一个任务切换)。
在原始的 8086 处理器(以及所有实模式下的 x86 处理器)中,中断向量表控制了 ISR 的流程。IVT 从内存地址 0x00 开始,可以高达 0x3FF,最多可以有 256 个 ISR(从中断 0 到 255)。IVT 中的每个条目包含 2 个字的数据:IP 的值和 CS 的值(按此顺序)。例如,假设我们有以下中断
int 14h
当我们触发中断时,处理器会转到 IVT 中的第 21 个位置 (14h = 20,索引从 0 开始)。由于每个表条目为 4 个字节(2 个字节 IP,2 个字节 CS),因此微处理器会转到位置 [4*14H]=[50H]。在位置 50H 是新的 IP 值,在位置 52H 是新的 CS 值。硬件和软件中断都存储在 IVT 中,因此安装新的 ISR 就像将函数指针写入 IVT 一样简单。在更新的 x86 模型中,IVT 被中断描述符表取代。
当在实模式下发生中断时,FLAGS 寄存器会被压入堆栈,然后是 CS,然后是 IP。iret 指令会恢复 CS:IP 和 FLAGS,使被中断的程序不受影响地继续执行。对于硬件中断,所有其他寄存器(包括通用寄存器)必须显式保存(例如,如果中断例程使用 AX,它应该在开始时压入 AX,并在结束时弹出 AX)。对于软件中断,最好保存所有寄存器,除了包含返回值的寄存器。更重要的是,任何被修改的寄存器都必须记录在案。
从 286 开始(但在 386 上扩展),中断可以由内存中名为中断描述符表 (IDT) 的表来管理。IDT 仅在处理器处于保护模式时才会起作用。与 IVT 非常相似,IDT 包含指向 ISR 例程的指针列表;但是,现在有三种方法可以调用 ISR
- 任务门:这些会导致任务切换,允许 ISR 在其自己的上下文中运行(使用它自己的 LDT 等)。请注意,IRET 仍然可以用于从 ISR 返回,因为处理器在 ISR 的任务段中设置了一个位,导致 IRET 执行任务切换以返回到先前的任务。
- 中断门:这些类似于原始的中断机制,将 EFLAGS、CS 和 EIP 放入堆栈。ISR 可以位于特权级别等于或高于当前执行段的段中,但不能位于特权级别较低的段中(特权级别数值较低,级别 0 是最高特权级别)。
- 陷阱门:这些与中断门相同,只是它们不会清除中断标志。
以下 NASM 结构表示 IDT 条目
struc idt_entry_struct base_low: resb 2 sel: resb 2 always0: resb 1 flags: resb 1 base_high: resb 2 endstruc
字段 | 中断门 | 陷阱门 | 任务门 |
---|---|---|---|
base_low | ISR 入口地址的低字 | 未用 | |
sel | ISR 的段选择器 | TSS 描述符 | |
always0 | 位 5、6 和 7 应为 0。位 0-4 未用,可以保留为零。 | 未用,可以保留为零。 | |
flags | 低 5 位是(MSB 优先):01110,位 5 和 6 形成 DPL,位 7 是 Present 位。 | 低 5 位是(MSB 优先):01111,位 5 和 6 形成 DPL,位 7 是 Present 位。 | 低 5 位是(MSB 优先):00101,位 5 和 6 形成 DPL,位 7 是 Present 位。 |
base_high | ISR 入口地址的高字 | 未用 |
where
- DPL 是描述符特权级别(0 到 3,其中 0 是最高特权级别)
- Present 位指示该段是否在 RAM 中存在。如果此位为 0,则如果触发中断,将发生段不存在错误(异常 11)。
这些 ISR 通常由操作系统安装和管理。只有具有足够特权修改 IDT 内容的任务才能直接安装 ISR。
ISR 本身必须放置在适当的段中(如果使用任务门,则必须设置适当的 TSS),特别是在特权级别永远不低于执行代码的特权级别的情况下。不可预测中断(例如硬件中断)的 ISR 应放置在特权级别 0(这是最高特权级别)中,这样在运行特权级别 0 任务时就不会违反此规则。
请注意,ISR,特别是硬件触发的 ISR,应始终存在于内存中,除非有充分的理由不将其存在于内存中。大多数硬件中断需要及时处理,而交换会导致明显的延迟。此外,一些硬件 ISR(例如硬盘 ISR)可能在交换过程中需要。由于硬件触发的 ISR 在不可预测的时间中断进程,因此鼓励设备驱动程序程序员将 ISR 保持得非常短。ISR 通常只是安排内核任务完成必要的工作;此内核任务将在下一个合适的机会运行。因此,硬件触发的 ISR 通常非常小,交换它们到磁盘几乎没有好处。
但是,即使 ISR 实际上存在于 RAM 中,也可能希望将 Present 位设置为 0。操作系统可以将段不存在处理程序用于其他功能,例如监控中断调用。
x86 包含一个寄存器,其作用是跟踪 IDT。此寄存器称为IDT 寄存器,或简称为“IDTR”。IDT 寄存器长 48 位。低 16 位称为 IDTR 的 LIMIT 部分,高 32 位称为 IDTR 的 BASE 部分
|LIMIT|----BASE----|
BASE 是 IDT 在内存中的基地址。IDT 可以位于内存中的任何位置,因此 BASE 需要指向它。LIMIT 字段包含 IDT 的当前长度。
要加载 IDTR,使用LIDT指令
lidt [idtr]
要存储 IDTR,使用SIDT指令
sub esp,6 sidt [esp] ;store the idtr to the stack
中断指令
[edit | edit source]int arg
调用指定的硬件中断。
into 0x04
如果溢出标志被置位,则调用中断 4。
iret
从中断服务例程 (ISR) 返回。
默认 ISR
[edit | edit source]良好的编程实践是提供一个默认的 ISR,它可以作为未使用的中断的占位符。 这样做是为了防止在发生无法识别的中断时执行随机代码。 默认 ISR 可以像一个简单的 iret 指令一样。
但是,请注意,在 DOS(处于实模式)下,某些 IVT 条目包含指向重要位置(但不一定是可执行位置)的指针。 例如,条目 0x1D 是指向视频控制器视频初始化参数表的远指针,条目 0x1F 是指向图形字符位图表的指针。
禁用中断
[edit | edit source]有时,重要的是一个例程不会意外中断。 因此,x86 允许在必要时禁用硬件中断。 这意味着处理器将忽略它从中断控制器接收到的任何中断信号。 通常,控制器将一直等待,直到处理器接受中断信号,因此中断被延迟而不是被拒绝。
x86 在 FLAGS 寄存器中有一个中断标志 (IF)。 当此标志设置为 0 时,硬件中断被禁用,否则它们被启用。 命令 cli 将此标志设置为 0,而 sti 将其设置为 1。 将值加载到 FLAGS 寄存器中的指令(例如 popf 和 iret)也可能修改此标志。
请注意,此标志不会影响 int 指令或处理器异常;只有硬件生成的硬件中断。 另请注意,在保护模式下,运行权限低于 IOPL 的代码如果使用 cli 或 sti,将生成一个异常。 这意味着操作系统可以禁止“用户”程序禁用中断,从而获得对系统的控制权。
当中断处理程序开始时,中断会自动被禁用; 这确保处理程序不会被中断(除非它发出 sti)。 设备驱动程序等软件可能需要精确的计时,因此不应被中断。 这也有助于避免在短时间内两次发生相同中断时出现的问题。 请注意,iret 指令在中断处理程序开始之前恢复 FLAGS 的状态,从而允许在中断处理程序完成后发生进一步的中断。
在执行某些系统任务(例如进入保护模式时)也应禁用中断。 这包括执行多个步骤,如果处理器尝试在该过程完成之前调用中断处理程序,则有可能导致异常、执行无效代码、破坏内存或导致其他问题。