Linux设备驱动程序 之 工作队列
工作队列可以把工作推后,交给一个内核线程去执行–这个下半部分总是会在进程上下文中执行;通过工作队列执行的代码占尽进程上下文的优势;最重要的是工作队列允许重新调度甚至睡眠;
在工作队列和软中断/tasklet中做出选择很容易;如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择软中断或者tasklet;
如果需要用一个可以重新调度的实体来执行下半部处理,那么应该使用工作队列;它是唯一能在进程上下文中运行的下半部机制,也只有它才可以睡眠;这意味着如果需要获取大量的内存,或者在需要获取信号量时,或者需要执行阻塞式IO操作时,它会非常有用;如果不需要一个内核线程来推后执行工作,那么考虑使用tasklet;
创建推后的工作
可以通过DECLARE_WORK在编译时静态的创建work_struct结构实例并初始化:
1 #define DECLARE_WORK(n, f) \ 2 struct work_struct n = __WORK_INITIALIZER(n, f) 3 4 #define DECLARE_DELAYED_WORK(n, f) \ 5 struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0) 6 7 #define DECLARE_DEFERRABLE_WORK(n, f) \ 8 struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, TIMER_DEFERRABLE)
或者在运行时动态的创建一个work_struct实例,然后使用下面函数进行初始化:
1 #define INIT_WORK(_work, _func) \ 2 __INIT_WORK((_work), (_func), 0) 3 4 #define INIT_WORK_ONSTACK(_work, _func) \ 5 __INIT_WORK((_work), (_func), 1) 6 7 #define INIT_DELAYED_WORK(_work, _func) \ 8 __INIT_DELAYED_WORK(_work, _func, 0) 9 10 #define INIT_DELAYED_WORK_ONSTACK(_work, _func) \ 11 __INIT_DELAYED_WORK_ONSTACK(_work, _func, 0)
工作队列处理函数
工作队列处理函数的原型是:
1 typedef void (*work_func_t)(struct work_struct *work);
这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中;默认情况下,允许响应中断,并不持有任何锁;如果需要,函数可以睡眠;
需要注意的是,尽管操作处理函数运行在进程上下文,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射;通常在发送系统调用时,内核会代表用户空间进程运行,也只有此时它才会映射用户空间的内存;
调度工作(缺省工作队列)
如果要把给定工作的处理交给缺省的工作线程,则需要调用:
1 bool schedule_work(struct work_struct *work)
work马上会被调度,一旦其所在的处理器上的工作线程被唤醒,它就会被执行;
如果不希望马上被执行,而是希望它经过一段时间的延迟后才执行,则需要下面函数,可以在指定时间之后执行:
1 bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)
这时,dwork会在delay指定的始终节拍用完之后才会执行;
刷新操作
排入队列的工作会在工作者线程下一次被唤醒时执行;有时,在继续下一步工作之前,必须保证一些操作已经执行完毕了;这一点对模块来说就很重要,在卸载之前,它就有可能需要调用下面的函数;而在内核的其他部分,为了防止竞争条件的出现,也可能需要确保不再有待处理的工作;
为了,内核准备了用于刷新指定工作队列的函数:
1 bool flush_work(struct work_struct *work); 2 3 bool flush_delayed_work(struct delayed_work *dwork);
函数会一直等待,直到队列中的所有对象都被执行以后才返回;在等待所有待处理的工作执行的时候,该函数会进入休眠状态,所以只能在进程上下文中使用;
内核也提供了取消执行工作的函数:
1 bool cancel_work(struct work_struct *work); 2 bool cancel_work_sync(struct work_struct *work); 3 4 bool cancel_delayed_work(struct delayed_work *dwork); 5 bool cancel_delayed_work_sync(struct delayed_work *dwork);
创建新的工作队列
如果缺省队列不能满足需求,则应该创建一个新的工作队列和与之对应的工作者线程;由于这么做会在每个处理器上都创建一个工作者线程,所以只有在你明确了必须要靠自己一套线程来提高性能的情况下,再创建自己的队列;
创建一个新的任务队列和与之相关的工作者线程,需要使用下面函数,name参数用于该内核线程的命名;
1 #define create_workqueue(name) \ 2 alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name)) 3 #define create_freezable_workqueue(name) \ 4 alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND | \ 5 WQ_MEM_RECLAIM, 1, (name)) 6 #define create_singlethread_workqueue(name) \ 7 alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
这样就会在每个处理器上创建工作者线程,并准备好开始处理工作之前的准备工作;
创建一个工作的时候无须考虑工作队列的类型,在创建之后,可以调用下面的函数;这些函数与schedule_work()相似,唯一的区别是它们针对给定的工作队列而不是缺省的队列进行操作;
1 bool queue_work(struct workqueue_struct *wq, 2 struct work_struct *work) 3 4 bool queue_delayed_work(struct workqueue_struct *wq, 5 struct delayed_work *dwork, 6 unsigned long delay)
刷新指定的工作队列可以使用下面函数:
1 void flush_workqueue(struct workqueue_struct *wq);
结束对工作队列的使用后,可以使用下面函数释放资源:
1 void destroy_workqueue(struct workqueue_struct *wq);