中断下半部之工作队列
2016-09-24 16:03
330 查看
一、中断的顶半部和底半部
设备的中断会打断内核中进程的正常调度和运行,而系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理,由于中断的优先级最高,这时候就会影响到其他进程的实时性。
为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作,“登记中断”意味着将底半部处理程序挂到该设备的下半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同。
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
二、中断底半部的实现机制
Linux 系统实现底半部的机制主要有tasklet,工作队列和软中断。tasklet 是基于软中断实现的(内核定时器也依靠软中断实现),一般都使用tasklet或者工作队列来实现中断下半部。
通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则:
(1)如果推后执行的任务需要在一个tick(1/HZ,即每隔多久发生一次时钟中断,这个‘多久’便是一个tick)之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程。
(2)如果推后执行的任务需要睡眠,那么只能选择工作队列;
(3)如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时,当然使用内核定时器也可以。
(4)如果推后执行的任务对延迟的时间没有任何要求,此时通常为无关紧要的任务,那么使用工作队列;
(5)如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。它是惟一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时,在你需要执行阻塞式的I/O操作时,它都会非常有用。
三、工作队列详解
(1)工作队列概述
工作队列可以把工作推后,交由一个内核线程去执行,这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要的特点是工作队列允许重新调度甚至是睡眠。
实际上,工作队列的本质就是将工作交给内核线程events处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。
我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct。这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct。而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,当然,自己也可以创建自己的工作者线程。这些work_struct结构被连接成链表,当一个工作线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,工作线程就会继续休眠。其结构如下图所示。
(2)工作队列的使用
工作队列是2.6内核开始引入的机制,在2.6.20之后,工作队列的数据结构发生了一些变化,本文只对2.6.20之后的版本进行介绍。
a、静态地创建一个名为n,待执行函数为f的work_struct结构。
DECLARE_WORK(n, f)
#ifdef CONFIG_LOCKDEP
/*
* NB: because we have to copy the lockdep_map, setting _key
* here is required, otherwise it could get initialised to the
* copy of the lockdep_map!
*/
#define __WORK_INIT_LOCKDEP_MAP(n, k) \
.lockdep_map = STATIC_LOCKDEP_MAP_INIT(n, k),
#else
#define __WORK_INIT_LOCKDEP_MAP(n, k)
#endif
#define __WORK_INITIALIZER(n, f) { \
.data = WORK_DATA_STATIC_INIT(), \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
__WORK_INIT_LOCKDEP_MAP(#n, &(n)) \
}
#define __DELAYED_WORK_INITIALIZER(n, f, tflags) { \
.work = __WORK_INITIALIZER((n).work, (f)), \
.timer = __TIMER_INITIALIZER(delayed_work_timer_fn, \
0, (unsigned long)&(n), \
(tflags) | TIMER_IRQSAFE), \
}
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
b、动态初始化一个work_struct结构。
INIT_WORK(_work, _func)
#define PREPARE_WORK(_work, _func) \
do { \
(_work)->func = (_func); \
} while (0)
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
#endif
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
c、使用内核缺省工作者线程的API
int schedule_work(struct work_struct *work);
work马上就会被调度,一旦其所在的处理器上的内核工作线程被唤醒,这个work就会被执行。
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。
void flush_scheduled_work(void);
该函数的作用,是为了防止有竞争条件的出现。一般情况下cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块。
int cancel_delayed_work(struct work_struct *work);
取消相应的delayed_work。直接返回。
返回非 0:内核会确保不会初始化给定入口项的执行,即终止该工作。
返回0:则说明该工作已经在其他处理器上运行
因此在cancel_delayed_work返回后可能仍在运行,怎么办?
int cancel_work_sync(struct work_struct *work);
取消相应的delayed_work。但是,如果这个delayed_work已经在运行,那么,cancel_delayed_work_sync会阻塞,直到delayed_work完成并取消相应的delayed_work。
d、自己创建工作者线程API
struct workqueue_struct *create_workqueue(const char *name);
创建一个名字为name的工作队列,为系统每个cpu都分配一个内核线程。
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)
与queue_work的区别是多了一个cpu参数,即指定在哪个CPU上运行。
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
类似于schedule_delayed_work,区别在于queue_delay_work把给定工作提交给创建的工作队列wq而不是缺省队列。
int queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
与queue_delayed_work的区别是多了一个cpu参数,即指定在哪个CPU上运行。
void flush_workqueue(struct workqueue_struct *wq);
刷新调度执行指定工作队列里的所有work。并阻塞直到所有work执行完毕后,返回退出。若所有work都已经执行完毕,则直接返回。实际上,它只是等待(睡眠),直到缺省工作队列上的工作被执行。
void destroy_workqueue(struct workqueue_struct *wq);
释放创建的工作队列。
四、例子(使用工作队列来对按键进行消抖)
button.c文件如下:
Makefile文件如下:
设备的中断会打断内核中进程的正常调度和运行,而系统对更高吞吐率的追求势必要求中断服务程序尽可能地短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理,由于中断的优先级最高,这时候就会影响到其他进程的实时性。
为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。
顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作,“登记中断”意味着将底半部处理程序挂到该设备的下半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。
现在,中断处理工作的重心就落在了底半部的头上,它来完成中断事件的绝大多数任务。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同。
尽管顶半部、底半部的结合能够改善系统的响应能力,但是,僵化地认为 Linux设备驱动中的中断处理一定要分两个半部则是不对的。如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。
二、中断底半部的实现机制
Linux 系统实现底半部的机制主要有tasklet,工作队列和软中断。tasklet 是基于软中断实现的(内核定时器也依靠软中断实现),一般都使用tasklet或者工作队列来实现中断下半部。
通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则:
(1)如果推后执行的任务需要在一个tick(1/HZ,即每隔多久发生一次时钟中断,这个‘多久’便是一个tick)之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程。
(2)如果推后执行的任务需要睡眠,那么只能选择工作队列;
(3)如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时,当然使用内核定时器也可以。
(4)如果推后执行的任务对延迟的时间没有任何要求,此时通常为无关紧要的任务,那么使用工作队列;
(5)如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。它是惟一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时,在你需要执行阻塞式的I/O操作时,它都会非常有用。
三、工作队列详解
(1)工作队列概述
工作队列可以把工作推后,交由一个内核线程去执行,这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要的特点是工作队列允许重新调度甚至是睡眠。
实际上,工作队列的本质就是将工作交给内核线程events处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。
我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct。这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct。而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,当然,自己也可以创建自己的工作者线程。这些work_struct结构被连接成链表,当一个工作线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,工作线程就会继续休眠。其结构如下图所示。
(2)工作队列的使用
工作队列是2.6内核开始引入的机制,在2.6.20之后,工作队列的数据结构发生了一些变化,本文只对2.6.20之后的版本进行介绍。
a、静态地创建一个名为n,待执行函数为f的work_struct结构。
DECLARE_WORK(n, f)
#ifdef CONFIG_LOCKDEP
/*
* NB: because we have to copy the lockdep_map, setting _key
* here is required, otherwise it could get initialised to the
* copy of the lockdep_map!
*/
#define __WORK_INIT_LOCKDEP_MAP(n, k) \
.lockdep_map = STATIC_LOCKDEP_MAP_INIT(n, k),
#else
#define __WORK_INIT_LOCKDEP_MAP(n, k)
#endif
#define __WORK_INITIALIZER(n, f) { \
.data = WORK_DATA_STATIC_INIT(), \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
__WORK_INIT_LOCKDEP_MAP(#n, &(n)) \
}
#define __DELAYED_WORK_INITIALIZER(n, f, tflags) { \
.work = __WORK_INITIALIZER((n).work, (f)), \
.timer = __TIMER_INITIALIZER(delayed_work_timer_fn, \
0, (unsigned long)&(n), \
(tflags) | TIMER_IRQSAFE), \
}
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
b、动态初始化一个work_struct结构。
INIT_WORK(_work, _func)
#define PREPARE_WORK(_work, _func) \
do { \
(_work)->func = (_func); \
} while (0)
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
PREPARE_WORK((_work), (_func)); \
} while (0)
#endif
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
c、使用内核缺省工作者线程的API
int schedule_work(struct work_struct *work);
work马上就会被调度,一旦其所在的处理器上的内核工作线程被唤醒,这个work就会被执行。
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。
void flush_scheduled_work(void);
该函数的作用,是为了防止有竞争条件的出现。一般情况下cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块。
int cancel_delayed_work(struct work_struct *work);
取消相应的delayed_work。直接返回。
返回非 0:内核会确保不会初始化给定入口项的执行,即终止该工作。
返回0:则说明该工作已经在其他处理器上运行
因此在cancel_delayed_work返回后可能仍在运行,怎么办?
int cancel_work_sync(struct work_struct *work);
取消相应的delayed_work。但是,如果这个delayed_work已经在运行,那么,cancel_delayed_work_sync会阻塞,直到delayed_work完成并取消相应的delayed_work。
d、自己创建工作者线程API
struct workqueue_struct *create_workqueue(const char *name);
创建一个名字为name的工作队列,为系统每个cpu都分配一个内核线程。
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)
与queue_work的区别是多了一个cpu参数,即指定在哪个CPU上运行。
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
类似于schedule_delayed_work,区别在于queue_delay_work把给定工作提交给创建的工作队列wq而不是缺省队列。
int queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
与queue_delayed_work的区别是多了一个cpu参数,即指定在哪个CPU上运行。
void flush_workqueue(struct workqueue_struct *wq);
刷新调度执行指定工作队列里的所有work。并阻塞直到所有work执行完毕后,返回退出。若所有work都已经执行完毕,则直接返回。实际上,它只是等待(睡眠),直到缺省工作队列上的工作被执行。
void destroy_workqueue(struct workqueue_struct *wq);
释放创建的工作队列。
四、例子(使用工作队列来对按键进行消抖)
button.c文件如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/device.h> #include <linux/init.h> #include <linux/major.h> #include <linux/delay.h> #include <linux/io.h> #include <asm/uaccess.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/gpio.h> #include <asm/gpio.h> #define BUTTON_NAME "poll_button" #define BUTTON_GPIO 140 static int button_major = 0; static int button_minor = 0; static struct cdev button_cdev; static struct class *p_button_class = NULL; static struct device *p_button_device = NULL; static struct work_struct button_wq; static volatile int ev_press = 0; static volatile char key_value[] = {0}; static volatile int flag_interrupt = 1; static int old_value; static int Button_Irq = 0; static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static irqreturn_t buttons_interrupt(int irq, void *dev_id) { if(flag_interrupt) { flag_interrupt = 0; old_value = gpio_get_value(BUTTON_GPIO); schedule_work(&button_wq); } return IRQ_RETVAL(IRQ_HANDLED); } static void buttonwq_callback(struct work_struct *work) { msleep(10); //消抖时间10ms key_value[0] = gpio_get_value(BUTTON_GPIO); if(key_value[0] == old_value) { printk("button pressed! key_value[0] = %d\n", key_value[0]); ev_press= 1; wake_up_interruptible(&button_waitq); } flag_interrupt = 1; return; } static int button_irqcfg(void) { Button_Irq = gpio_to_irq(BUTTON_GPIO); enable_irq(Button_Irq); if(request_irq(Button_Irq, buttons_interrupt, IRQF_TRIGGER_FALLING, "BUTTON_IRQ", NULL) != 0) { printk("request irq failed !!! \n"); disable_irq(Button_Irq); free_irq(Button_Irq, NULL); return -EBUSY; } return 0; } static int button_open(struct inode *inode,struct file *file) { //button_irqcfg(); return 0; } static int button_close(struct inode *inode, struct file *file) { //free_irq(Button_Irq, NULL); return 0; } static int button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { unsigned long err; if (filp->f_flags & O_NONBLOCK) { /*nothing to do*/ //如果应用程序设置了非阻塞O_NONBLOCK,那么驱动这里就不使用等待队列进行等待。 } else { wait_event_interruptible(button_waitq, ev_press); } err = copy_to_user(buff, (const void *)key_value, min(sizeof(key_value), count)); key_value[0] = 0; ev_press = 0; return err ? -EFAULT : min(sizeof(key_value), count); } static const struct file_operations button_fops = { .owner = THIS_MODULE, .open = button_open, .release = button_close, .read = button_read, //.poll = button_poll, //.write = button_write, //.ioctl = button_ioctl }; static int button_setup_cdev(struct cdev *cdev, dev_t devno) { int ret = 0; cdev_init(cdev, &button_fops); cdev->owner = THIS_MODULE; ret = cdev_add(cdev, devno, 1); return ret; } static int __init button_init(void) { int ret; dev_t devno; printk("button driver init...\n"); INIT_WORK(&button_wq, buttonwq_callback); button_irqcfg(); if(button_major) { devno = MKDEV(button_major, button_minor); ret = register_chrdev_region(devno, 1, BUTTON_NAME); } else { ret = alloc_chrdev_region(&devno, button_minor, 1, BUTTON_NAME); button_major = MAJOR(devno); } if(ret < 0) { printk("get button major failed\n"); return ret; } ret = button_setup_cdev(&button_cdev, devno); if(ret) { printk("button setup cdev failed, ret = %d\n",ret); goto cdev_add_fail; } p_button_class = class_create(THIS_MODULE, BUTTON_NAME); ret = IS_ERR(p_button_class); if(ret) { printk(KERN_WARNING "button class create failed\n"); goto class_create_fail; } p_button_device = device_create(p_button_class, NULL, devno, NULL, BUTTON_NAME); ret = IS_ERR(p_button_device); if (ret) { printk(KERN_WARNING "button device create failed, error code %ld", PTR_ERR(p_button_device)); goto device_create_fail; } return 0; device_create_fail: class_destroy(p_button_class); class_create_fail: cdev_del(&button_cdev); cdev_add_fail: unregister_chrdev_region(devno, 1); return ret; } static void __exit button_exit(void) { dev_t devno; printk("button driver exit...\n"); devno = MKDEV(button_major, button_minor); device_destroy(p_button_class, devno); class_destroy(p_button_class); cdev_del(&button_cdev); unregister_chrdev_region(devno, 1); free_irq(Button_Irq, NULL); } module_init(button_init); module_exit(button_exit); MODULE_AUTHOR("Jimmy"); MODULE_DESCRIPTION("button Driver"); MODULE_LICENSE("GPL");
Makefile文件如下:
ifneq ($(KERNELRELEASE),) obj-m := button.o else KERNELDIR ?= /ljm/git_imx6/linux-fsl/src/linux-3-14-28-r0 TARGET_CROSS = arm-none-linux-gnueabi- PWD := $(shell pwd) default: $(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules endif install: $(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order
相关文章推荐
- Linux 驱动之中断下半部之工作队列
- linux设备驱动归纳总结(六):3.中断下半部之工作队列
- linux设备驱动归纳总结(六):3.中断下半部之工作队列
- 《Linux设备驱动开发详解》-- Linux中断处理底半部机制(tasklet、工作队列和软中断)
- linux设备驱动归纳总结(六):3.中断下半部之工作队列
- linux设备驱动归纳总结(六):3.中断下半部之工作队列
- 【Linux开发】linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列
- (六)3中断下半部之工作队列
- Linux2.6内核--中断下半部实现方法 工作队列
- 中断上半部,下半部/软中断/tasklet/工作队列
- Linux内核中断底半部处理--工作队列
- 中断服务下半部之工作队列【转】
- Linux内核提供了三种不同形式的中断底半部实现机制:软中断、tasklet和工作队列。
- 中断服务下半部之工作队列详解
- 中断服务下半部之工作队列详解
- 中断服务下半部之工作队列详解
- linux设备驱动归纳总结(六):3.中断下半部之工作队列
- 中断下半部-工作队列【转】
- linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列
- linux 触摸屏驱动中断下半部实现-工作队列