那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
2、工作、工作队列和工作者线程
如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。
表示工作的数据结构
工作用<linux/workqueue.h>中定义的work_struct结构表示:
struct work_struct{
unsigned long pending;
struct list_head entry;
void (*func) (void *);
void *data;
void *wq_data;
struct timer_list timer;
};
这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。
3、 创建推后的工作
要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过DECLARE_WORK在编译时静态地建该结构:
DECLARE_WORK(name, void (*func) (void *), void *data);
这样就会静态地创建一个名为name,待执行函数为func,参数为data的work_struct结构。
同样,也可以在运行时通过指针创建一个工作:
INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data);
这会动态地初始化一个由work指向的工作。
4、工作队列中待执行的函数
工作队列待执行的函数原型是:
void work_handler(void *data)
这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。
5、 对工作进行调度
现在工作已经被创建,我们可以调度它了。想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用
schedule_work(&work);
work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。
有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度它在指定的时间执行:
schedule_delayed_work(&work, delay);
这时,&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。
6 、创建自己的工作队列
使用内核提供的共享列队,列队是保持顺序执行的,做完一个工作才做下一个,如果一个工作内有耗时大的处理如阻塞等待信号或锁,那么后面的工作都不会执行。如果你不喜欢排队或不好意思让别人等太久,那么可以创建自己的工作者线程,所有工作可以加入自己创建的工作列队,列队中的工作运行在创建的工作者线程中。
创建工作列队使用3个宏 成功后返回workqueue_struct *指针,并创建了工作者线程。三个宏主要区别在后面两个参数singlethread和freezeable,singlethread为0时会为每个cpu上创建一个工作者线程,为1时只在当前运行的cpu上创建一个工作者线程。freezeable会影响内核线程结构体thread_info的PF_NOFREEZE标记
if (!cwq->freezeable)
current->flags |= PF_NOFREEZE;
set_user_nice(current, -5);
在线程函数内设置了测试点如下
if (cwq->freezeable)
try_to_freeze();
如果设置了PF_NOFREEZE这个flag,那么系统挂起时候这个进程不会被挂起。
主要函数
#define create_workqueue(name) __create_workqueue((name), 0, 0) //多处理器时会为每个cpu创建一个工作者线程
#define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1) //只创建一个工作者线程,系统挂起是线程也挂起
#define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0) //只创建一个工作者线程,系统挂起是线程线程不挂起
以上三个宏调用__create_workqueue函数定义
extern struct workqueue_struct *__create_workqueue(const char *name,int singlethread, int freezeable);
释放创建的工作列队资源
void destroy_workqueue(struct workqueue_struct *wq)
延时调用指定工作列队的工作
queue_delayed_work(struct workqueue_struct *wq,struct delay_struct *work, unsigned long delay)
取消指定工作列队的延时工作
cancel_delayed_work(struct delay_struct *work)
将工作加入工作列队进行调度
queue_work(struct workqueue_struct *wq, struct work_struct *work)
等待列队中的任务全部执行完毕。
void flush_workqueue(struct workqueue_struct *wq);
7、 工作队列的简单应用
#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>
static struct workqueue_struct *queue = NULL;
static struct work_struct work;
static void work_handler(struct work_struct *data)
{
printk(KERN_ALERT "work handler function./n");
}
static int __init test_init(void)
{
queue = create_singlethread_workqueue("helloworld");
if (!queue)
goto err;
INIT_WORK(&work, work_handler);
schedule_work(&work);
return 0;
err:
return -1;
}
static void __exit test_exit(void)
{
destroy_workqueue(queue);
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);