本章探讨 Linux 内核如何处理中断。虽然内核具有用于处理中断的通用机制和接口,但大多数中断处理细节都是特定于体系结构的。
Linux 使用许多不同的硬件部件来执行许多不同的任务。视频设备驱动显示器,IDE 设备驱动磁盘等等。您可以同步地驱动这些设备,也就是说,您可以发送一个操作请求(例如将内存块写入磁盘),然后等待操作完成。虽然这种方法可行,但效率非常低下,操作系统会花费大量时间“忙于无所事事”,因为它等待每个操作完成。一种更好、更有效的方法是发出请求,然后执行其他更有用的工作,稍后在设备完成请求时被设备中断。通过这种方案,系统中可能会有许多未完成的设备请求同时发生。
必须有硬件支持,以便设备能够中断 CPU 正在执行的任何操作。大多数(如果不是全部)通用处理器,如 Alpha AXP 都使用类似的方法。CPU 的一些物理引脚的布线方式是,改变电压(例如从 +5v 变为 -5v)会导致 CPU 停止正在执行的操作,并开始执行特殊代码来处理中断;即中断处理代码。这些引脚中的一个可能连接到间隔定时器,并每秒接收 1/1000 次中断,其他引脚可能连接到系统中的其他设备,例如 SCSI 控制器。
系统通常使用中断控制器将设备中断分组在一起,然后再将信号传递到 CPU 上的单个中断引脚。这节省了 CPU 上的中断引脚,并在设计系统时提供了灵活性。中断控制器具有控制中断的掩码和状态寄存器。设置掩码寄存器中的位可以启用和禁用中断,而状态寄存器返回系统中当前活动的中断。
系统中的某些中断可能是硬连线的,例如,实时时钟的间隔定时器可能永久连接到中断控制器上的引脚 3。但是,某些引脚连接到什么可能取决于插入特定 ISA 或 PCI 插槽的控制器卡。例如,中断控制器上的引脚 4 可能连接到 PCI 插槽编号 0,该插槽某天可能装有以太网卡,但第二天可能装有 SCSI 控制器。最重要的是,每个系统都有自己的中断路由机制,操作系统必须足够灵活以应对。
大多数现代通用微处理器都以相同的方式处理中断。当发生硬件中断时,CPU 停止执行它正在执行的指令,并跳转到内存中的某个位置,该位置要么包含中断处理代码,要么包含分支到中断处理代码的指令。此代码通常在 CPU 的特殊模式下运行,即中断模式,并且通常在这种模式下不会发生其他中断。但也有例外;某些 CPU 对中断进行优先级排序,并且可能会发生更高级别的中断。这意味着第一级中断处理代码必须非常小心地编写,并且它通常有自己的堆栈,它在处理中断之前使用该堆栈来存储 CPU 的执行状态(CPU 的所有常规寄存器和上下文)。一些 CPU 有一组仅在中断模式下存在的特殊寄存器,中断代码可以使用这些寄存器来完成它需要做的大部分上下文保存。
当中断被处理后,CPU 的状态被恢复,中断被解除。然后 CPU 将继续执行在被中断之前正在执行的任何操作。重要的是,中断处理代码尽可能高效,并且操作系统不要过于频繁或过长时间地阻止中断。
系统设计人员可以自由使用他们想要的任何中断架构,但 IBM PC 使用 Intel 82C59A-2 CMOS 可编程中断控制器或其衍生产品。这种控制器自 PC 诞生以来就已存在,并且是可编程的,其寄存器位于 ISA 地址空间的众所周知的位置。即使是非常现代的支持逻辑芯片组也保持了 ISA 内存中相同位置的等效寄存器。非基于 Intel 的系统,如基于 Alpha AXP 的 PC,不受这些架构约束的限制,因此通常使用不同的中断控制器。
图 7.1 显示有两个 8 位控制器链接在一起;每个控制器都有一个掩码和一个中断状态寄存器,PIC1 和 PIC2。掩码寄存器的地址分别为 0x21 和 0xA1,状态寄存器的地址分别为 0x20 和 0xA0。向掩码寄存器的特定位写入 1 会启用中断,写入 0 会禁用中断。因此,向位 3 写入 1 将启用中断 3,写入 0 将禁用中断 3。不幸的是(并且令人恼火的是),中断掩码寄存器是只写的,您无法读回您写入的值。这意味着 Linux 必须保留一份本地副本,记录它已将掩码寄存器设置为的值。它在中断启用和禁用例程中修改这些保存的掩码,并在每次都将完整的掩码写入寄存器。
当中断被信号化时,中断处理代码读取两个中断状态寄存器 (ISR)。它将地址为 0x20 的 ISR 视为 16 位中断寄存器的低八位,并将地址为 0xA0 的 ISR 视为高八位。因此,0xA0 处 ISR 的位 1 上的中断将被视为系统中断 9。PIC1 的位 2 不可用,因为它用于链接来自 PIC2 的中断,PIC2 上的任何中断都会导致 PIC1 的位 2 被设置。
各个设备驱动程序调用这些例程来注册它们的中断处理例程地址。
某些中断通过 PC 架构的约定固定,因此驱动程序在初始化时只需请求其中断即可。这就是软盘设备驱动程序所做的;它始终请求 IRQ 6。有时设备驱动程序可能不知道设备将使用哪个中断。对于 PCI 设备驱动程序来说,这不是问题,因为它们始终知道它们的中断号。不幸的是,ISA 设备驱动程序没有简单的方法来查找它们的中断号。Linux 通过允许设备驱动程序探测它们的中断来解决这个问题。
首先,设备驱动程序对设备执行某些操作,导致设备中断。然后,系统中所有未分配的中断都被启用。这意味着设备的待处理中断现在将通过可编程中断控制器传递。Linux 读取中断状态寄存器并将其内容返回给设备驱动程序。非零结果表示在探测期间发生了一个或多个中断。驱动程序现在关闭探测,并且所有未分配的中断都被禁用。
如果 ISA 设备驱动程序已成功找到其 IRQ 号,那么它现在可以像往常一样请求控制它。
基于 PCI 的系统比基于 ISA 的系统动态得多。ISA 设备使用的中断引脚通常使用硬件设备上的跳线设置,并在设备驱动程序中固定。另一方面,PCI 设备的 interrupts 由 PCI BIOS 或 PCI 子系统在系统启动时初始化 PCI 时分配。每个 PCI 设备可以使用四个中断引脚之一,A、B、C 或 D。这是在设备构建时固定的,大多数设备默认在引脚 A 上中断。每个 PCI 插槽的 PCI 中断线 A、B、C 和 D 都路由到中断控制器。因此,来自 PCI 插槽 4 的引脚 A 可能会路由到中断控制器的引脚 6,来自 PCI 插槽 4 的引脚 B 可能会路由到中断控制器的引脚 7,依此类推。
PCI 中断如何路由完全是特定于系统的,并且必须有一些设置代码来理解这种 PCI 中断路由拓扑。在基于 Intel 的 PC 上,这是在启动时运行的系统 BIOS 代码,但对于没有 BIOS 的系统(例如基于 Alpha AXP 的系统),Linux 内核会执行此设置。
PCI 设置代码将中断控制器的引脚号写入每个设备的 PCI 配置头中。它使用其对 PCI 中断路由拓扑的了解以及设备的 PCI 插槽号和它正在使用的 PCI 中断引脚来确定中断引脚(或 IRQ)号。设备使用的中断引脚是固定的,并保存在此设备的 PCI 配置头中的字段中。它将此信息写入为此目的保留的中断线字段中。当设备驱动程序运行时,它会读取此信息并使用它向 Linux 内核请求中断控制权。
系统中可能存在许多 PCI 中断源,例如,当使用 PCI-PCI 桥时。中断源的数量可能超过系统可编程中断控制器上的引脚数量。在这种情况下,PCI 设备可以共享中断,中断控制器上的一个引脚接受来自多个 PCI 设备的中断。Linux 通过允许中断源的第一个请求者声明它是否可以共享来支持这一点。共享中断会导致多个irqaction数据结构被irq_action 向量向量中的一个条目指向。当共享中断发生时,Linux 将调用该源的所有中断处理程序。任何可以共享中断的设备驱动程序(应该是所有 PCI 设备驱动程序)都必须准备好在其没有中断需要服务时调用其中断处理程序。
Linux 中断处理子系统的主要任务之一是将中断路由到正确的中断处理代码片段。此代码必须理解系统的中断拓扑。例如,如果软盘控制器在中断控制器的引脚 6 1 上中断,那么它必须识别来自软盘的中断,并将其路由到软盘设备驱动程序的中断处理代码。Linux 使用一组指向数据结构的指针,这些数据结构包含处理系统中断的例程的地址。这些例程属于系统中设备的设备驱动程序,每个设备驱动程序都有责任在驱动程序初始化时请求它想要的中断。图 7.2 显示irq_action是一个指向irqaction数据结构的指针向量。每个irqaction数据结构都包含有关此中断的处理程序的信息,包括中断处理例程的地址。由于中断的数量以及它们如何处理因体系结构而异,有时甚至因系统而异,因此 Linux 中断处理代码是特定于体系结构的。这意味着irq_action 向量向量的大小取决于中断源的数量。
当中断发生时,Linux 必须首先通过读取系统可编程中断控制器的中断状态寄存器来确定其来源。然后,它将该来源转换为irq_action 向量向量的偏移量。因此,例如,来自软盘控制器的中断控制器引脚 6 上的中断将被转换为中断处理程序向量中的第七个指针。如果发生的中断没有中断处理程序,那么 Linux 内核将记录错误,否则它将调用此中断源的所有irqaction数据结构的中断处理例程。
当设备驱动程序的中断处理例程被 Linux 内核调用时,它必须有效地找出它为什么被中断并做出响应。为了找到中断的原因,设备驱动程序将读取中断设备的状态寄存器。设备可能正在报告错误或请求的操作已完成。例如,软盘控制器可能正在报告它已完成将软盘的读写头定位在软盘上的正确扇区上方。一旦确定了中断的原因,设备驱动程序可能需要做更多的工作。如果需要,Linux 内核具有允许它将该工作推迟到稍后的机制。这避免了 CPU 在中断模式下花费太多时间。有关更多详细信息,请参阅设备驱动程序章节(第 dd-chapter 章)。
审阅注意 快速中断和慢速中断,这些是英特尔的东西吗?
1 实际上,软盘控制器是 PC 系统中的固定中断之一,因为按照惯例,软盘控制器始终连接到中断 6。