第 七 章 中 断 与 中 断 处 理


第七章 中断与中断处理 本章讲述Linux内核如何处理中断。 虽然通常操作系统都提供一些通用的机制和接口来处理中断,大多数中 断处理的细节是与具体的设备体系结构有关的。
图7.1
Linux支持许多不同的硬件。视频设备驱动显示器,IDE设备驱动磁盘等 等。你可以同步地驱动这些设备:发出一个操作请求然后等待操作的完 成(比如将一块内存的内容写进磁盘)。这种方法虽然在逻辑和实践上 都行的通,但是效率很差。在等待你请求的操作返回的时候,操作系统 什么也不能作(busydoingnothing),浪费了许多CPU时间(译者:外设的速 度比CPU通常慢很多)。一个更好的,更有效的方法是:操作系统发出 外设操作请求,然后去做别的事情。当外设请求完成并返回时,中断操 作系统。通过这种方案,系统里可以同时支持许多设备请求,而不是 严格的同步。 不管系统采用什么CPU,我们必须用一些硬件来提支持设备中断CPU。 大多数处理器(比如AlphaAXP)采用类似的方法:CPU管葵7d(Pin)中的一些 上的电压的变化(例如从+5伏到-5伏)可以导致CPU停止它正在处理的 事情,而转到一段特殊的代码过程上去处理中断。在那些CPU管脚中, 有一个管脚连接在一个内部定时器上,从而可以每秒接收1000次的中断 。其他的负责中断的管脚会连在相应的其他设备上,比如SCSI驱动器。 在传递中断信号到一个CPU中断管脚前,系统常采用一个中断控制器并 用它将众多的设备中断组合起来。这样就节省了CPU的中断管脚并且给 系统设计带来了灵活性。中断控制器常用其的Mask寄存器和状态寄存器 来控制来自设备的中断信号。通过设置Mask寄存器的位操作可以允许 或屏蔽一些外设的中断。状态寄存器可以用来查询当前系统中已激活的 待处理的中断。 系统中有一些中断管脚是固定连接的,比如,实时时钟的定时器可能被 永久地连在中断控制器的管脚3上。当然,这些管脚具体连接的是什么 设备取决于插在ISA或PCI插槽上的是设备控制器。比如,中断控制器 的管脚4可能对应于PCI插槽0,而在此插槽上有可能今天是一块 Ethernet网卡,明天是一个SCSI控制器。对于这样每一种设备都提供不 同的,为特定设备而写的中断处理过程,操作系统必须提供足够的灵活 性来处理。 大多数通用微处理器采用同样的方式处理中断:当一个硬件中断发生时 ,CPU停止它正在处理的指令,跳转到内存中的一个地址。在那个地址 处,含有中断处理过程或一条可以指向中断处理过程的指令。这段代 码一般运行在CPU的一种特殊模式---中断模式。一般而言,在这种模式 下,其他的中断不会被接受。当然也存在例外的情况。一些CPU将中断 按优先级划分,从而在处理低优先级的中断时,更高级的中断可以被 处理。换句话说,最低级的那断中断处理过程必须非常细心的编写。例 如,需要一个自己的栈空间以用来保存CPU的执行状态(所有的CPU寄 存器和上下文)在CPU被剥夺并处理更高一级的中断之前。(译者注:1 。上下文:context.这里的上下文讲的是用户进程切换到核心态时的上 下文。2。中断处理是一个过程,通常依附在一个进程的上下文中。3 。这里讲的栈通常是指一个进程在核心态下的核心栈。所有核心态下的 “过程”调用包括中断处理过程都在这个栈上处理。)。有些CPU提供 一套特殊的寄存器集。这套寄存器集只存在于中断模式下。从而中断过 程处理代码可以利用它们来保存大多数的,需要保存的上下文。(译者 注:现代操作系统调度中,一个很大的代价发生在进程上下文切换中。 感兴趣的读者可以访问UCBerkeley的NOW项目中的co-schedule部份。提 供一套寄存器有利于性能优化。) 当中断处理完毕后,CPU的状态被恢复;CPU将继续从断点处执行(译 者住:这个断点有可能是会到用户态,也有可能仍然在核心态,比如, 继续完成系统调用或处理低一级的中断请求。)。所以,中断处理程序 要尽可能的高效以防止堵塞其他的中断。 7.1可编程中断控制器 系统设计师可以随意选择他们希望的中断控制器硬件。IBM-PC系列用的 是INTEL的82C59A-2CMOS可编程中断控制器系列。这种控制器自从有了 PC就存在了,提供了一套可编程的,在ISA地址空间里,地址是周知 (Well-Known)的寄存器。任何一个现代的逻辑chipsets为这些寄存器保留着 同样的ISA内存地址。非INTEL处理器系统,例如基于AlphaAXP的PC就 不受上述限制。它们采用不同的中断控制器。 图7.1所示是两个8位的控制器连在一起,PIC1和PIC2。每一个控制器 有一个Mask寄存器和一个中断状态寄存器,Mask与中断状态寄存器的 地址分别在ox21,oxA1和ox20,oxA0。对一个Mask的某一位置1将允许一个 中断;置0将屏蔽一个中断。例如,位3置1将使能(Enable)中断3。反之 将屏蔽中断3。遗憾的是,中断Mask寄存器是只能写,不可读,你不能 读回刚刚写进去的位值。这意味著Linux必须在核心中保存一份当前Mask 寄存器的备份。每次核心先改写这个“最近的”备份然后一次性的刷 新Mask寄存器。 当一个中断到来时,中断处理过程读图中的两个中断状态寄存器(ISR)。 系统把在0x20的ISR当做这个16位中断状态寄存器的低8位,在地址0xA0 上的ISR为高8位。所以如果在oxA0上的ISR的第一位被置一的话,系统 认为来了一个中断9。PIC1的第2位被用来连接PIC2。所以任何PIC2的中 断都会导致PIC1的第2位被置1。 7.2中断处理数据结构的初始化 核心的中断处理数据结构的设置由设备驱动程序(DeviceDriver)来负责,因 为是它们需要控制中断。设备驱动程序利用Linux核心中的一些服务例 程(译者注:核心中一些预先编好的功能函数。)来请求,使能或屏蔽一 个中断。 这些各自不相同的设备驱动程序通过调用上述例程来等级它们的中断处 理过程地址。 对于PC体系结构,一些中断的中断号是固定的,约定好的。初始化时 ,驱动器只要申请这个中断就可以。比如软盘驱动器将固定使用IRQ6。 有时候设备驱动程序不知道设备将使用那个中断。对于PCI设备驱动程 序而言,这不是个问题因为设备占用的中断号可以被知道。但对于ISA 设备驱动程序就不是那么容易知道。Linux通过允许设备驱动程序探测中 断号来解决上述问题。 首先,这个设备驱动程序通过一些操作使得这个设备发出中断。然后使 能所有的,系统中还没有分配出去的中断号。这意味著这个设备发出 的中断通过中断控制器会被系统接收。然后Linux读取ISR的内容并将当 前值传递给上述的设备驱动程序。一个非0值将意味著一个或多个中断 已经发生。这时,设备驱动程序重新屏蔽所有未分配的中断口。 ISA设备驱动程序在知道它的设备占用的中断号后,就可以象正常一样 去注册它的中断处理过程了。 基于PCI的系统比起基于ISA的系统有更多的灵活性。ISA设备一般通过 设置硬件板上的跳线(Jumpers)来设置中断。跳线设置后,在系统初始化 后,核心程序中这个中断号是已经固定的分配给这个设备了(译者注: 如果没有中断冲突的化)。然而,PCI设备的中断是在系统启动时,通过 PCIBIOS或PCI子系统在初始化时来分配的。每一个PCI设备卡有四个中 断管脚,A,B,C和D。通常设备缺省使用管脚A。每一个PCI插槽的A,B,C和 D中断管脚都被引向中断控制器。所以PCI插槽4的管脚A可能映射在中 断控制器的管脚6上,管脚B可能映射在中断控制器的管脚7上。 PCI中断的如何映射跟不同的系统有关。任何一个系统都要提供一些代 码用来解释PCI中断映射拓朴。基于INTEL的PC通过BIOS代码。对于没有 BIOS的系统(基于AlphaAXP的系统),Linux核心将会负责处理上述任务。 上述PCI设置代码将每块PCI设备相对应的IRQ号写入一个PCI配置头 (ConfigurationHeader)数据结构。IRQ号的获得是通过PCI中断映射拓朴, PCI插槽和哪一个PCI中断管脚正被使用而推导出来的。对每块PCI设备 ,它用的IRQ号将被固定下来并写入其相应的PCI配置头数据结构的值域 中--"interruptline"。当这个设备运行时,它读取这个信息然后向Linux核心 要求占有这个中断的处理权。 在一个系统中,有可能同时存在许多PCI中断源。例如当PCI桥的情况下 。所以就有可能中断源的数目超过系统提供的可编程中断控制器的管脚 数目。这种情况下,PCI设备之间可能要共用一些中断口,中断控制器 一个管脚将接收来自多个PCI设备的中断。Linux允许第一个申请占有一 个特定中断口的中断源愿不愿意将这个中断口被其他设备共享。共享的 中断口信息都存放在一些叫做irqaction的数据结构中。irqaction结构的地址 可在一个向量irq_action中找到。当一个共享的中断发生时,Linux将调用 挂在这个中断上的,所有的设备的,中断处理程序。因此任何一个可以 支持共享中断的设备驱动程序(所有的PCI驱动程序)都必须能够支持其 中断处理过程被调用虽然在那个时刻这个设备没有中断发生。 7.3中断处理
图7.2
Linux中断处理子系统的一个首要任务是当处理中断时,将指令控制指向 正确的中断处理代码过程。完成上述任务的代码必须了解系统的中断分 部情况。例如,如果软盘驱动控制器用的中断口是中断控制器的管脚6 ,那么当接收到一个中断信号6时,系统必须将CPU执行地址转到软盘 设备驱动程序代码处。Linux使用一系列指针指向含有中断处理例程的数 据结构。这些例程分别属于系统中不同的设备驱动程序。每一个设备驱 动程序在初始化时负责申请它所需要的中断号。如图7.2所示,irq_action 是一个指针向量指向irqaction数据结构。每一个irqaction数据结构含有为这 个中断口(译者注:irq_action向量的下标加1)服务的处理程序的信息(包含 中断处理程序的入口地址)。至于系统支持的中断数目和中断如何被处 理,对于不同的(硬件)体系结构和操作系统,方法不一样。Linux的中断 处理代码是与体系结构有关的。irq_action向量的大小依赖于系统中中断 源的数目。 当一个中断发生时,Linux首先必须通过读取当前的ISR(中断状态寄存器 )来决定中断的来源。然后核心把这个中断源映射到irq_action向量一个偏 移量上。例如,一个来自软驱的中断6将被映射到向量的第7个入口。 如果对于一个发生了的中断没有一个中断处理句柄相对应,Linux核心将 记载一个错误。否则,核心将通过查询所有的”挂“这个中断口上 irqaction结构并调用相应的中断处理例程。(译者注:如果是线性的链表 查询的话,可以在这里做一些算法上的优化。如将最近常发生中断的那 个中断源的数据结构移到链表的前面。很多现成的算法可以用上来。) 当一个设备驱动程序的中断处理例程被Linux核心调用以后,它必须迅速 地解决为什么来了中断并做出反应。为了找出中断的原因,设备驱动程 序会读取这个中断设备的状态寄存器。这个设备有可能正在汇报一个 错误或一个请求的操作完成。比如,一个软盘控制器汇报对一个指定的 磁盘扇区的磁头定位已经结束。一旦中断的原因被查明,设备驱动程 序有可能需要采取更多的工作去响应这个中断。如果是这样,Linux核心 提供机制允许设备驱动程序推延其操作。从而可以避免CPU花费太多的 时间在中断模式下。有关这方面的细节请参阅设备驱动程序章节。