【译】中断

905 阅读11分钟

本文由 marsCatXDU 翻译,如有谬误,还请大神们多多指教

原文地址:wiki.osdev.org/Interrupts

翻译的原文版本:28 August 2020, at 09:44.

中断

中断,是 CPU 接收到的来自设备(如键盘、硬盘等)的信号。该信号能要求 CPU 立即停止正在运行的指令并开始进行其他的任务。
比如,当按下键盘上的“a”键时,键盘控制器就会向 CPU 发送一个中断,OS 就会立即在屏幕上把这个“a”显示出来——即使 CPU 刚刚正在做的事情和显示字符毫无关系。字符显示完成后,CPU 便会重新回到之前的工作上继续运行。
当产生一个特定的中断时,CPU 会在操作系统提供的一个表1 中查找该中断对应的程序入口点,然后跳转到该入口点所指向的代码2 处运行。

1:该表在 x86 保护模式中被称为 IDT(Interrupt Descriptor Table),最多可以有 256 个入口点。该表的名字和最大入口点数取决于具体的 CPU 架构。
2:该代码被称为中断处理程序(ISR, Interrupt Service Routine 或 Interrupt Handler

中断的类型

在多数平台上,中断可以大致分为以下三类:

  • 异常:由 CPU 产生,并用于告知系统内核刚刚发生了某个事件或达成了某种状态,需要内核的注意和处理。在 x86 CPU 中,Double Fault3 、Page Fault(缺页故障)、General Protection Fault (一般保护故障,试图访问不可访问地址)等

    3:“Double Fault”,指明在调用前一个异常处理程序期间,又出现了一个异常。一般而言,两个异常是顺序处理的。
    然而,有一些异常无法顺序处理,在这种情况下处理器就会发出一个“double fault”信号。
    原因一般是内核栈溢出或硬件问题

  • 中断请求(Interrupt Request,IRQ)或硬件中断(Hardware Interrupt):该类中断由芯片组在(chipset)外部产生,向 INTR 引脚发出锁存信号引起的中断(或者其他等效的信号)。当代主要有以下两种常用的 IRQ:

    • IRQ线路 或 基于引脚的IRQ:这些 IRQ 基本是静态存在于芯片组内部的。在连接着芯片组上的设备和 IRQ 控制器之间的线路上串行地将来自设备的中断请求发往 CPU (通过逐个串行发送来避免竞争)。在很多情况下,一个 IRQ 控制器会根据设备的优先级次序来一次向 CPU 发送多个 IRQ。例如运行在所有 IBM-PC 兼容机的芯片组中广为人知的 Intel 8259控制器链,将两个控制器连接在一起,每个控制器提供 8 个输入引脚,共提供 16 个可用的 IRQ 信号引脚。
    • 基于消息的中断:通过向存储位置中写入值来发出中断信号,该存储位置保留有关中断设备、中断本身和向量信息的数据。
      中断设备被指定一个【可被固件及内核写入数据】的地址。然后 IRQ 就会被设备使用一个仲裁协议来指定设备总线。PCI 总线就是一个提供基于消息实现中断功能的实例
  • 软件中断(软中断):该中断的信号由正运行在 CPU 上的软件发出,以告诉 CPU 该程序需要引起内核的注意。这些类型的中断一般被用于系统调用。在 x86 CPU 中,用于启动软件中断的指令为 INT。又因为 x86 CPU 可以使用 256 个中断向量值中的任何一个作为软件中断,内核基本上都会在这些值中选一个作为软中断。例如,很多当代的 UNIX 系统都会在基于 x86 的平台上选择 0x80 向量

开发者可以自行决定如何设置向量,只是需要避免在同一个向量上设置多个不同类型的中断。在实际应用中通常都是按照 Intel 的要求,将前 32 个向量留给异常,其他的可以由开发者自由安排。

从键盘的角度看中断

一般来说,当我们按下键盘上的按钮时,键盘的控制器会通知一个叫做 PIC 的设备产生一个中断(PIC,Programmable Interrupt Controller),又因为 IRQ #1 就是键盘中断,所以按钮被按下时,IRQ 1 就会被送到 PIC。接下来 PIC 就要决定是否要立即给 CPU 一个 IRQ,还是将 IRQ 号转换为一个对应着 CPU 中断表中的中断向量(一个 0 到 255 的整数)

操作系统的任务应该是:通过使用 IO 指令(或 C 中的 inportb/outportb, inportw/outportw, inportd/outportd )与键盘交互,以处理中断——确定被按下的是哪个按键,并做出反应(比如把按键显示在屏幕上,通知正在运行的应用有一个按钮刚被按下等),然后回到中断到来前的代码处继续执行。如果从缓冲区读按键失败,会导致键盘后续发来的 IRQ 全部无法收到

从 PIC 的角度看中断

在绝大多数系统中都有两个 PIC,每个都有 8 个不同的输入和一个对 CPU 发出 IRQ 信号的输出。
从 PIC (slave PIC)的输出信号连接到主 PIC (master PIC)的第三个输入上(input #2)。如此一来,当从 PIC 想要通知 CPU 产生中断时,实际上是通知了主 PIC,然后主 PIC 再通知 CPU——这被称为“级联”(cascade)。
当主 PIC 的第三个输入被配置为级联模式而非正常 IRQ 模式时,就意味着无法再产生正常的 IRQ 2(因为用于级联了)

一个设备向 PIC 芯片发送中断,PIC 再直接或间接地告知 CPU 产生中断。当 CPU 接收到中断信号时,PIC 芯片就会将中断号发送到 CPU(00h 到 FFh 的一个整数)。系统第一次启动时,07 号 IRQ 被设置为中断 08h0Fh,815 号 IRQ 被设置为中断 70h77h。因此,对于 6 号 IRQ,PIC 会要求 CPU 进行 INT 0Eh 中断服务—— 该服务很可能有着【用于和任何连接到主 PIC 芯片的 "input #6" 的设备进行交互】的代码。当然,当两个或更多设备共享同一个 IRQ 时会有一些麻烦,如果对此感兴趣可以看看 即插即用(Plug and Play

需要注意,中断也是分优先级的,顺序为 0,1,2,8,9,10,11,12,13,14,15,3,4,5,6,7。因此如果 IRQ 8 和 IRQ 3 同时到来,被发往 CPU 的将是 IRQ 8。当CPU 处理完中断,CPU 会像下面这样告知 PIC 可以继续发送中断:

mov al, 20h
out 20h, al

如果中断来自从 PIC:

mov al, 20h
out A0h, al
out 20h, al

这样 PIC 就会发送分配给 IRQ 3 的中断,CPU 便会开始处理 IRQ 3(使用 IDT 来查找该中断对应的处理程序)

脑子比较敏感的读者可能会注意到,CPU 保留了 0-31 号中断,而 IRQ 0-7 被设置为了 08-0Fh 中断。当发生了必须由操作系统处理的错误时,会调用保留的中断。

当电脑第一次被启动时,绝大多数这类错误都不会产生。然而当进入保护模式时(所有操作系统都应该使用保护模式,实模式已经过时了),这些错误都有可能在任何时候出现,并需要操作系统的处理。那么操作系统是怎么区分 INT 9 (协处理器段溢出)和 INT9:IRQ1 的呢?操作系统可以去问一下设备,它是不是真的有个中断需要处理——但这样又慢又蠢,而且不是所有的设备都能够回复这样的消息。最好的办法是告诉 PIC 将 IRQ 映射到不同的中断上去。比如 INT 78h-7Fh(更多信息,见 PIC 中的 FAQ)。需要注意,IRQ 只能够被映射到 08h 的倍数的 INT 上,如 00h-07h, 08h-0Fh, 10h-17h, 17h-1Fh。而且开发者很可能会使用 20h-27h 或以上,因为 00h-1Fh 都是 CPU 自己保留的。同样,每个 PIC 都应该被分开单独编程。可以让主 PIC 将 0-7 IRQ 映射到 INT 20h-17h,但 IRQ 8-F 仍保留在 70h-77h,除非要求从 PIC 把这些 IRQ 改放到其他的地方

更详细的内容,见 PIC芯片的编程

从 CPU 的角度看中断

每当 CPU 完成一条机器指令时,都会检查一下 PIC 针脚上是否有中断。如果确实有,CPU 就会将一些状态信息入栈存储(这样就能够在处理完操作系统的中断服务程序后返回该处继续执行之前的任务),然后跳转到 IDT 所指向的位置,交给操作系统从这里开始执行。正在运行的程序也可以通过设置中断标志(Interrupt flag,即状态寄存器中的 IF )阻止 CPU 响应中断。一旦该标志置零,CPU 就会忽略掉 PIC 的请求而继续执行当前的程序。汇编中的 clisti 置零可用于控制该标志。

从操作系统的角度看中断

当中断到来时,操作系统就会根据 IDT 跳转到中断对应的处理程序的代码处。通常,这部分代码会和设备进行交互,并使用 iret 指令返回到之前的任务上(该指令告诉 CPU 从栈中恢复之前保存的状态)。在 ret 之前,代码会被全部执行完并告诉 PIC 当前中断已经处理完毕,可以发送新的或者之前没发送完的中断了。直到收到 CPU 像下面这样响应中断前,PIC 都不会再发送新的中断:

mov al, 20h
out 20h, al

在键盘输入的例子中,中断服务程序会询问键盘被按下的按键并处理收到的信息,然后像下面这样响应 PIC 并返回:

push eax		;; 保存当前状态
in al, 60h		;; 从键盘读信息

mov al, 20h
out 20h, al		;; 告知 PIC 已收到中断
pop eax			;; 恢复状态
iret			;; 返回到中断服务前的代码

在这之后 CPU 就会返回到之前正在做的事情上继续执行(除非在处理一个中断时 PIC 又收到了另一个中断:这种情况下,等 CPU 将新的状态存入栈中后, PIC 就会通知 CPU 执行一个新的中断服务程序)

那我该如何对这玩意进行编程呢?

按照上面的思路一步步来即可:

  • 为 IDT 分配空间
  • 告诉 CPU IDT 的空间在哪里(详见 GDT Tutorial ,lidt 和 lgdt 的工作原理非常相似)
  • 告诉 PIC 不再需要使用 BIOS 默认设置( 详见 PIC芯片编程
  • 为 IRQ 和异常( exceptions )写一些 ISR 处理程序(见 ISR
  • 将 ISR 处理程序的地址放在适当的描述符中(在 IDT 中的)
  • 在 PIC 的 IRQ mask 中启用所有支持的中断

通用 IBM-PC 兼容的中断信息

标准 ISA IRQs

IRQDescription
0Programmable Interrupt Timer Interrupt
1Keyboard Interrupt
2Cascade (used internally by the two PICs. never raised)
3COM2 (if enabled)
4COM1 (if enabled)
5LPT2 (if enabled)
6Floppy Disk
7LPT1 / Unreliable "spurious" interrupt (usually)
8CMOS real-time clock (if enabled)
9Free for peripherals / legacy SCSI / NIC
10Free for peripherals / SCSI / NIC
11Free for peripherals / SCSI / NIC
12PS2 Mouse
13FPU / Coprocessor / Inter-processor
14Primary ATA Hard Disk
15Secondary ATA Hard Disk

默认 PC 中断向量分配

IntDescription
0-31Protected Mode Exceptions (Reserved by Intel)
保护模式异常(由 Intel 保留)
8-15Default mapping of IRQ0-7 by the BIOS at bootstrap
由 BIOS 在启动阶段默认映射的 IRQ0-7
70h-78hDefault mapping of IRQ8-15 by the BIOS at bootstrap
由 BIOS 在启动阶段默认映射的 IRQ8-15

端口

PortDescription
20h & 21hcontrol/mask ports of the master PIC
A0h & A1hcontrol/mask ports of the slave PIC
60hdata port from the keyboard controller
64hcommand port for keyboard controller - use to enable/disable kbd interrupts, etc.

相关内容

文章

外部链接