中断和中断处理(一)

(一):中断

中断本质上是一种特殊的电信号,由硬件设备发向处理器。处理器在接收到中断后,会马上向操作系统反映此信号的到来,然后就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.

转载于:https://www.cnblogs.com/bobo1223/p/7287556.html

你可能感兴趣的:(中断和中断处理(一))