说到工作队列,相信接触linux内核驱动的同学肯定看到过,但是为什么要引入工作队列呢?工作队列什么时候引入的? 它的作用是什么?我们应该怎么使用它等等问题,一定困惑了不少刚接触驱动的新人,当然也困惑了我很长一段时间 今天就我个人的学习经历以及查找网上资源进行一个小结,本文仅代表个人愚见,如果不足之处还请指正和交流沟通,下面就进入正文! 工作队列的引入是为了把工作推后执行,工作推后执行之前已经有了软中断和tasklet这两种下半部机制为什么还要进入工作队列, 其实tasklet就是软中断的一个补充添加了一定的机制,软中断不能被自己打断,能被硬中断打断,可以并发运行于多个cpu上,所以软中断必须设计为可重入函数 那么其数据结构就需要自旋锁保护防止被修改为不期望的数据。基于软中断必须使用可重入函数,带来编程的复杂度, 于是就出现了tasklet补充这个不足,其实软中断类型默认11中,而tasklet就是其中的HI_SOFTIRQ和TASKLET_SOFTIRQ两种相当于 是实现也是软中断机制一种特定的tasklet只能在一个cpu上运行,不同的tasklet可以运行在不同的cpu上;软中断是静态分配的,在内核编译好后就不能改变 tasklet就灵活许多。
其实相比于软中断和tasklet其优势在于交给一个内核线程去执行,也就是说这个下半部允许在进程上下文中执行,尤其是可以重新调度甚至是睡眠 那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。 如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体执行你的下半部处理, 也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。 这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。 如果不需要用一个内核线程来推后执行工作,那么就考虑使用 tasklet。
前面描述了关于工作队列的引入带来的优势和作用,下面就来描述一下工作队列的用法 工作队列是在内核2.6版本引入的机制,在2.6.20之后做了些许改动,主要是数据结构上和函数API上的改动,具体看下面 在2.6.20之前的数据结构 struct work_struct { unsigned long pending; struct list_head entry; void (func)(void ); void *data; void *wq_data; struct timer_list timer; }; 结构成员说明 pending是用来记录工作是否已经挂在队列上; entry是循环链表结构; func作为函数指针,由用户实现; data用来存储用户的私人数据,此数据即是func的参数; wq_data一般用来指向工作者线程(工作者线程参考下文); timer是推后执行的定时器。 work_struct的这些变量里,func和data是用户使用的,其他是内部变量,我们可以不用太关心。 API: 1、INIT_WORK(_work, _func, _data); 2、int schedule_work(struct work_struct *work); 3、int schedule_delayed_work(struct work_struct *work, unsigned long delay); 4、void flush_scheduled_work(void); 5、int cancel_delayed_work(struct work_struct *work); 1、初始化指定工作,目的是把用户指定的函数_func及_func需要的参数_data赋给work_struct的func及data变量。 2、对工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。 3、延迟执行工作,与schedule_work类似。 4、刷新缺省工作队列。此函数会一直等待,直到队列中的所有工作都被执行。 5、flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。 以上均是采用缺省工作者线程来实现工作队列,其优点是简单易用,缺点是如果缺省工作队列负载太重,执行效率会很低,这就需要我们创建自己的工作者线程和工作队列。
API: struct workqueue_struct *create_workqueue(const char *name); int queue_work(struct workqueue_struct *wq, struct work_struct *work); int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay); void flush_workqueue(struct workqueue_struct *wq); void destroy_workqueue(struct workqueue_struct *wq); 1、创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。 2、类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。 3、延迟执行工作。 4、刷新指定工作队列。 5、释放创建的工作队列。
2.6.20之后数据结构 typedef void (*work_func_t)(struct work_struct *work); struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; }; 相比于之前的版本数据结构entry没有变,func和data变了,而其它变量没有了 data的类型是atomic_long_t,可以看出是原子类型,与之前的用法有所改变,现在是之前版本pending和wq_data的复合体; func的参数是一个指向work_struct的指针,指向的数据就是定义func的work_struct。 所以可能就有疑问:用户数据如何传输给函数和如何做到延时执行?之前版本是void *data传参和timer表示延时时间
关于延时重新定义了一个结构体 struct delayed_work { struct work_struct work; struct timer_list timer; }; 这么做的目的是只有在需要延时的工作才会有到timer,之前的显然有点浪费,而且普通情况下timer并没有意义。 API 初始化队列动态创建 INIT_WORK(struct work_struct *work, work_func_t func);//定义正常执行的工作项 INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);//定义延后执行的工作项
静态创建 DECLARE_WORK(name,function); //定义正常执行的工作项 DECLARE_DELAYED_WORK(name,function);//定义延后执行的工作项
创建工作队列 struct workqueue_struct *create_singlethread_workqueue(const char *name); struct workqueue_struct *create_workqueue(const char *name); 相比于create_singlethread_workqueue,create_workqueue同样会分配一个wq的工作队列,但是不同之处在于,对于多cpu系统而言, 对每一个cpu,都会为之创建一个per_CPU的cwq结构,对应每一个cwq,都会生成一个新的work_thread进程,但是当用queue_work向cwq上 提交work节点时,是哪个cpu调用该函数,便向该cpu对应的cwq上的worklist上增加work节点 int schedule_work(struct work_struct *work); work马上会被调用,一旦其所在的处理器工作线程被唤醒就会被执行 int schedule_delayed_work(struct delayed_work *work, unsigned long delay); work指向的工作在delay指定的节拍之后才会被执行 struct workqueue_struct *create_workqueue(const char *name); int queue_work(struct workqueue_struct *wq, struct work_struct *work); 把工作交给指定的工作队列wq,而不是缺省的工作队列 int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay); 调度延迟工作队列 void flush_scheduled_work(void);//刷新缺省的工作队列 void flush_workqueue(struct workqueue_struct *wq);刷新指定工作队列 int cancel_delayed_work(struct delayed_work *work);删除工作 void destroy_workqueue(struct workqueue_struct *wq);释放创建的工作队列
demo示例1 static init_events(struct work_struct *work); struct workqueue_struct *wq; 静态创建工作 static DECLARE_WORK(init_work, init_events); 在内核模块驱动执行prob函数中 创建工作队列 wq = create_singlethread_workqueue(“init_work”); if (wq == NULL) { printk(“create wq fail!\n”); return -ENOMEM; }
然后可以使用进行调度队列 queue_work(wq, &init_work);
demo示例2 动态创建工作 struct workqueue_struct *wq; struct work_struct init_work;
在内核模块驱动执行prob函数中 INIT_WORK(&init_work, init_events); 创建工作队列 wq = create_singlethread_workqueue(“init_work”); if (wq == NULL) { printk(“create wq fail!\n”); return -ENOMEM; }
queue_work(wq, &init_work);
示例3 static struct delayed_work work; static struct workqueue_struct * workqueue = NULL; 创建延时工作达到轮询的效果 INIT_DELAYED_WORK(&work, init_events); workqueue = create_workqueue(“poll_check”); 调度延迟工作队列(轮询调度) queue_delayed_work(gtp_esd_check_workqueue, &work,delay); cancel_delayed_work_sync(&work);等待直到删除(不能用于中断上下文) destroy_workqueue(wq);删除创建的工作队列