中断是一种处理异步事件的机制。中断源触发中断信号,内核检测到当前的中断事件需要处理,则暂停当前的运行进程,转而去处理中断事件。用于处理中断事件的函数称为中断处理程序,中断处理程序的入口地址一般存放在内存中的固定位置,这样方便内核查找各个中断所对应的中断处理程序。

       中断处理程序有一个限制条件,中断处理程序不能进入睡眠状态,即它不能调用任何有可能会导致睡眠的函数。原因是:中断处理程序运行在中断上下文中,中断上下文与进程无关,它并不是一个调度实体,如果将其睡眠,将无法唤醒(根本原因:设计如此)。

       为了快速响应中断,中断处理程序应该尽可能快速处理,否则将有可能影响到下一个中断的处理。但在某些情况下,中断处理程序需要处理一些费时的任务甚至可能会导致睡眠,因此内核开发者引入了中断上下部的概念:在中断上部处理紧急的任务,然后调用中断下部;而一些费时的或需要睡眠的部分交由中断下部进行。

       在中断上半部一般处理一些硬件操作,在结束前调用中断的下半部,在上半部是不允许进入睡眠状态的,因此任何的可能导致睡眠的函数在上半部是不能被使用的。对于中断的下半部,在2.6版本内核常用的两种实现机制为:tasklet和workqueue。

       tasklet是借助软中断的方式来实现的,tasklet的调度函数tasklet_schedule()或tasklet_hi_schedule(),通过触发软中断TASKLET_SOFTIRQ或HI_SOFTIRQ,从而唤起相应的软中断来处理下半部分。这样在处理下半部时,上半部可以处理下一次的中断事件。同样的在软中断中也是不能调用可能引起睡眠的函数,它可以延缓处理下半部,也可以与下一次上半部并发处理。

/*
**   按键驱动程序:通过中断(下部通过tasklet)实现的部分源码。
*/
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h> 
#include <mach/irqs.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>

static struct input_dev *button_dev;

static void bottom_interrupt(unsigned long t)
{
        printk(KERN_INFO "This bottom part by tasklet!\n");
}

DECLARE_TASKLET(button_tasklet, bottom_interrupt, 0);

static irqreturn_t button_interrupt(int irq, void *dev)
{
        printk(KERN_INFO "This top part!\n");
        tasklet_schedule(&button_tasklet);
        return IRQ_HANDLED;
}

static int __init button_init(void)
{
        int ret;
        ret = gpio_request(S5PV210_GPH0(2), "GPH0_2");
        if (ret) {
                printk(KERN_ERR "gpio request failed!\n");
                return -EBUSY;
        }
        s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f));

        if (request_irq(IRQ_EINT2, button_interrupt,
                        IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "button-x210", NULL)) {
                printk(KERN_ERR "irq request failed!\n");
                ret =  -EBUSY;
                goto request_irq_error;
        }

        button_dev = input_allocate_device();
        if (!button_dev) {
                printk(KERN_ERR "allocate input device failed!\n");
                ret =  -ENOMEM;
                goto allocate_error;
        }
        set_bit(EV_KEY, button_dev->evbit);
        set_bit(KEY_LEFT, button_dev->keybit);

        ret = input_register_device(button_dev);
        if (ret) {
                printk(KERN_ERR "register input device failed\n");
                goto register_error;
        }

        return 0;
register_error:
        input_free_device(button_dev);
allocate_error:
        free_irq(IRQ_EINT2, NULL);
request_irq_error:
        gpio_free(S5PV210_GPH0(2));
        return ret;
}

static void __exit button_exit(void)
{
        input_free_device(button_dev);
        free_irq(IRQ_EINT2, NULL);
        gpio_free(S5PV210_GPH0(2));
}

module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("alants");

       workqueue则是通过将下半部交由一个内核线程去处理,这样中断的下半部就是在进程的上下文中执行。因此使用workqueue的方式实现的下半部是可以进入睡眠的。workqueue的调度是通过接口schedule_work()来操作的,schedule_work()所进行的实际操作就是将当前的下半部的相关结构体work_struct添加到当前的work_list中,等待内核线程调用。

/*
**   按键驱动程序:通过中断(下部通过workqueue)实现的部分源码。
*/
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/irqs.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>

static struct input_dev *button_dev;

static void bottom_interrupt(struct work_struct *work)
{      
        printk(KERN_INFO "This bottom part by workqueue!\n");
}

DECLARE_WORK(button_work, bottom_interrupt);

static irqreturn_t button_interrupt(int irq, void *dev)
{
        printk(KERN_INFO "This top part!\n");
        schedule_work(&button_work);
        return IRQ_HANDLED;
}

static int __init button_init(void)
{
        int ret;
        ret = gpio_request(S5PV210_GPH0(2), "GPH0_2");
        if (ret) {
                printk(KERN_ERR "gpio request failed!\n");
                return -EBUSY;
        }
        s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f));

        if (request_irq(IRQ_EINT2, button_interrupt,
                        IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "button-x210", NULL)) {
                printk(KERN_ERR "irq request failed!\n");
                ret =  -EBUSY;
                goto irq_request_error;
        }

        button_dev = input_allocate_device();
        if (!button_dev) {
                printk(KERN_ERR "allocate input device failed!\n");
                ret =  -ENOMEM;
                goto allocate_error;
        }
        set_bit(EV_KEY, button_dev->evbit);
        set_bit(KEY_LEFT, button_dev->keybit);

        ret = input_register_device(button_dev);
        if (ret) {
                printk(KERN_ERR "register input device failed\n");
                goto register_error;
        }

        return 0;
register_error:
        input_free_device(button_dev);
allocate_error:
        free_irq(IRQ_EINT2, NULL);
irq_request_error:
        gpio_free(S5PV210_GPH0(2));
        return ret;
}

static void __exit button_exit(void)
{
        input_unregister_device(button_dev);
        input_free_device(button_dev);
        free_irq(IRQ_EINT2, NULL);
        gpio_free(S5PV210_GPH0(2));
}

module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("alants");

       从上述两种按键驱动中断实现方式来看,workqueue和tasklet的使用似乎没有很大的区别,但是在实际使用中要根据中断下半部分具体的处理内容(例如,是否会进入睡眠)来考虑下半部采用哪种机制。