从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当的处理了。
不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这样,操作系统才能给不同的中断提供不同的中断处理程序。这些中断值被称为中断请求线(IRQ: Interrupt Request)。通常 IRQ 都是一些数值量,例如,在 PC 上,IRQ0 是时钟中断,IRQ1 是键盘中断。
异常:同步中断
中断:由硬件产生的异步中断
一. 中断处理程序
在响应一个特定中断的时候,内核会执行一个函数,即 中断处理程序(interrupt handler) 或 中断服务例程(interrupt service routine, ISR)。产生中断的每个设备都有一个相应的中断处理程序(实际上,中断处理程序通常不是和特定设备关联,而是和特定中断关联,也就是说,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序)。一个设备的中断处理程序是它设备驱动程序的一部分。
在 Linux 中,中断处理程序看起来就就是普通的 C 函数,只不过这些函数必须按照特定的类型声明,以便内核能够以标准的方式传递处理程序的信息,在其它方面,它们与一般的函数看起来别无二致。中断处理程序与其它内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。
中断随时可能发生,因此中断处理程序也就随时可能执行,所以必须保证中断处理程序能够快速执行,这样才能保证尽可能快的恢复中断代码的执行。因此,尽管对硬件而言,迅速对其中断进行服务非常重要,但是对系统其它部分而言,让中断处理程序在尽可能短的时间内完成运行也同样重要。
上半部与下半部的对比:
又想程序运行得快,又想程序完成的工作量多,这两个目的显然有所抵触。鉴于两个目的之间存在不可调和的矛盾,所以我们一般把中断处理切为两个部分。中断处理程序是上半部——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或者复位硬件,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作会推迟到下半部去。此后,在合适的时机,下半部会被开中断执行。
以网卡为例:当网卡接收流入网络的数据包时,需要通知内核数据包到了。网卡需要立即完成这件事,从而优化网络的吞吐量和传输周期,以避免超时,因此,网卡立即发出中断:我这里有最新的数据包了。内核通过执行网卡已注册的中断处理程序来做出应答。中断开始执行,应答硬件,拷贝最新的网络数据包到内存,然后读取网卡更多的数据包。这些都是重要、紧迫而又与硬件相关的工作。处理和操作数据包的其它工作在随后的下半部中进行。
二. 注册和编写中断处理程序
中断处理程序是管理硬件的驱动程序的组成部分。每一设备都有相关的驱动程序,如果设备使用中断(大部分设备如此),那么相应的驱动程序就注册一个中断处理程序。
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irqflags, const char *devname, void *dev_id)
第一个参数 irq 表示要分配的中断号,对大多数的设备来说,这个值要么是可以通过探测获取,那么可以通过编程动态确定。
第二个参数 handler 是一个指针,指向处理这个中断的实际中断处理程序。
卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线:
void free_irq(unsigned int irq, void *dev_id)
如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除 dev_id 所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用。
中断处理程序:
static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs)
第一个参数 irq 就是这个处理程序要响应的中断的中断线号,如今这个参数已经没有太大的用处了。
第二个参数 dev_id 是一个普通指针,它与在中断处理程序注册时传递给 request_irq() 的参数 dev_id 必须一致。如果该值有唯一确定性,那么它就相当于一个 cookie,可以用来区分共享同一中断处理程序的多个设备。
最后一个参数 regs 是一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态,除了调试的时候,它们很少用到。
中断处理程序的返回值是一个特殊类型:irqreturn_t。中断处理程序可能返回两个特殊的值:
IRQ_NONE: 检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源
IRQ_HANDLED: 中断处理程序被正确调用,且确实是它所对应的设备产生了中断时。
另外,也可以使用宏 IRQ_RETVAL(x)。如果 x 为非 0 值,那么该宏返回 IRQ_HANDLED,否则返回前者。
Linux 中的中断处理程序是无需重入的,因为同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。
三. 中断上下文