图 10.2 描述了 Linux 内核的中断处理机制。 为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点, Linux 将中断处理程序分解为两个半部:顶半部(top half )和底半部(bottom half ) 。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行 “登记中断” 的工作。 “登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux设备驱动中的中断处理一定要分两个半部则是不对的。 如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
在 Linux 系统中,查看/proc/interrupts 文件可以获得系统中断的统计信息,如下所示。在单处理器的系统中,第一列是中断号,第二列是向 CPU0 产生该中断的次数,之后的是对于中断的描述。
Linux 中断编程
10.3.1 申请和释放中断
在 Linux 设备驱动中,使用中断的设备需要申请和释放对应的中断,分别使用内核提供的 request_irq()和 free_irq()函数。
1.申请 IRQ
int request_irq(unsigned int irq, void (*handler)(int irq, void *dev_id, struct pt_regs *regs),unsigned long irqflags, const char * devname,void *dev_id);
irq 是要申请的硬件中断号。
handler 是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id 参数将被传递给它。
irqflags 是中断处理的属性,若设置了 SA_INTERRUPT,则表示中断处理程序是快速处理程序, 快速处理程序被调用时屏蔽所有中断, 慢速处理程序不屏蔽; 若设置了 SA_SHIRQ, 则表示多个设备共享中断, dev_id 在中断共享时会用到, 一般设置为这个设备的设备结构体或者 NULL。
request_irq()返回 0 表示成功,返回-INV AL 表示中断号无效或处理函数指针为NULL,返回-EBUSY 表示中断已经被占用且不能共享。
2.释放 IRQ
与 request_irq()向对应的函数为 free_irq(),free_irq()的原型如下:
void free_irq(unsigned int irq,void *dev_id);
free_irq()中参数的定义与 request_irq()相同。
10.3.2 使能和屏蔽中断
下列 3 个函数用于屏蔽一个中断源。
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
disable_irq_nosync()与 disable_irq()的区别在于前者立即返回,而后者等待目前的中断处理完成。注意,这 3 个函数作用于可编程中断控制器,因此,对系统内的所有CPU 都生效。
下列两个函数将屏蔽本 CPU 内的所有中断。
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
前者会将目前的中断状态保留在 flags 中,注意 flags 被直接传递,而不是通过指针传递。后者直接禁止中断。
与上述两个禁止中断对应的恢复中断的方法如下:
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
以上各 local_开头的方法的作用范围是本 CPU 内。
10.3.3 底半部机制
Linux 系统实现底半部的机制主要有 tasklet、工作队列和软中断。