如前所述,在256个中断向量中,除了32个分配给异常外,还有224个作为中断向量。对于每个IRQ,Linux都用一个irq_desc_t数据结构来描述,我们把它叫做IRQ描述符,224个IRQ形成一个数组irq_desc[],其定义在/include/linux/irq.h中:
/*
* This
is the "IRQ descriptor", which contains various
information
* about the irq, including what kind of hardware handling it
has,
* whether it is disabled etc etc.
*
* Pad
this out to 32 bytes for cache and indexing reasons.
*/
typedef struct {
unsigned int status;
/* IRQ status */
hw_irq_controller *handler;
struct irqaction *action; /* IRQ action list */
unsigned int depth;
/* nested irq disables */
spinlock_t lock;
} ____cacheline_aligned irq_desc_t;
extern irq_desc_t irq_desc
[NR_IRQS];
编码作者对这个数据结构给出了一定的解释,“____cacheline_aligned”表示这个数据结构的存放按32字节(高速缓存行的大小)进行对齐,以便于将来存放在高速缓存并容易存取。下面对这个数据结构的各个域给予描述:
status
描述IRQ中断线状态的一组标志(在irq.h中定义),其具体含义及应用将在do_IRQ()函数中介绍:
handler
指向hw_interrupt_type描述符,这个描述符是对中断控制器的描述,下面会给出具体解释。
action
指向一个单向链表的指针,这个链表就是对中断服务例程进行描述的irqaction结构,后面将给予具体描述。
depth
如果启用这条IRQ中断线,depth则为0,如果禁用这条IRQ中断线不止一次,则为一个正数。每当调用一次disable_irq( ),该函数就对这个域的值加1;如果depth等于0,该函数就禁用这条IRQ中断线。相反,每当调用enable_irq( )函数时,该函数就对这个域的值减1;如果depth变为0,该函数就启用这条IRQ中断线。
1.IRQ描述符的初始化
在系统初始化期间,init_ISA_irqs()函数对IRQ数据结构(或叫描述符)的域进行初始化(参见i8258.c):
for (i = 0; i < NR_IRQS; i++) {
irq_desc[i].status = IRQ_DISABLED;
irq_desc[i].action = 0;
irq_desc[i].depth = 1;
if (i < 16) {
/*
* 16 old-style INTA-cycle interrupts:
*/
irq_desc[i].handler = &i
} else {
/*
* 'high' PCI IRQs filled in on demand
*/
irq_desc[i].handler = &no_irq_type;
}
}
从这段程序可以看出,初始化时,让所有的中断线都处于禁用状态;每条中断线上还没有任何中断服务例程(action为0);因为中断线被禁用,因此depth为1;对中断控制器的描述分为两种情况,一种就是通常所说的
然后,更新中断描述符表IDT,如
2.中断控制器描述符hw_interrupt_type
这个描述符包含一组指针,指向与特定中断控制器电路(PIC)打交道的低级I/O例程,定义如下:
/*
* Interrupt controller descriptor. This is all we need
* to describe about the low-level hardware.
*/
struct hw_interrupt_type {
const char * typename;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*end)(unsigned int irq);
void (*set_affinity)(unsigned int irq, unsigned long mask);
};
typedef struct hw_interrupt_type hw_irq_controller;
Linux除了支持本章前面已提到的
struct hw_interrupt_type i
"XT-PIC",
startup_
shutdown_
do_
enable_
disable_
};
在这个结构中的第一个域“XT-PIC”是一个名字。接下来,
3.中断服务例程描述符irqaction
在IRQ描述符中我们看到指针action的结构为irqaction,它是为多个设备能共享一条中断线而设置的一个数据结构。在include/linux/interrupt.h中定义如下:
struct irqaction {
void (*handler)(int, void *, struct pt_regs *);
unsigned long flags;
unsigned long mask;
const char *name;
void *dev_id;
struct irqaction *next;
};
这个描述符包含下列域。
handler
指向一个具体I/O设备的中断服务例程。这是允许多个设备共享同一中断线的关键域。
flags
用一组标志描述中断线与I/O设备之间的关系。
SA_INTERRUPT
中断处理程序必须以禁用中断来执行
SA_SHIRQ
该设备允许其中断线与其他设备共享。
SA_SAMPLE_RANDOM
可以把这个设备看作是随机事件发生源;因此,内核可以用它做随机数产生器。(用户可以从/dev/random 和/dev/urandom设备文件中取得随机数而访问这种特征)
SA_PROBE
内核在执行硬件设备探测时正在使用这条中断线。
name
I/O设备名(通过读取/proc/interrupts文件,可以看到,在列出中断号时也显示设备名。)
dev_id
指定I/O设备的主设备号和次设备号。
next
指向irqaction描述符链表的下一个元素。共享同一中断线的每个硬件设备都有其对应的中断服务例程,链表中的每个元素就是对相应设备及中断服务例程的描述。
4.中断服务例程
我们这里提到的中断服务例程(Interrupt Service Routine)与以前所提到的中断处理程序(Interrupt
handler)是不同的概念。具体来说,中断处理程序相当于某个中断向量的总处理程序,例如IRQ0x05_interrupt(),是中断号5(向量为37)的总处理程序,如果这个5号中断由网卡和图形卡共享,则网卡和图形卡分别有其相应的中断服务例程。每个中断服务例程都有相同的参数:
IRQ:中断号;
dev_id: 设备标识符,其类型为void*;
regs: 指向内核堆栈区的指针,堆栈中存放的是中断发生后所保存的寄存器,有关pt_regs结构的具体内容将在后面介绍。
在实际中,大多数中断服务例程并不使用这些参数。
在IDT表初始化完成之初,每个中断服务队列还为空。此时,即使打开中断且某个外设中断真的发生了,也得不到实际的服务。因为CPU虽然通过中断门进入了某个中断向量的总处理程序,例如IRQ0x05_interrupt(),但是,具体的中断服务例程(如图形卡的)还没有挂入中断请求队列。因此,在设备驱动程序的初始化阶段,必须通过request_irq()函数将对相应的中断服务例程挂入中断请求队列。
request_irq()函数的代码在/arch/i386/kernel/irq.c中:
/*
* request_irq - allocate
an interrupt line
* @irq: Interrupt
line to allocate
* @handler:
Function to be called when the IRQ occurs
* @irqflags:
Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A
cookie passed back to the handler function
*
* This call
allocates interrupt resources and enables the
* interrupt line
and IRQ handling. From the point this
* call is made your handler function may be invoked. Since
* your handler function must clear any interrupt the board
* raises, you must take care both to initialise your hardware
* and to set up the interrupt handler in the right order.
*
* Dev_id must be
globally unique. Normally the address of the
* device
data structure is used as the cookie. Since the handler
* receives this
value it makes sense to use it.
*
* If your
interrupt is shared you must pass a non NULL dev_id
* as this is required when freeing the interrupt.
*
* Flags:
*
* SA_SHIRQ
Interrupt is shared
*
*
SA_INTERRUPT
Disable local interrupts while processing
*
*
SA_SAMPLE_RANDOM The
interrupt can be used for entropy
*
*/
int
request_irq(unsigned int irq,
void (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
{
int retval;
struct irqaction * action;
#if 1
/*
* Sanity-check: shared interrupts should REALLY pass in
* a real dev-ID, otherwise we'll have trouble
later trying
* to figure out which interrupt is which
(messes up the
* interrupt freeing logic etc).
*/
if (irqflags & SA_SHIRQ) {
if (!dev_id)
printk("Bad boy: %s (at 0x%x) called us
without a dev_id!\n", devname, (&irq)[-1]);
}
#endif
if (irq >= NR_IRQS)
return -EINVAL;
if (!handler)
return -EINVAL;
action = (struct irqaction *)
kmalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->flags = irqflags;
action->mask = 0;
action->name = devname; 对action进行初始化
action->next = NULL;
action->dev_id = dev_id;
retval = setup_irq(irq, action);
if (retval)
kfree(action);
return retval;
}
编码作者对此函数给出了比较详细的描述。其中主要语句就是对setup_irq()函数的调用,该函数才是真正对中断请求队列进行初始化的函数(有所简化):
int
setup_irq(unsigned int irq, struct irqaction * new)
{
int shared = 0;
unsigned long flags;
struct irqaction *old, **p;
irq_desc_t *desc = irq_desc + irq; /*获得irq的描述符*/
/* 对中断请求队列的操作必须在临界区中进行 */
spin_lock_irqsave(&desc->lock,flags); /*进入临界区*/
p =
&desc->action; /*让p 指向irq描述符的action域,即irqaction链表的首部*/
if ((old = *p) != NULL) { /*如果这个链表不为空*/
/* Can't share interrupts unless both agree to
*/
if (!(old->flags & new->flags &
SA_SHIRQ)) {
spin_unlock_irqrestore(&desc->lock,flags);
return -EBUSY;
}
/* 把新的中断服务例程加入到irq中断请求队列*/
do {
p
= &old->next;
old = *p;
} while (old);
shared = 1;
}
*p =
new;
if (!shared) { /*如果irq不被共享 */
desc->depth = 0;
/*启用这条irq线*/
desc->status &= ~(IRQ_DISABLED |
IRQ_AUTODETECT | IRQ_WAITING);
desc->handler->startup(irq); /*即调用startup_
}
spin_unlock_irqrestore(&desc->lock,flags); /*退出临界区*/
register_irq_proc(irq); /*在proc文件系统中显示irq的信息*/
return 0;
}
下面我们举例说明对这两个函数的使用:
1.对register_irq()函数的使用:
在驱动程序初始化或者在设备第一次打开时,首先要调用该函数,以申请使用该irq。其中参数handler指的是要挂入到中断请求队列中的中断服务例程。假定一个程序要对/dev/fd0/(第一个软盘对应的设备)设备进行访问,有两种方式,一是直接访问/dev/fd0/,另一种是在系统上安装一个文件系统,我们这里假定采用第一种。通常将IRQ6分配给软盘控制器,给定这个中断号6,软盘驱动程序就可以发出下列请求,以将其中断服务例程挂入中断请求队列:
request_irq(6, floppy_interrupt,
SA_INTERRUPT|SA_SAMPLE_RANDOM, "floppy", NULL);
我们可以看到,floppy_interrupt()中断服务例程运行时必须禁用中断(设置了SA_INTERRUPT标志),并且不允许共享这个IRQ(清SA_SHIRQ标志)。
在关闭设备时,必须通过调用free_irq()函数释放所申请的中断请求号。例如,当软盘操作终止时(或者终止对/dev/fd0/的I/O操作,或者卸载这个文件系统),驱动程序就放弃这个中断号:
free_irq(6, NULL);
2.对setup_ irq()函数的使用
在系统初始化阶段,内核为了初始化时钟中断设备irq0描述符,在time_init( )函数中使用了下面的语句:
struct irqaction irq0 =
{timer_interrupt, SA_INTERRUPT, 0, "timer", NULL,};
setup_irq(0, &irq0);
首先,初始化类型为irqaction的irq0变量,把handler域设置成timer_interrupt( )函数的地址,flags域设置成SA_INTERRUPT,name域设置成"timer",最后一个域设置成NULL以表示没有用dev_id值。接下来,内核调用setup_x86_irq( ),把irq0插入到IRQ0的中断请求队列:
类似地,内核初始化与IRQ2和IRQ13相关的irqaction描述符,并把它们插入到相应的请求队列中,在 init_IRQ( )函数中有下面的语句:
struct irqaction irq2 =
{no_action, 0, 0, "cascade", NULL,};
struct irqaction irq13 =
{ math_error_irq, 0, 0, "fpu", NULL,};
setup_x86_irq(2, &irq2);
setup_x86_irq(13, &irq13);