系列文章目录
Linux 内核设计与实现
深入理解 Linux 内核(一)
深入理解 Linux 内核(二)
Linux 设备驱动程序(一)
Linux 设备驱动程序(二)
Linux设备驱动开发详解
文章目录
- 系列文章目录
- 十、中断处理
- 1、安装中断处理例程
十、中断处理
1、安装中断处理例程
如果读者确实想 “看到” 产生的中断,那么仅仅通过向硬件设备写入是不够的,还必须要在系统中安装一个软件处理例程。如果没有通知 Linux 内核等待用户的中断,那么内核只会简单应答并忽略该中断。
中断信号线是非常珍贵且有限的资源,尤其是在系统上只有 15 根或 16 根中断信号线时更是如此。内核维护了一个中断信号线的注册表,该注册表类似于 I/O 端口的注册表。模块在在使用中断前要先请求一个中断通道(或者中断请求 IRQ),然后在使用后释放该通道。我们将会在后面看到,在很多场合下,模块也希望可以和其他的驱动程序共享中断信号线。下列在头文件 <linux/sched.h> 中声明的函数实现了该接口:
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 *)
这是要安装的中断处理函数指针。我们会在本章的后面部分讨论这个函数的参数含义。
unsigned long flags
如读者所想,这是一个与中断管理有关的位掩码选项(将在后面描述)。
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 设备使用的熵池
(entropy pool)有贡献。从这些设备读取,将会返回真正的随机数,从而有助于应
用软件选择用于加密的安全密钥。这些随机数是从一个熵池中得到的,各种随机事
件都会对该熵池作出贡献,如果读者的设备以真正随机的周期产生中断,就应该设
置该标志位。另一方面,如果中断是可预期的(列如,帧捕捉卡的垂直消隐),就
不值得设置这个标志位 —— 它对系统的熵没有任何贡献。能受到攻击者影响的设
备不应该设置该位,例如,网络驱动程序会被外部的事件影响到预定的数据包的时
间周期,因而也不会对熵池有贡献,更详细的信息请参见 drivers/char/random.c 文
件中的注释。
中断处理例程可在驱动程序初始化时或者设备第一次打开时安装。虽然在模块的初始化函数中安装中断处理例程看起来是个好主意,但实际上并非如此。因为中断信号线的数量是非常有限的,我们不想肆意浪费。计算机拥有的设备通常要比中断信号线多得多,如果一个模块在初始化时请求了 IRQ,那么即使驱动程序只是占用它而从未使用,也将会阻止任意一个其他的驱动程序使用该中断。而在设备打开的时候申请中断,则可以共享这些有限的资源。
这种情况很可能出现,例如,在运行一个与调制解调器共用同一中断的帧捕捉卡驱动程序时,只要不同时使用这两个设备就可以共享同一中断。用户在系统启动时装载特殊的设备模块是一种普遍做法,即使该设备很少使用。数据捕捉卡可能会和第二个串口使用相同的中断,我们可以在捕获数据时,避免使用调制解调器连接到互联网服务供应商(ISP),但是如果为了使用调制解调器而不得不卸载一个模块,总是令人不快的。
调用 request_irq 的正确位置应该是在设备第一次打开、硬件被告知产生中断之前。调用 free_irq 的位置是最后一次关闭设备、硬件被告知不用再中断处理器之后。这种技术的缺点是必须为每个设备维护一个打开计数,这样我们才能知道什么时候可以禁用中断。
尽管我们已经讨论了不应该在装载模块时调用 request_irq,但 short 模块还是在装载时请求了它的中断信号线,这样做的方便之处是,我们可以直接运行测试程序,而不需要额外运行其他的进程来保持设备的打开状态。因此,short 在它自己的初始化函数(short_init)中请求中断,而不是像真正的设备驱动那样在 short_open 中请求中断。
下面这段代码要请求的中断是 short_irq,对这个变量的实际赋值操作(例如,决定使用哪个 IRQ)会在后面给出,因为它与当前的讨论无关。short_base 是并口使用的 I/O 地址空间的基地址;向并口的 2 号寄存器写入,可以启用中断报告。
if (short_irq >= 0)
result = request_irq(short_irq, short_interrupt, SA_INTERRUPT, "short", NULL);
if (result) {
printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq);
short_irq = -1;
} else {
/* 真正启用中断 —— 假定这是一个并口 */
outb(0x10, short_base + 2);
}
}