一、Linux工作队列与Linux小任务机制的区别
工作队列(work queue)是另外一种将工作推后执行的形式,tasklet(小任务机制)有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
那么,什么情况下使用工作队列,什么情况下使用tasklet呢?如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
一般,不要轻易的去使用工作队列,因为每当创建一条工作队列,内核就会为这条工作队列创建一条内核线程。工作队列位于进程上下文,与软中断,tasklet有所区别,工作队列里允许延时,睡眠操作,而软中断,tasklet位于中断上下文,不允许睡眠和延时操作。
二、使用Linux工作队列
1、需要包含的头文件
#include <linux/workqueue.h>
2、工作队列相关的数据结构(各个版本内核可能不同,这里用的是3.5)
1//工作队列结构 2struct work_struct { 3 atomic_long_t data; 4 //链表处理 5 struct list_head entry; 6 //工作处理函数 7 work_func_t func; 8#ifdef CONFIG_LOCKDEP 9 struct lockdep_map lockdep_map; 10#endif 11};
3、操作工作队列相关的API
1创建一个队列就会有一个内核线程,一般不要轻易创建队列 2位于进程上下文--->可以睡眠 3定义: 4 struct work_struct work; 5 6初始化: 7 INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work)); 8 9定义并初始化: 10 DECLARE_WORK(name, void (*func)(struct work_struct *work)); 11 12=========================================================== 13 14调度: 15 int schedule_work(struct work_struct *work); 16 返回1成功, 0已经添加在队列上 17 18延迟调度: 19 int schedule_delayed_work(struct work_struct *work, unsigned long delay); 20 21=========================================================== 22 23创建新队列和新工作者线程: 24 struct workqueue_struct *create_workqueue(const char *name); 25 26调度指定队列: 27 int queue_work(struct workqueue_struct *wq, struct work_struct *work); 28 29延迟调度指定队列: 30 int queue_delayed_work(struct workqueue_struct *wq, 31 struct work_struct *work, unsigned long delay); 32销毁队列: 33 void destroy_workqueue(struct workqueue_struct *wq);
4、Demo实现(基于Tiny4412 Linux3.5内核)
1#include <linux/module.h> 2#include <linux/kernel.h> 3#include <linux/init.h> 4#include <linux/platform_device.h> 5#include <linux/fb.h> 6#include <linux/backlight.h> 7#include <linux/err.h> 8#include <linux/pwm.h> 9#include <linux/slab.h> 10#include <linux/miscdevice.h> 11#include <linux/delay.h> 12#include <linux/gpio.h> 13#include <mach/gpio.h> 14#include <plat/gpio-cfg.h> 15#include <linux/timer.h> /*timer*/ 16#include <asm/uaccess.h> /*jiffies*/ 17#include <linux/delay.h> 18#include <linux/interrupt.h> 19#include <linux/workqueue.h> 20struct tasklet_struct task_t ; 21struct workqueue_struct *mywork ; 22//定义一个工作队列结构体 23struct work_struct work; 24static void task_fuc(unsigned long data) 25{ 26 if(in_interrupt()){ 27 printk("%s in interrupt handle!\n",__FUNCTION__); 28 } 29} 30//工作队列处理函数 31static void mywork_fuc(struct work_struct *work) 32{ 33 if(in_interrupt()){ 34 printk("%s in interrupt handle!\n",__FUNCTION__); 35 } 36 msleep(2); 37 printk("%s in process handle!\n",__FUNCTION__); 38} 39 40static irqreturn_t irq_fuction(int irq, void *dev_id) 41{ 42 tasklet_schedule(&task_t); 43 //调度工作 44 schedule_work(&work); 45 if(in_interrupt()){ 46 printk("%s in interrupt handle!\n",__FUNCTION__); 47 } 48 printk("key_irq:%d\n",irq); 49 return IRQ_HANDLED ; 50} 51 52static int __init tiny4412_Key_irq_test_init(void) 53{ 54 int err = 0 ; 55 int irq_num1 ; 56 int data_t = 100 ; 57 //创建新队列和新工作者线程 58 mywork = create_workqueue("my work"); 59 //初始化 60 INIT_WORK(&work,mywork_fuc); 61 //调度指定队列 62 queue_work(mywork,&work); 63 tasklet_init(&task_t,task_fuc,data_t); 64 printk("irq_key init\n"); 65 irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); 66 err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1"); 67 if(err != 0){ 68 free_irq(irq_num1,(void *)"key1"); 69 return -1 ; 70 } 71 return 0 ; 72} 73 74static void __exit tiny4412_Key_irq_test_exit(void) 75{ 76 int irq_num1 ; 77 printk("irq_key exit\n"); 78 irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); 79 //销毁一条工作队列 80 destroy_workqueue(mywork); 81 free_irq(irq_num1,(void *)"key1"); 82} 83 84module_init(tiny4412_Key_irq_test_init); 85module_exit(tiny4412_Key_irq_test_exit); 86 87MODULE_LICENSE("GPL"); 88MODULE_AUTHOR("YYX"); 89MODULE_DESCRIPTION("Exynos4 KEY Driver");
将程序编译完,将zImage下到板子上,重新启动会看到内核打印信息
可以看到,当我们按下按键的时候,进入外部中断服务函数,此时task_fuc先被调用,然后调用到mywork_fuc,并打印了mywork_fuc里面的信息,从这里我们用程序验证了,工作队列是位于进程上下文,而不是中断上下文,和tasklet是有所区别的,下一节我们将会讲一讲tasklet(小任务机制)。