尽管device的IO region可以用来控制device,但是还不够。一个device,往往会有外界有交互,当外界发生了某个事件,需要device做出某种响应,driver也需要做处理。CPU不可能一直等device的某个event,而应该在event发生时,由device通知CPU。因此那就要有一种机制,让device能够通知到driver,这就是中断的作用。
所谓中断,就是硬件在需要CPU做出反应的时候发出的signal,kernel处理硬件中断的方式方法和user space处理signal是类似的。
driver能做的,其实就是实现一个interrupt handler,并注册给kernel,当device中断产生时,handler能够正确处理这个中断就可以了。在讲中断之前,需要强调一点,interrupt handler是和其他的code并发运行的,因此handler里一定要考虑竞争和并发的问题。
10.1. Preparing the Parallel Port
准备并口设备这里不讲了,因为没有并口设备。
10.2. Installing an Interrupt Handler
如果想在device driver中接收并处理设备产生的中断,得需要device driver告诉kernel怎么把中断传递给driver。如果没有匹配的handler,设备的中断会被忽略。
中断线(interrupt line)是kernel里很宝贵又很稀有的资源,一共只有15/16个,driver在使用中断之前要申请中断线资源,使用完后释放。在很多情况下,kernel中有些module driver可能要与别的drivershare同样的中断线。
获取中断资源的函数:
<linux/interrupt.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,表明成功,如果返回负值,说明获取irq失败。下面是参数的说明:
unsigned int irq : driver请求的设备中断号
irqreturn_t (*handler)(int, void *, struct pt_regs *) : 中断发生时用来处理中断的handler
unsigned long flags : 和中断管理相关的一些bitmask,包含SA_INTERRUPT,SA_SHIRQ,SA_SAMPLE_RANDOM,在下面会详细说明。
const char *dev_name : 中断的owner,设备名字,在/proc/interrupts下会显示。
void *dev_id : 对于shared interrupt line有用。一般是device的structure,或者一些private data,用来表明是谁的中断。如果不是shared interrupt line,可以设为NULL。
关于参数flags,有SA_INTERRUPT,SA_SHIRQ,SA_SAMPLE_RANDOM这几种:
SA_INTERRUPT: 如果设这个flag,说明是一个Fast的handler,Fast的handler运行的时候会关闭当前CPU的中断。
SA_SHIRQ:如果设这个flag,说明中断可以在设备之间share。
SA_SAMPLE_RANDOM: 如果你的device产生的中断比较随机,那么可以设这个flag,这样的话,OS上层需要产生随机数的时候,就可以把你的中断作为参考因素之一。
interrupt的handler可以在module driver初始化的时候注册,也可以在device被open的时候注册。LDD3推荐后者,也就是在device被open的时候才注册,因为中断线是有限的资源,有些device driver初始化的时候注册,但是后续可能并不会使用,这就造成了浪费,因此推荐在driver被open的时候才去请求中断,并注册handler。
一般来说,在device被open,但是还没有让device产生中断之前request_irq,在所有的device都被close之后再free_irq,这种方式的缺点在于,需要device driver自己保存device的计数,来确定什么时候关闭了所有的device。
一个使用request_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 { /* actually enable it -- assume this *is* a parallel port */
outb(0x10,short_base+2);
}
}
有些架构比如X86,有一个query irq是否可用的接口:
int can_request_irq(unsigned int irq, unsigned long flags);
如果irq当前没有人使用,就返回非零值。
10.2.1. The /proc Interface
每当hardware产生中断,并被CPU接收时,kernel就会把中断计数加1,这个计数可以用来check hardware是否正常工作。中断的计数可以通过/proc/interrupts来读取。另外,/proc/stat也可以出去interrupt的数量,区别在于stat里是自系统起来获取的中断数;而/proc/interrupts只统计当前active的中断数。比如这里有一个读取中断计数的例子:
root@montalcino:/bike/corbet/write/ldd3/src/short# m /proc/interrupts
CPU0 CPU1
0: 4848108 34 IO-APIC-edge timer
2: 0 0 XT-PIC cascade
8: 3 1 IO-APIC-edge rtc
10: 4335 1 IO-APIC-level aic7xxx
11: 8903 0 IO-APIC-level uhci_hcd
12: 49 1 IO-APIC-edge i8042
NMI: 0 0
LOC: 4848187 4848186
ERR: 0
MIS: 0
第一类是中断数IRQ number。从上面可以看出,CPU0一直在处理中断,而CPU1就很少,因为在同一个CPU上处理中断容易缓存命中,有助于提高performance。最后两列,第一个是interrupt controller,以及注册这个中断的设备名字。
从/proc/stat读取的中断信息:
intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0
第一个值表示所有中断的总和,后面的每一个都代表一个interrupt line(从0开始)。这里统计的是所有曾经产生的中断,即便你的device被关闭了,这里仍然可以看到它之前产生的中断,因此有时候,/proc/stat里的信息要比/proc/interrupts里的更有参考意义。
这两个文件的 另一个区别是:interrupts是与架构无关的,而stat是架构相关的。系统中可用的IRQ line根据架构的不同而不同,比如SPARC上只有16个IRQ line,而在IA-64上则有256个IRQ line。
下面这个是在IA-64上的/proc/interrupts的输出实例:
CPU0 CPU1
27: 1705 34141 IO-SAPIC-level qla1280
40: 0 0 SAPIC perfmon
43: 913 6960 IO-SAPIC-level eth0
47: 26722 146 IO-SAPIC-level usb-uhci
64: 3 6 IO-SAPIC-edge ide0
80: 4 2 IO-SAPIC-edge keyboard
89: 0 0 IO-SAPIC-edge PS/2 Mous