尽管有些设备仅仅通过控制其寄存器就可以得到控制,但现实中的大部分设备却要比这复杂一些。因为大部分设备的处理时间与处理器不在同一个周期,且一定会比处理器慢的多,这就造成了一种让处理器等待设备的现象,显然这是不行的,而有一种解决方法就是中断操作。
中断仅仅就是一个信号,当硬件需要获得处理器对它的关注时,就可以发送这个信号。 Linux 处理中断的方式非常类似在用户空间处理信号的方式。大多数情况下,一个驱动只需要为它的设备的中断注册一个处理例程,并当中断到来时进行正确的处理。本质上来讲,中断处理例程和其他的代码并行运行。因此,它们不可避免地引起并发问题,并竞争数据结构和硬件。 透彻地理解并发控制技术对中断来讲非常重要。
安装中断处理例程
内核维护了一个中断信号线的注册表,改注册表类似于I/O端口的注册表。模块在使用中断之前要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。
<linux/sched.h>
int request_irq(unsigned long irq,
irqreturn_t (*handler)(int,void *,struct pt_regs *),
unsigned long flags,
const char *dev_name,
void *dev_id);
/*表示注册中断,返回值: 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,但推荐用它指向设备的数据结构。*/
/* flags:
SA_INTERRUPT :快速中断标志。快速中断处理例程运行在当前处理器禁止中断的状态下。
SA_SHIRQ : 在设备间共享中断标志。
SA_SAMPLE_RANDOM :该位表示产生的中断能对 /dev/random 和 /dev/urandom 使用的熵池(entropy pool)有贡献。 读取这些设备会返回真正的随机数,从而有助于应用程序软件选择用于加密的安全密钥。 若设备以真正随机的周期产生中断,就应当设置这个标志。若设备中断是可预测的,这个标志不值得设置。可能被攻击者影响的设备不应当设置这个标志。更多信息 看 drivers/char/random.c 的注释。
*/
中断处理例程可在驱动初始化时或在设备第一次打开时安装。推荐在设备第一次打开、硬件被告知产生中断前时申请中断,因为可以共享有限的中断资源。
void free_irq(unsigned int irq,void *dev_id);
//释放中断
这样调用 free_irq 的位置是设备最后一次被关闭、硬件被告知不用再中断处理器之后。但这种方式的缺点是必须为每个设备维护一个打开计数。
下面的中断申请的示例(并口):
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);
}
}
i386 和 x86_64 体系定义了一个函数来查询一个中断线是否可用:
int can_request_irq(unsigned int irq, unsigned long flags);
/*当能够成功分配给定中断,则返回非零值。但注意,在 can_request_irq 和 request_irq 的调用之间给定中断可能被占用*/
/proc接口
当硬件中断到达处理器时, 内核提供的一个内部计数器会递增,产生的中断报告显示在文件 /proc/interrupts中。这一方法可以用来检查设备是否按预期地工作。此文件只显示当前已安装处理例程的中断的计数。若以前request_irq的一个中断,现在已经free_irq了,那么就不会显示在这个文件中,但是它可以显示终端共享的情况。
/proc/stat记录了几个关于系统活动的底层统计信息, 包括(但不仅限于)自系统启动以来收到的中断数。stat 的每一行以一个字符串开始, 是该行的关键词:intr 标志是中断计数。第一个数是所有中断的总数, 而其他每一个代表一个单独的中断线的计数, 从中断 0 开始(包括当前没有安装处理例程的中断),无法显示终端共享的情况。
以上两个文件的一个不同是:/proc/interrupts几乎不依赖体系,而/proc/stat的字段数依赖内核下的硬件中断,其定义在<asm/irq.h>中。
ARM的定义为:
#define NR_IRQS 128
自动检测IRQ号
驱动初始化时最迫切的问题之一是决定设备要使用的IRQ 线,驱动需要信息来正确安装处理例程。自动检测中断号对驱动的可用性来说是一个基本需求。有时自动探测依赖一些设备具有的默认特性,以下是典型的并口中断探测程序:
if (short_irq < 0) /* 依靠使并口的端口号,确定中断*/
switch(short_base) {
case 0x378: short_irq = 7; break;
case 0x278: short_irq = 2; break;
case 0x3bc: short_irq = 5; break;
}
这段代码根据选定的I/O地址的基地址分配中断号,也允许用户在装载时用下面的命令来覆盖默认值:
insmod xxxxx.ko irq=x
当目标设备有能力告知驱动它要使用的中断号时,自动探测中断号只是意味着探测设备,无需做额外的工作探测中断。
但不是每个设备都对程序员友好,对于他们还是需要一些探测工作。这个工作技术上非常简单: 驱动告知设备产生中断并且观察发生了什么。如果一切顺利,则只有一个中断信号线被激活。尽管探测在理论上简单,但实现可能不简单。
有 2 种方法来进行探测中断:调用内核定义的辅助函数和DIY探测。
内核帮助下的检测
linux提供了一个底层设施来探测中断号。它只能在非共享中断的模式下工作,但是大多数硬件有能力工作在共享中断的模式下,并可提供更好的找到配置中断号的方法,内核提供的这一设施由两个函数组成,在头文件<linux/interrupt.h>中声明:
<linux/interrupt.h>
unsigned long probe_irq_on(void);
/*返回一个未分配中断的位掩码。驱动必须保留返回的位掩码,并在后边传递给probe_irq_off,在调用它之后,驱动程序应当至少安排它的设备产生一次中断*/
int probe_irq_off(unsigned long);
/*在请求设备产生一次中断后,驱动调用这个函数,并将probe_irq_on产生的位掩码作为参数传递给 probe_irq_off,probe_irq_off返回在probe_on之后发生的中断号。如果没有中断发生,返回0,如果产生了多次中断&