Interrupts
“Interrupts 中断” 是从设备 (例如键盘或硬盘驱动器) 到CPU的信号,告诉CPU应当立即停止当前正在执行的操作并执行其他操作。 例如,键盘控制器可以在按下字符键时发送中断。 然后,操作系统可以立即在屏幕上显示该字符,即使CPU之前正在做完全不相关的事情,然后CPU可以再返回到之后要做的事情。 当出现特定中断时,CPU会从操作系统提供的表中查找该特定中断的条目。 在 x86保护模式 中,该表称为 中断描述符表 (Interrupt Descriptor Table),最多可以有256个条目(Entry),但是该表的名称和它可以拥有的最大条目数可能会因CPU架构而异。 CPU找到中断的条目后,跳转到条目指向的代码。 响应中断而运行的此代码称为 中断服务例程 (ISR) 或中断处理程序。
中断的类型
在大多数平台上,通常有三类中断:
- Exceptions: 这些是由CPU内部生成的,用于向正在运行的内核发出需要其注意的事件或情况的警报。 在x86 cpu上,这些包括双重故障(Double Fault)、页面故障(Page Fault)、通用保护故障(General Protection)等异常条件。
- Interrupt Request (IRQ) 或 Hardware Interrupt: 这种类型的中断由芯片组外部产生,并通过锁存到目前CPU的#INTR引脚或等效信号来发出信号。 现今常用的IRQ有两种。
- IRQ Lines, 或 Pin-based IRQs: 这些通常在芯片组上静态路由。 设备上的芯片通过连线或电路将信号发送到IRQ控制器,该控制器将设备发送的中断请求串行化,并将它们逐个发送到CPU以防止信号冲突。 在许多情况下,IRQ控制器会根据设备的优先级一次向CPU发送多个IRQ。 一个非常著名的IRQ控制器的例子是 Intel 8259 控制器链,它存在于所有IBM-PC兼容的芯片组上,将两个控制器链接在一起,每个控制器提供8个输入引脚,用于传统IBM-PC上总共16个可用的IRQ信号引脚。
- Message Signaled Interrupts: 消息信号中断通过将值写入为关于中断设备、中断本身和向量信息的保留存储器位置来直接发出信号。(译者注:本文的“向量-vector”愿意指的是某些特殊作用的列表中的独立值,例如这里是一系列中断(编号)中的一个) 设备被通过固件或内核软件分配了写入以上值的位置。 然后,设备使用特定于设备总线的仲裁协议生成IRQ。 PCI总线也是一种提供基于消息的中断功能的总线。
- Software Interrupt: 这是运行在CPU上的软件发出的中断信号,一般表明软件需要内核做出某些响应。 这些类型的中断一般用于 系统调用。 在x86 CPU上,用于启动软件中断的指令是 “INT” 指令。 由于x86 CPU可以使用256可用的中断向量中的任何一个来进行软件中断,所以内核通常需要先从中选择一个。 例如,许多当代的Unix衍生系统在基于x86的平台上使用向量 0x80。
作为一项规则,当一个CPU给开发人员自由选择哪些向量用于什么 (如在x86上),应该避免在同一向量上有不同类型的中断。 通常的做法是,按照Intel的要求,将前32个向量留给Exceptions。 但是,你对其余向量的划分取决于你。
从键盘的角度来看
基本上,当按键被按下时,键盘控制器会告诉被称为 可编程中断控制器(PIC) 的设备发起中断。 由于键盘和PIC的固有接线方式,IRQ #1是键盘中断,因此当按下一个键时,IRQ 1被发送到PIC。 PIC的作用是决定是否应立即通知CPU该IRQ,并将IRQ数转换为CPU表的 “中断向量” (即0和255之间的数字)。
操作系统应该通过 in 和 out 指令 (或 inportb/outportb) 与键盘通话来处理中断,inportw/outportw,以及 inportd/outportd 在C中,见 内联汇编示例),询问按下了什么键,再对此做一些工作 (例如在屏幕上显示键,并通知当前应用程序已按下某个键),然后返回到中断进入前正在执行的代码。 实际上,如果无法从缓冲区读取键将阻止键盘的任何后续IRQ。
从PIC的角度来看
实际上,大多数系统上都有两个PIC,每个PIC都有8个不同的输入,外加一个输出信号,用于告诉CPU发生了IRQ。 “从PIC”的输出信号连接到“主PIC”的第三个输入口 (输入 #2); 因此,当“从PIC”想要告诉CPU发生中断时,它实际上先告诉“主PIC”,而由主PIC告诉CPU。 这被称为 “级联”。这种方式下主PIC的第三个输入是为此配置的,而不是配置为普通的IRQ,这意味着IRQ 2不能发生。
设备向PIC芯片发送中断,PIC告诉CPU发生了中断 (直接或间接)。 当CPU确认 “中断发生” 信号时,PIC芯片向CPU发送中断编号 (在00h和FFh之间,或十进制0和255之间)。 系统首次启动时,IRQ 0至7设置为中断08h至0Fh,IRQ 8至15设置为中断70h至77h。 因此,对于IRQ 6,PIC将告诉CPU服务INT 0Eh,该服务应该具有用于与连接到主PIC芯片的 “输入 #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同时进来,则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处理该中断 (使用IDT查找该中断的处理程序)。
警觉的读者可能会注意到CPU保留了中断0-31,但IRQs 0-7设置为中断08-0fh。 现在,当操作系统必须处理的可怕错误发生时,CPU将调用保留的中断。 当计算机首次启动时,大多数此类错误都不会发生。 但是,当你进入保护模式 (并且每个操作系统都应使用保护模式,实模式已过时) 时,这些错误随时可能发生,并且操作系统需要能够处理它们。 操作系统将如何区分INT 9,异常-协处理器的段超限,和INT 9: IRQ 1之间的区别? 其实这时操作系统可以询问设备是否真的有中断。 但这是缓慢又麻烦的,并不是所有的设备都能够做这种事情。 最好的方法是告诉PIC将IRQ映射到 “不同的” 中断,例如INT 78h-7Fh。 有关此信息,请参阅 PIC的常见问题部分。 请注意,IRQ只能映射到08h: 00h-07h,08h-0Fh,10h-17h,17h-1Fh的倍数的整数。 而且你最好使用20h-27h或更高,因为CPU保留了00h-1Fh。 此外,每个PIC都必须单独编程。 你可以告诉“主PIC”将IRQs 0-7映射到INTs 20h-27h,但是IRQs 8-F仍然是INTs 70h-77h,或者你告诉“从PIC”也把它们也放在其他地方。
有关详细信息,请参阅 对PIC芯片进行编程。
从CPU的角度来看
每次CPU执行一条机器指令时,它都会检查PIC的引脚是否已通知中断。 如果是这种情况,它会在堆栈上存储一些当前状态信息(这样,当操作系统完成对INT中断的服务时,它就可以返回到当前正在做的任何事情),并跳转到IDT所指向的位置。 操作系统从这里开始接管工作。 同时,当前程序可以通过“中断标志”(IF状态寄存器)来防止CPU再受到中断的干扰。 只要清除该标志,CPU就会忽略PIC的请求,持续运行当前程序。 汇编指令 cli and sti 可以控制该标志。
从操作系统的角度来看
当出现中断时,IDT (由操作系统预先设置) 用于跳转到操作系统的相关代码部分,该部分处理中断 (因此称为 “中断处理程序” 或 “中断服务例程”)。 通常,操作系统代码开始与设备交互,完成后通过使用 iret 指令返回到它以前的工作, (该指令告诉CPU从堆栈中加载其保存的状态信息)。 在上面 ret 之前,需要执行以下代码,以告诉PIC可以发送任何新的或挂起的中断,因为当前中断已完成。(译者注:以下代码是给8259a芯片发送EOI命令,通知其一个中断已完成) 在CPU确认中断之前,PIC是不会再发送任何中断的:
mov al,20h
out 20h,al
在 键盘输入 的情况下,中断处理程序会询问键盘按下了哪个键,对信息执行某些操作,然后确认并返回:
push eax ;; 确保不损坏当前状态
in al,60h ;; 从键盘读取信息
mov al,20h
out 20h,al ;; 确认对PIC的中断
pop eax ;; 恢复状态
iret ;; 返回到以前执行的代码。
无论CPU以前做什么,然后都需要重新恢复 (除非PIC在服务这个PIC时接收到另一个INT,在这种情况下,PIC会告诉CPU,并且CPU再次将状态信息保存在堆栈上后,就开始执行一个新的中断处理程序)。
那么我该如何编程实现这些 ?
一步一步来,现在你已经抓住了整个事情的脉络,知道要做什么:
- 为中断描述符表(IDT)腾出空间
- 告诉CPU该空间在哪里 (请参阅 GDT教程: lidt 的工作方式与 lgdt 的工作方式非常相同)
- 告诉PIC你不再要使用BIOS默认值 (请参阅 对PIC芯片进行编程)
- 为IRQ和异常编写几个ISR处理程序 (请参阅 中断服务例程)
- 将ISR处理程序的地址放在适当的描述符中 (在 中断描述符表 中)
- 在 (PIC的) IRQ掩码中启用所有支持的中断
通用IBM-PC兼容中断信息
标准 ISA 中断请求
IRQ | 描述 |
---|---|
0 | 可编程中断定时器中断 |
1 | 键盘中断 |
2 | 级联 (两个芯片内部使用。不会发起) |
3 | 串口COM2 (如果开启) |
4 | 串口COM1 (如果启用) |
5 | 并口LPT2(如果启用) |
6 | 软盘 |
7 | 并口LPT1 / 通常是“伪”中断(不可靠) |
8 | CMOS实时时钟(如果开启) |
9 | 自由外设/legacy SCSI/网卡 |
10 | 自由外设/SCSI/网卡 |
11 | 自由外设/SCSI/网卡 |
12 | PS2鼠标 |
13 | FPU/协处理器/Inter-processor |
14 | 主ATA硬盘 |
15 | 辅ATA硬盘 |
默认PC中断向量分配
Int | 描述 |
---|---|
0-31 | 保护模式异常(Intel保留) |
8-15 | 引导程序中BIOS对IRQ0-7的默认映射 |
70h-78h | 引导时BIOS对IRQ8-15的默认映射 |
Ports
Port | 描述 |
---|---|
20h & 21h | 主PIC的控制/屏蔽端口 |
A0h & A1h | 从PIC的控制/屏蔽端口 |
60h | 键盘控制器的数据端口 |
64h | 键盘控制器的命令端口-用于启用/禁用kbd中断等。 |
S390中断
根据指令上下文识别S390体系结构上的异常。
外部中断(External Interrupt)
当CPU计时器或外部设备触发所述中断时,产生外部中断。
主管调用中断(Supervisor Call Interrupt)
主管调用生成一个中断,该中断将当前的 PSW 放入 FLCSOPSW (服务旧PSW)。 然后从 FLCSNPSW (服务新PSW) 加载新的PSW。 SVC代码的位置放在 FLCESICODE
输入/输出中断(Input/Ouput Interrupt)
当设备完成命令程序的执行时,通常会触发此中断。
程序检查异常(Program Check Exception)
当访问无效地址或执行无效指令时,将识别程序异常。
规格例外(Specification Exception)
通常在指令的给定参数或寄存器无效时产生。
机器检查异常(Machine Check Exception)
当部分设备报告故障时产生。 通常意味着所述设备应该被更换,这取决于用户自己。