说明:版权所有归作者,只供学习交流,若有其它用途请联系作者,转载请遵守IT人职业规范,请注明转载地址
第十章:中断处理
1,(为什么需要中断)
(1)外设的处理速度一般慢于CPU
(2)CPU不能一直等待外部事件
所以设备必须有一种方法来通知CPU它的工作进度,这种方法就是中断。
2,(中断信号线的申请)中断信号线是非常珍贵的资源,尤其是在系统只有15根或16根中断信号线时更是如此。内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表。模块在使用中断前要先申请一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
#include
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int ,void *,struct pt_regs),
unsigned long flags,const char *dev_name, void *dev_id)
void free_irq(unsigned int irq, void *dev_id);
通常,从request_irq函数返回给请求函数的值为0时表示申请成功,为负值时表示错误码。函数返回-EBUSY表示已经有另一个驱动程序占用了你要申请的中断信号线。这些函数参数如下:
unsigned int irq:要申请的中断号。
Irqreturn_t(*handler)(int, void *, struct pt_regs *)
要安装的中断处理函数指针。中断号(int irq)是很有用的。第二个参数void *dev_id是一种客户数据类型(即驱动程序可用的私有数据)。传递给request_irq函数的void *参数会在中断发生时作为参数被传回处理例程。最后一个参数struct pt_regs *regs很少使用,它保存了处理器进入中断代码之前的处理器上下文快照。该寄存器可被用来监视和调试,对一般的设备驱动程序任务来说通常不是必需的。
Const char *dev_name:
传递给request_irq的字符串,用来在/proc/interrupts中显示中断的拥有者。
Void *dev_id:
这个指针用于共享的中断信号线。它是唯一的标示符,在中断信号线空闲时可以使用它,驱动程序也可以使用它指向驱动程序自己的私有数据区(用来识别哪个设备产生中断)。在没有强制使用共享方式时,dev_id可以被设置为NULL,总之用它来指向设备的数据结构是一个比较好的思路。通常,我们会为dev_id传递一个指向自己设备的数据结构的指针,这样,一个管理若干同样设备的驱动程序在中断处理例程中不需要做任何额外的代码,就可以找出哪个设备产生了当前的中断事件。
可以在flags中设置的位如下所示:
SA_INTERRUPT:
当该位被设置时,表明这是一个“快速”的中断处理例程。快速处理例程运行在中断的禁用状态下。
SA_SHIRQ:
该位表示中断可以在设备之间共享。
SA_SAMPLE_RANDOM
该位指出产生的中断能对/dev/random设备和/dev/urandom设备使用的熵池有贡献。从这些设备读取,将会返回真正的随机数,从而有助于应用软件选择用于加密的安全密钥。这些随机数是从一个熵中得到的,各种随机事件都会对该熵池做出贡献,如果你的设备以真正随机的周期产生中断,就应该设置该标志位。另一方面,如果中断是可预期的,就不值得设置这个标志位,因为它对系统的熵没有任何贡献。能受到攻击者影响的设备不应该设置该位,例如,网络驱动程序会被外部的事件影响到预定的数据包的时间周期,因而也不会对熵池有贡献。
说明:调用request_irq的正确位置应该是在设备第一次打开、硬件被告知产生中断之前。调用free_irq的位置是最后一次关闭设备、硬件被告知不用再中断处理器之后。这种技术的缺点是必须为每个设备维护一个打开计数,这样我们才知道什么时候可以禁用中断。虽然在模块的初始化函数中安装中断处理例程看起来是个好主意,但实际上并非如此。因为中断信号线的数量是非常有限的,我们不想肆意浪费。计算机拥有的设备通常要比中断信号线多得多,如果一个模块在初始化时请求了IRQ,那么即使驱动程序只是占用它而从未使用,也将会阻止任意一个其他驱动程序使用该中断。而在设备打开的时候申请中断,则可以共享这些有限的资源。
3,(快速中断和慢速中断)快速中断是那些可以很快被处理的中断,而处理慢速中断则会明显花费更长的时间。当慢速中断正被处理时,慢速中断要求处理器可以再次启用中断。否则,需要快速处理的任务可能会被延迟过长。
在现代内核中,很多快速中断和慢速中断的区别已经消失了。剩下的只有一个:快速中断(使用SA_INTERRUPT标志申请的中断)执行时,当前处理器上的其他中断都被禁止。注意,其他的处理器仍然可以处理中断,尽管从来不会看到两个处理器同时处理同一IRQ的情况。
那么,我们的驱动程序应该使用哪种中断处理例程呢?在现代系统中,SA_INTERRUPT只是在少数几种特殊情况(例如定时器中断)下使用。我们不应该使用SA_INTERRUPT标志,除非有足够必要的理由想要在其他中断被禁用的时候运行自己的中断处理例程。
4,(中断处理程序和普通的C代码之间的区别)中断处理程序就是普通的C代码。特别之处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:
(1)不能向用户空间发送或接受数据,因为它不是在进程上下文中执行的。
(2)不能使用可能引起阻塞的函数
(3)不能使用可能引起调度的函数
5,(启用和禁止单个中断)有时(但很少),驱动程序需要禁止某个特定中断线的中断产生。为此,内核提供了三个函数,这些函数均在
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
调用这些函数中的任何一个都会更新可编程中断控制器中指定的中断掩码,因而就可以在所有的处理器上禁用或者启用IRQ。对这些函数的调用是可以嵌套的——如果disable_irq被成功调用两次,那么在IRQ真正重新启用前,则需要执行两次enable_irq调用。从一个中断处理例程中调用这些函数是可以的,但是在处理某个IRQ时再打开它并不是一个好习惯。
disable_irq不但会禁止给定的中断,而且也会等待当前正在执行的中断处理例程完成。要明白的是,如果调用disable_irq的线程拥有任何中断处理例程需要的资源(比如自旋锁),则系统会死锁。和disable_irq不同,disable_irq_nosync是立即返回的。因此使用后者将会更快,但是可能会让你的驱动程序处于竞态。
说明:我们不能禁用共享的中断线,在现代系统上,中断共享是很常见的。如果我们使用disable_irq禁用共享中断线,共享中断线上的其他设备将同样无法使用中断,也就是无法正常工作了。
6,(禁用所有的中断)如果要禁用所有的中断该怎么办?在2.6内核中,可通过下面两个函数之一关闭当前处理器上的所有中断,这两个函数定义在
void local_irq_save(unsigned long flags);
void local_irq_disable(void);
对local_irq_save的调用将把当前中断状态保存到flags中,然后禁用当前处理器上的中断发送。注意,flags被直接传递,而不是通过指针传递。local_irq_disable不保存状态而关闭处理器上的中断发送;只有我们知道中断并未在其他地方被禁用的情况下,才能使用这个版本。
可通过如下函数打开中断:
Void local_irq_restore(unsignedlong flags);
Void local_irq_enable(void);
第一个版本会将local_irq_save保存的flags状态值恢复,而local_irq_enable无条件打开中断。与disable_irq不同,local_irq_disable不会维护对多次的调用的跟踪。如果调用链中的多个函数需要禁用中断,则应该使用local_irq_save。