跳转到内容

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。操作系统可以将段不存在处理程序用于其他功能,例如监控中断调用。

IDT 寄存器

[编辑 | 编辑源代码]

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 寄存器中的指令(例如 popfiret)也可能修改此标志。

请注意,此标志不会影响 int 指令或处理器异常;只有硬件生成的硬件中断。 另请注意,在保护模式下,运行权限低于 IOPL 的代码如果使用 clisti,将生成一个异常。 这意味着操作系统可以禁止“用户”程序禁用中断,从而获得对系统的控制权。

当中断处理程序开始时,中断会自动被禁用; 这确保处理程序不会被中断(除非它发出 sti)。 设备驱动程序等软件可能需要精确的计时,因此不应被中断。 这也有助于避免在短时间内两次发生相同中断时出现的问题。 请注意,iret 指令在中断处理程序开始之前恢复 FLAGS 的状态,从而允许在中断处理程序完成后发生进一步的中断。

在执行某些系统任务(例如进入保护模式时)也应禁用中断。 这包括执行多个步骤,如果处理器尝试在该过程完成之前调用中断处理程序,则有可能导致异常、执行无效代码、破坏内存或导致其他问题。

华夏公益教科书