在上面的实例中,进入中断后仅仅是将按键的值读取出来,系统开销非常的小。但是在实际应用中,在中断中执行的任务要复杂得多,它可能要耗用较大的时间来处理。为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。 顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。 现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。 尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。比如前面的实验,中断中执行的任务相当少,就没有必要。 Linux系统实现底半部的机制主要有tasklet、工作队列和软中断。为了掌握他们的用法,将上一小节实验的内容通过tasklet机制实现。在kernel/drivers/char/x4412目录下新建驱动程序x4412-eint-tasklet.c文件,编辑内容如下: - #include <linux/irq.h>
- #include <linux/interrupt.h>
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/errno.h>
- #include <linux/sched.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <asm/uaccess.h>
- #include <linux/slab.h>
- #include <linux/device.h>
- #include <mach/gpio.h>
- /* 定义并初始化等待队列头 */
- static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
- static struct class *cdrv_class;
- static struct device *cdrv_device;
- void x4412_eint_do_tasklet(unsigned long data);
- DECLARE_TASKLET(x4412_eint_tasklet,x4412_eint_do_tasklet,0);
- typedef struct pin_desc
- {
- unsigned int pin;
- unsigned int key_val;
- }pin_desc;
- static struct pin_desc pins_desc[6] =
- {
- {EXYNOS4_GPX1(0),0x01},//LEFT
- {EXYNOS4_GPX1(3),0x02},//RIGHT
- {EXYNOS4_GPX1(2),0x03},//UP
- {EXYNOS4_GPX1(1),0x04},//DOWN
- {EXYNOS4_GPX1(5),0x05},//MENU
- {EXYNOS4_GPX1(4),0x06},//BACK
- };
-
- struct pin_desc *pin_desc_p;
- static unsigned char key_val;
- static int ev_press = 0;
- int major;
-
- void x4412_eint_do_tasklet(unsigned long data)
- {
- unsigned int pinval;
- pinval = gpio_get_value(pin_desc_p->pin);
- if(pinval)
- {
- key_val = 0x80 | (pin_desc_p->key_val);//松开
- }
- else
- {
- key_val = pin_desc_p->key_val;// 按下
- }
- ev_press = 1;
- printk("eint occured.\r\n");
- wake_up_interruptible(&button_waitq);
- }
- /* 用户中断处理函数 */
- static irqreturn_t x4412_buttons_irq(int irq, void *dev_id)
- {
- pin_desc_p = (struct pin_desc *)dev_id;
- tasklet_schedule(&x4412_eint_tasklet);
- return IRQ_HANDLED;
- }
- static int x4412_eint_open(struct inode * inode, struct file * filp)
- {
- return 0;
- }
- static ssize_t x4412_eint_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
- {
- if (count != 1)
- return -EINVAL;
- wait_event_interruptible(button_waitq, ev_press);
- if(copy_to_user(buf, &key_val, 1))
- return -EFAULT;
- ev_press = 0;
- return 1;
- }
- static int x4412_eint_release(struct inode *inode, struct file *file)
- {
- int irq;
- irq = gpio_to_irq(pins_desc[0].pin);
- free_irq(irq, &pins_desc[0]);
- irq = gpio_to_irq(pins_desc[1].pin);
- free_irq(irq,&pins_desc[1]);
- irq = gpio_to_irq(pins_desc[2].pin);
- free_irq(irq,&pins_desc[2]);
- irq = gpio_to_irq(pins_desc[3].pin);
- free_irq(irq, &pins_desc[3]);
- irq = gpio_to_irq(pins_desc[4].pin);
- free_irq(irq,&pins_desc[4]);
- irq = gpio_to_irq(pins_desc[5].pin);
- free_irq(irq,&pins_desc[5]);
- return 0;
- }
- static const struct file_operations x4412_eint_fops =
- {
- .owner = THIS_MODULE,
- .read = x4412_eint_read,
- .open = x4412_eint_open,
- .release = x4412_eint_release,
- };
- /* 驱动入口函数 */
- static int x4412_eint_init(void)
- {
- int ret,irq;
- major = register_chrdev(0, "x4412-key", &x4412_eint_fops);
- cdrv_class = class_create(THIS_MODULE, "x4412-eint");
- cdrv_device = device_create(cdrv_class, NULL, MKDEV(major, 0), NULL, "x4412-eint");
- irq = gpio_to_irq(pins_desc[0].pin);
- ret = request_irq(irq, x4412_buttons_irq,IRQ_TYPE_EDGE_BOTH, "LEFT", &pins_desc[0]);
- if(ret)
- printk("request irq_eint8 error!\r\n");
- irq = gpio_to_irq(pins_desc[1].pin);
- ret = request_irq(irq, x4412_buttons_irq,IRQ_TYPE_EDGE_BOTH, "RIGHT",&pins_desc[1]);
- if(ret)
- printk("request irq_eint11 error!\r\n");
- irq = gpio_to_irq(pins_desc[2].pin);
- ret = request_irq(irq, x4412_buttons_irq, IRQ_TYPE_EDGE_BOTH, "UP", &pins_desc[2]);
- if(ret)
- printk("request irq_eint10 error!\r\n");
- irq = gpio_to_irq(pins_desc[3].pin);
- ret = request_irq(irq, x4412_buttons_irq, IRQ_TYPE_EDGE_BOTH, "DOWN", &pins_desc[3]);
- if(ret)
- printk("request irq_eint9 error!\r\n");
- irq = gpio_to_irq(pins_desc[4].pin);
- ret = request_irq(irq, x4412_buttons_irq, IRQ_TYPE_EDGE_BOTH, "MENU", &pins_desc[4]);
- if(ret)
- printk("request irq_eint13 error!\r\n");
- irq = gpio_to_irq(pins_desc[5].pin);
- ret = request_irq(irq, x4412_buttons_irq, IRQ_TYPE_EDGE_BOTH, "BACK", &pins_desc[5]);
- if(ret)
- printk("request irq_eint12 error!\r\n");
- pin_desc_p = kmalloc(sizeof(struct pin_desc), GFP_KERNEL);
- if(!pin_desc_p)
- {
- return -ENOMEM;
- }
- return 0;
- }
- /* 驱动出口函数 */
- static void x4412_eint_exit(void)
- {
- unregister_chrdev(major, "x4412-key");
- device_unregister(cdrv_device); //卸载类下的设备
- class_destroy(cdrv_class); //卸载类
- }
- module_init(x4412_eint_init); //用于修饰入口函数
- module_exit(x4412_eint_exit); //用于修饰出口函数
- MODULE_AUTHOR("www.9tripod.com");
- MODULE_LICENSE("GPL");
复制代码 和前一小节的源码比较,不然发现使用tasklet相当的简单,在程序的开始声明tasklet的执行函数,并通过DECLARE_TASKLET宏关联该执行函数: - void x4412_eint_do_tasklet(unsigned long data);
- DECLARE_TASKLET(x4412_eint_tasklet,x4412_eint_do_tasklet,0);
复制代码 在中断顶半部,即x4412_buttons_irq 函数中引用tasklet_schedule函数就能使系统在适当的时候调度运行: - tasklet_schedule(&x4412_eint_tasklet);
复制代码 值得说明的是,在上一小节的中断服务线程中通过dev_id传入了pins_desc结构体,因此程序中定义了一个全局结构体指针,用于将dev_id传入到中断顶关部的结构体引用到中断底半部中。 编辑kernel/drivers/char/x4412/Kconfig文件,添加如下语句: - config X4412_EINT_TASKLET_DRIVER
- tristate "x4412 eint tasklet driver"
- default m
- help
- compile for x4412 eint tasklet driver,y for kernel,m for module.
复制代码 编辑kernel/drivers/char/x4412/Makefile文件,添加如下语句: - obj-$(CONFIG_X4412_EINT_TASKLET_DRIVER) += x4412-eint-tasklet.o
复制代码 编译内核,加载驱动后测试驱动模块,观察和上一章节的现象是否相同。
|