Linux中的workqueue机制简化了内核线程的创建,通过调用workqueue的接口即可创建内核线程,而且可以根据当前系统CPU的个数创建相同数量的线程,使得多个线程能够并行处理事件。
工作队列允许内核函数(例如可延迟函数)激活,稍后由一种叫做工作者线程的内核线程来执行,它和可延迟函数有一定的区别:
不同点:
可延迟函数运行于中断上下文(不可睡眠),不一定在创建它的进程中运行。
工作队列中的函数运行于进程上下文(可睡眠),由内核线程来执行。
相同点:
因为可延迟函数运行时,不可能有正在运行的进程,而工作队列是由内核线程执行的,所以它们都不能访问用户地址空间。
工作队列的使用:
内核中通过下述结构体表示一个具体的工作(任务):
struct work_struct
{
unsigned long pending;//这个工作是否正在等待处理
struct list_head entry;//链接所有工作的链表,形成工作队列
void (*func)(void *);//处理函数
void *data;//传递给处理函数的参数
void *wq_data;//内部使用数据
struct timer_list timer;//延迟的工作队列所用到的定时器
};
这些工作(结构体)链接成的链表就是工作队列。工作者线程会在被唤醒时,执行链表上的所有工作,当一个工作被执行完后,相应的work_struct结构体会被删除。当这个工作链表上没有工作时,工作线程就会睡眠。
操作步骤:
1、创建
创建分为工作队列的创建和工作函数(任务)的创建.
(1)工作队列的创建需要有其描述符,它的数据结构是 workqueue_struct. 该结构定义在<linux/workqueue.h>中.这里我们不需要关心它的具体组成,内核已经写好了两个函数用于创建队列:
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct * workqueue_singlethread_workqueue(const char *name);
它们的区别在于实际处理器的多少,如果是单核处理器的话,他们毫无区别.
因为每个工作队列都有一个或多个(多核处理器)专用的进程(内核线程),这些进程运行提交到该工作队列函数. create_workqueue内核会在系统中的每个处理器上为该工作队列创建专用的线程. 这样,如果工作队列足够多的话,可能对系统的性能有所影响,而create_singlethread_workqueue则只会创建一个专用的线程. 所以,如果单个工作线程足够使用,推荐使用第二个函数来创建工作队列.
同样的,工作任务的创建也需要有其描述符,它的数据结构是work_struct.内核同样创建了几个宏用于创建工作任务:
DECLARE_WORK(name,void (*function)(void *),void *data);//用于在内核编译时使用.
INIT_WORK(struct work_struct *work,void(*function)(void *),void *data);//用于在系统运行时创建.首次创建时使用它.
PREPARE_WORK(struct work_struct *work,void(*function)(void *),void *data);//用于在系统运行时创建.没有INIT_WORK初始化彻底,因为它不会初始化用来将work_struct结构连接到工作队列的指针.如果结构已经被提交到工作队列,而只是需要修改该结构,则应该使用PREPARE_WORK而不是INIT_WORK.
2、添加任务:
将任务添加到自定义的工作队列,可使用如下两个函数之一:
int queue_work(struct workqueue_struct *queue,struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue,struct work_struct *work,unsigned long delay);
它们都会将work添加到自定义的queue.但是如果使用queue_delayed_work,则实际工作至少会在经过指定的jiffies(由delay指定)之后才会执行.如果工作被成功添加到队列,则上述函数的返回值为1,返回值为非零表示给定的work_struct结构已经等待在该队列中,不能再次加入该队列.
将任务添加到内核全局的工作队列,则可使用如下两个函数之一:
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay);
3、删除:
结束对工作队列的使用后,可调用下面的函数释放相关资源:
void destroy_workqueue(struct workqueue_struct *queue);
如果想继续使用该工作队列,可刷新该工作队列(清理其中的所有任务):
void flush_workqueue(struct workqueue_struct *wq);//清理指定工作队列中的所有任务
void flush_scheduled_work(void);//清理内核全局工作队列中的所有任务
注意:以上函数的调用者都会阻塞直到操作完成为止,如果任务正在被执行,则会等待其执行完。
Workqueue编程接口:
序号 | 接口函数 | 说明 |
1 | create_workqueue | 用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。输入参数: @name:workqueue的名称 |
2 | create_singlethread_workqueue | 用于创建workqueue,只创建一个内核线程。输入参数: @name:workqueue名称 |
3 | destroy_workqueue | 释放workqueue队列。输入参数: @ workqueue_struct:需要释放的workqueue队列指针 |
4 | schedule_work | 调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue——keventd_wq输入参数: @ work_struct:具体任务对象指针 |
5 | schedule_delayed_work | 延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间,输入参数: @work_struct:具体任务对象指针 @delay:延迟时间 |
6 | queue_work | 调度执行一个指定workqueue中的任务。输入参数: @ workqueue_struct:指定的workqueue指针 @work_struct:具体任务对象指针 |
7 | queue_delayed_work | 延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。 |