第7章中断和中断处理

7.5 编写中断处理程序

以下是一个中断处理程序声明:

static irqreturn_t intr_handler(int irq, void *dev);

注意,它的类型与request_irq()参数中handler所要求的参数类型相匹配。第一个参数irq是这个处理程序要响应的中断的中断号。在2.0版本以前的Linux内核中,由于没有dev这个参数,必须通过irq才能区分使用相同驱动程序,因而也使用相同的中断处理程序的多个设备。例如,具有多个相同类型硬盘驱动控制器的计算机。

       第二个参数dev是一个通用指针,它在与中断处理程序注册时传递给request_irq()的参数dev必须一致。如果该值有唯一确定性,那么它就相当于一个cookie,可以用来区分共享同一中断处理程序的多个设备。另外dev也可能指向中断处理程序使用的一个数据结构。因为对每个设备而言,设备结构都是唯一的,而且可能在中断处理程序中也用得到,因此,它也通常被看做dev。

中断处理程序的返回值是一个特殊类型:irqreturn_t。中断处理程序可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生了中断时,返回IRQ_HANDLED。另外,也可以使用宏IRQ_RETVAL(val)。如果val为非0值,那么该宏返回IRQ_HANDLED;否则,返回IRQ_NONE。利用这些特殊的值,内核可以知道设备发出的是否是一种虚假的中断。如果给定中断线上所有中断处理程序返回的都是IRQ_NONE,那么,内核就可以检测到出了问题。注意,irqreturn_t这个返回类型实际上是一个int型。之所以使用这些特殊值是为了与早期的内核保持兼容——2.6版本之前的内核并不支持这种特性,中断处理程序只需返回void就行了。如果要在2.4或更早的内核上使用这样的驱动程序,只需将typedef irqreturn_t 改为void,屏蔽掉此特性,并给no-ops定义不同的返回值,其他用不着做什么大的修改。中断处理程序通常会标记为static,因为它从来不会被别的文件中的代码直接调用。

中断处理程序扮演什么样的角色要取决于产生中断的设备和该设备为什么要发送中断。即使其他什么工作也不做,绝大部分的中断处理程序至少需要知道产生中断的设备,告诉它已经收到中断了。对于复杂一些的设备,可能还需要在中断处理程序中发送和接收数据,以及执行一些扩充的工作。如前所述,应尽可能将扩充的工作推给下半部处理程序。

重入和中断处理程序

Linux中的中断处理程序是无须重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接收另外一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断线总是被禁止的。可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。这简化了中断处理程序的编写。

7.5.1 共享的中断处理程序

共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有以下三处:

request_irq()的参数flags必须设置IRQF_SHARED标志。

对于每个注册的中断处理程序来说,dev参数必须唯一。指向任一设备结构的指针就可以满足这一要求;通常会选择设备结构,因为它是惟一的,而且中断处理程序可能会用到它。不能给共享的处理程序传递NULL值。

中断处理程序必须能够区分它的设备是否真的产生了中断。这既需要硬件的支持,也需要处理程序中有相关的处理逻辑。如果硬件不支持这一功能,那中断处理程序肯定会束手无策,它根本没法知道到底是与它对应的设备发出了这个中断,还是共享这条中断线的其他设备发出了中断。

所有共享中断线的驱动程序都必须满足以上要求。只要有任何一个设备没有按规则进行共享,那么中断线就无法共享了。指定IRQF_SHARED以调用request_irq()时,只有在以下两种情况下才可能成功:中断线当前未被注册,或者在该线上的所有已注册处理程序都指定了IRQF_SHARED。注意,在这一点上2.6版与以前的内核是不同的,共享的处理程序可以混用IRQF_DISABLED。

内核接收一个中断后,它将依次调用在该中断线上注册的每一个处理程序。因此,一个处理程序必须知道它是否应该为这个中断负责。如果与它相关的设备并没有产生中断,那么处理程序应该立即退出。这需要硬件设备提供状态寄存器,以便中断处理程序进行检查。

7.5.2 中断处理程序实例

考察一个实际的中断处理程序,它来自RTC驱动程序,可以在drivers/char/rtc.c中找到。很多机器包括PC都可以找到RTC。它是一个从系统定时器中独立出来的设备,用于设置系统时钟,提供报警器或周期性的定时器。对大多数体系结构而言,系统时钟的设置,通常只需要向某个特定的寄存器或IO地址写入想要的时间就可以了。然而报警器或周期性定时器通常就得靠中断来实现。这种中断与生活中的闹铃差不多:中断发出时,报警器或定时器就会启动。

RTC驱动程序装载时,rtc_init()函数会被调用,对这个驱动程序进行初始化。它的职责之一就是注册中断处理程序:

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\n", rtc_irq);
        return -EIO;
    }

从中看到,中断号由rtc_irq指定。这个变量用于为给定体系结构指定RTC中断。例如,在PC上,RTC位于IRQ 8。第二个参数是中断处理程序rtc_interrupt——它将与其他中断处理程序共享中断线,因为它设置了IRQF_SHARED标志。由第四个参数看出,驱动程序的名称为"rtc"。

最后是中断处理程序本身:

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.
     */

    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.
         */
        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;
}

只要计算机一接收到RTC中断,就会调用这个函数。首先要注意的是使用了自旋锁——第一次调用时为了保证rtc_irq_data不被SMP机器上的其他处理器同时访问,第二次调用避免rtc_callback出现相同的情况。

rtc_irq_data变量是unsigned long类型,存放有关RTC的信息,每次中断时都会更新以反映中断的状态。

接下来,如果设置了RTC周期性定时器,就要通过函数mod_timer()对其更新。

代码的最后一部分——处于注释现在执行其余的操作下,会执行一个可能被预先设置好的回调函数。RTC驱动程序允许注册一个回调函数,并在每个RTC中断到来时执行。

最后,这个函数会返回IRQ_HANDLED,标明已经正确地完成了对此设备的操作。因为这个中断处理程序不支持共享,而且RTC也没有什么用来测试虚假中断的机制,所以该处理程序总是返回IRQ_HANDLED。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值