为什么要有中断机制?
计算机有很多的硬件设备,如硬盘、键盘、鼠标等,操作系统需要对这些硬件设备进行管理,然而处理器的速度和外围硬件设备的速度往往不在一个数量级上。如果内核让处理器向硬件发出一个请求,然后专门等待回应,性能会很差。内核应该在此期间处理其他事务,等到硬件真正完成请求的操作后再回头对它进行处理。因此,采用一种机制让硬件在需要的时候向内核发出信号。
1 中断
中断本质就是一种特殊的电信号,由硬件发向处理器。内核随时可能因为新到来的中断被打断。
不同设备对应的中断不同,每个中断都通过唯一的一个数字标识。操作系统根据不同的中断提供不同的中断处理程序。这些中断值通常称为中断请求线(IRQ)。
异常
而谈到中断就会谈到异常,异常与中断的区别在于,异常在产生时必须考虑与处理器时钟同步(称为同步中断)。一般是处理器执行到由于编程失误而导致的错误指令(如除0),或者在执行期间出现特殊情况(如缺页)就会产生一个异常。
2 中断处理程序
在响应特定中断时,内核会执行一个函数,即中断处理程序。中断处理程序通常和特定中断关联。
中断处理程序就是函数,但与其他内核函数的真正区别是,中断处理程序是被内核调用来响应中断的,而它们运行于中断上下文中,该上下文中的代码不可被阻塞。
中断处理程序随时可能执行,所以必须保证其能快速执行。
3 上半部与下半部的对比
中断处理程序运行速度和完成工作量之间的平衡使得将中断处理切为两部分:
- 中断处理程序为上半部:接收到一个中断,立即执行,但只做有严格时限的工作。
- 下半部:将能被允许稍后完成的工作推迟到下半部做。
4 注册中断处理程序
驱动程序通过 request_irp()
函数来注册一个中断处理程序,并且激活给定的中断线以处理中断。
int request_irq(unsigned int irq, // 表示要分配的中断号
irq_handler_t handler, // 指针指向处理这个中断的实际中断处理程序
unsigned long flags, // 中断处理程序标志
// IRQF_DISABLED:禁止所有的其他中断
// IRQF_SAMPLE_RANDOM:这个设备产生的中断对内核熵池有贡献
// IRQF_TIMER:特别为系统定时器的中断处理准备
// IRQF_SHARED:可以在多个中断处理程序之间共享中断线。
const char *name, // 与中断相关的设备的ASCII文本表示
void *dev // 用于共享中断线
)
request_irp()
成功执行返回0,否则错误即指定的中断处理程序不会被注册。
卸载驱动程序时需要注销相应的中断处理程序,并释放中断线。
void free_irq(unsigned int irq, // 表示要释放的中断号
void *dev
)
如果中断线不是共享的,则删除处理程序的同时禁用此中断线;
如果中断线是共享的,则仅删除dev所对应的处理程序。
5 编写中断处理程序
一个中断处理程序声明:
static irqreturn_t intr_handler(int irq, void *dev)
// irq就是这个处理程序要响应的中断的中断号
// dev是一个通用指针,它与在中断处理程序注册时传递给request_irq()的参数dev必须一致
程序返回值的类型为 irqreturn_t
,实际上就是一个 int
型,有两种类型:
-
IRQ_NONE
中断处理程序检测到一个中断,但此中断对应的设备不是在注册处理函数期间指定的产生源 -
IRQ_HANDLED
中断处理程序被正确调用,并且确实是它所对应的设备产生了中断。
6 中断上下文
当执行一个中断处理程序时,内核处于中断上下文。中断上下文不可以睡眠。中断处理程序打断了其他的代码,所以所有中断处理程序必须尽可能迅速、简洁(具有较为严格的时间限制)。
过去中断处理程序没有自己的栈,后来中断处理程序拥有了自己的栈,称为中断栈。
7 中断处理机制的实现
中断从硬件到内核路由如下: