(一):中断
中断本质上是一种特殊的电信号,由硬件设备发向处理器。处理器在接收到中断后,会马上向操作系统反映此信号的到来,然后就u由操作系统来处理这些新到来的数据。不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标志。这些中断值被称为中断请求线(IRQ)。中断是随时随地发生的,也就是说中断并不考虑与处理器的时钟同步。
异常:异常的产生必须与处理器时钟同步,异常也被成为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况,必须靠内核来处理的时候,处理器就会产生一个异常。中断是由硬件引起的,异常是由于软件引起的。
(二):中断处理程序
在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程。产生中断的每个设备都有一个相应的中断处理程序。中断处理程序与其他内核函数的真正区别在于,中断处理程序是被内核调用来响应中端的,而他们运行在我们称之为中断上下文的特殊上下文中,中断上下文也被成为原子上下文,该上下文执行的代码不可阻塞。
(三):上半部与下半部的对比
由于中断处理程序既需要运行的快,又需要完成的工作量多,所以将终端处理切分为两部分。中断处理程序是上半部,一旦接收到一个中断,他就立即开始执行,但只做有严格时限的工作。能够被允许稍后执行的工作会推迟到下半部去。
(四):注册中断处理程序
中断处理程序是管理硬件的驱动程序的组成部分。每一个设备都有相关的驱动程序,如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序。
驱动程序可以通过request_irp()函数注册一个中断处理程序,该函数被定义在linux/interrupt.h文件中,并且激活给定的中断线,以处理中断。
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
第一个参数表示要分配的中断号。对于大多数其他设备来说,中断号要么可以通过探测获取,要么可以通过编程动态确定。
第二个参数handler是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统一接收到中断,该函数就被调用。
handler函数的原型。
typedef irqreturn_t (*irq_handler_t)(int, void *);
1:中断处理程序标志
第三个参数flags可以为0,也可能是下列一个或多个标志的位掩码。定义在linux/interrupt.h文件中。下面列举一下几个比较重要的标志:
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
IRQF_DISABLE - 该标志被设置后,意味着内核在处理中断处理程序本身期间,要禁止所有的其他中断。如果不设置,中断处理程序可以与除本身以外的其他任何中断同时运行。
IRQF_SAMPLE_RANDOM - 此标志表明这个设备产生的中断对内核熵池(entroy pool)有贡献。内核熵池负责提供从各种随机事件导出真正的随机数。如果指定了该标志,那么来自该设备的中断间隔时间就会作为熵填充到熵池。
IRQF_TIMER - 该标志是特别为系统定时器的中断处理准备的。
IRQF_SHARED - 该标志表明可以在多个中断处理程序之间共享中断线。在同一个中断线上注册的每个处理程序必须指定这个标志,否则,在每条线上只能有一个处理程序。
第四个参数name是与中断相关的设备的ASCII文本表示
第五个参数dev用于共享中断线。当一个中断处理程序需要释放的时候,dev将提供唯一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那一个。
request_irq()成功执行会返回0,如果返回非0值,表示有错误发生。其中最常见的错误是-EBUSY,他表示给定的中断线已经在使用。
注意request_irq()函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码调用该函数。
2:一个中断的例子
在一个驱动程序中请求一个中断线,并在通过request_irq()安装中断处理程序:
request_irq();
if(request_irq(irqn,my_interrupt,IRQF_SHARED,"my_device",my_dev)){
printk(KERN_ERR "my_device: cannot register IRQ %d
",irqn);
return -EIO;
}
在编写中断处理函数的时候,初始化硬件和注册中断处理程序的顺序必须正确,以防止中断处理程序在设别初始化之前就开始执行。
3:释放中断处理程序
卸载驱动程序的时候,需要注销中断处理程序,并释放中断线,上述动作需要调用:
void free_irq(unsigned int irq,void *dev)
如果指定的中断线不是共享的,那么该函数删除处理程序的同时,禁用这条中断线.如果中断线是共享的,则仅仅删除dev所对应的中断处理程序,而中断线本身只有在删除了最后一个处理程序时才会被禁用.
(五):编写中断处理程序
一下是一个中断处理程序的声明:
static irqreturn_t intr_handler(int irq,void *dev);
注意,他的类型与request_irq()参数中handler所要求的参数类型相匹配.第一个参数irq就是处理程序要相应的中断的中断号.
第二个参数dev是一个通用指针,他与在中断处理程序注册时传递给request_irq()的参数dev必须一致.
中断处理程序的返回值是一个特殊类型:irqreturn_t.中断处理程序可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED.当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE.当中断处理程序被正确调用,并且确实是他对应的设备产生了中断的时候,返回IRQ_HANDLED
注意:
Linux中的中断处理程序是无须重入的.当一个给定的中断处理程序正在执行时,相应的中断线在所有的处理器上都会被屏蔽掉.以防止在同一个中断线上接收另一个新的中断.由此可见,同一个中断处理程序绝对不会被同时调用以处理嵌套中断.
1:共享的中断处理程序
共享的中断处理程序和非共享的中断处理程序有一下几个差异:
1):request_irq()的参数flags必须设置为IRQF_SHARED标志
2):对于每一个注册的中断处理程序来说,dev参数必须唯一.指向任一设备结构的指针就可以满足这一要求.不能给中断处理程序传递 NULL值.
3):中断处理程序必须能够区分他的设备是否真正的产生了中断.这既需要硬件的支持,也需要处理程序中有相关的处理逻辑.
还有,指定IRQF_SHARED标志以调用 request_irq()的时候,只有在一下两种情况下才可能成功:中断线当前未被注册,或者在该线上的所有以注册处理程序都指定了IRQF_SHARED.
2:中断处理程序实例
下面我们看一下RTC(real-time clock)的中断处理程序.该程序位于drivers/char/rtc.c 文件中.该设备用于系统时钟,提供报警器或周期性的定时器.
首先,在rtc初始化的时候注册中断处理程序.我们来看一下.
函数rtc_init(void):
/* 对rtc_irq 注册 rtc_interrupt */
if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc",
(void *)&rtc_port)) {
rtc_has_irq = 0;
printk(KERN_ERR "rtc: cannot register IRQ %d
", rtc_irq);
return -EIO;
}
从中我们看出,中断号由rtc_irq提供.这个变量用于为给定体系结构指定RTC中断.第二个参数是我们的中断处理程序rtc_interrupt—他将于其他中断处理程序共享中断线,因为他设置了IRQF_SHARED标志.第四个参数,可以得出驱动程序的名称为”rtc”,因为这个设备允许共享中断线,所以他给dev型参传递了一个面向每个设备的实参值.
下面我们看一下具体的中断处理函数:
#ifdef RTC_IRQ
/*
* A very tiny interrupt handler. It runs with IRQF_DISABLED set,
* but there is possibility of conflicting with the set_rtc_mmss()
* call (the rtc irq and the timer irq can easily run at the same
* time in two different CPUs). So we need to serialize
* accesses to the chip with the rtc_lock spinlock that each
* architecture should implement in the timer code.
* (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.)
*
* 一个非常轻型的中断处理函数.他是和IRQF_DISABLED集一起运行的,
* 但是很有可能和set_rtc_mmss()调用发生冲突(rtc 中断和timer中断很容易
* 同时在两个不同的CPU上运行).所以我们需要使用rtc_lock自旋锁来序列化
* 对芯片的访问,使得每一个架构都应该在timer代码中实现.
*
*/
static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
/*
* Can be an alarm interrupt, update complete interrupt,
* or a periodic interrupt. We store the status in the
* low byte and the number of interrupts received since
* the last read in the remainder of rtc_irq_data.
*
* 可以是alarm报警中断,更新完成的中断,或者是周期性中断.
* 我们把这些状态保存在rtc_irq_data的低字节中,而把最后一次读取的中断
* 号保存到rtc_irq_data的其他字节中.
*/
//自旋锁
spin_lock(&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
if (is_hpet_enabled()) {
/*
* In this case it is HPET RTC interrupt handler
* calling us, with the interrupt information
* passed as arg1, instead of irq.
*
* 在这种情况下,是HPET RTC中断处理函数调用我们,
* 伴随着是中断信息作为参数1而不是irq
*/
rtc_irq_data |= (unsigned long)irq & 0xF0;
} else {
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
}
if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
spin_unlock(&rtc_lock);
/* Now do the rest of the actions */
/* 现在执行其他的操作 */
spin_lock(&rtc_task_lock);
if (rtc_callback)
rtc_callback->func(rtc_callback->private_data);
spin_unlock(&rtc_task_lock);
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
#endif
只要计算机一接收到RTC中断,就会调用这个函数.首先要注意的是使用了自旋锁–第一次调用是为了保证rtc_irq_data不会被SMP机器上的其他处理器同时访问,第二次调用是为了避免rtc_callback出现相同的情况.
程序后面会执行一个回调函数,RTC驱动程序允许注册一个回调函数,并在每个RTC中断到来时执行.
最后返回IRQ_HANDLED.