https://www.ibm.com/developerworks/cn/linux/l-cn-cncrrc-mngd-wkq/index.html
内核中提供了许多机制来提供延迟执行,如中断的下半部处理可延迟中断上下文中的部分工作;定时器可指定延迟一定时间后执行某工作;工作队列则允许在进程上下文环境下延迟执行等。
对于使用者,基本上只需要做 3 件事情,依次为:
执行在进程上下文中,这样使得它可以睡眠,被调度及被抢占。
总体说来,工作队列和定时器函数的处理有点类似,都是延迟执行相关的回调函数,但和定时器处理函数不同的是定时器回调函数只执行一次 ( 当然可以在执行时再次注册以反复调用,但这需要显示的再次注册 ), 且执行定时器回调函数时在时钟中断环境 , 限制较多,因此回调函数不能太复杂;而工作队列是通过内核线程实现,一直有效,可重复执行,执行时可以休眠,因此工作队列非常适合处理那些不是很紧急的任务,如垃圾回收处理等。
2 种选择:要么使用内核已经提供的共享工作队列,要么自己创建工作队列。
如选择使用共享的工作队列,基本的步骤为:
1. 创建工作项
/*静态创建工作项*/
typedef void (*work_func_t)(struct work_struct *work);
DECLARE_WORK(name, func);
DECLARE_DELAYED_WORK(name, func);
//该系列宏静态创建一个以 name 命名的工作项,并设置了回调函数 func
/*动态创建工作项*/
INIT_WORK(struct work_struct work, work_func_t func);
PREPARE_WORK(struct work_struct work, work_func_t func);
INIT_DELAYED_WORK(struct delayed_work work, work_func_t func);
PREPARE_DELAYED_WORK(struct delayed_work work, work_func_t func);
//该系列宏在运行时初始化工作项 work,并设置了回调函数 func
2. 调度工作项
/*调度工作项*/
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
//将工作项添加到共享的工作队列,工作项随后在某个合适时机将被执行
如果因为某些原因,如需要执行的是个阻塞性质的任务而不愿或不能使用内核提供的共享工作队列,这时需要自己创建工作队列,则上述步骤和使用的接口则略有改变:
3. 创建工作队列
在 2.6.36 之前,内核中的每个工作队列都有一个专用的内核线程来为它服务,创建工作队列时,有 2 个选择,可选择系统范围内的 ST,也可选择每 CPU 一个内核线程的 MT,其接口如下:
create_singlethread_workqueue(name)
create_workqueue(name)
4. 创建工作项
int queue_work(workqueue_t *queue, work_t *work);
int queue_delayed_work(workqueue_t *queue, work_t *work, unsigned long delay);
/*向工作队列中提交工作项*/
它们都会将工作项 work 提交到工作队列 queue,但第二个函数确保最少延迟 delay jiffies 之后该工作才会被执行。对于 MT 的情况,当用 queue_work 向 cwq 上提交工作项节点时, 是哪个 active CPU 正在调用该函数,那么便向该 CPU 对应的 cwq 上的 worklist 上增加工作项节点。
假如你需要取消一个挂起的工作队列中的工作项 , 你可以调用:
int cancel_delayed_work(struct work_struct *work);
/*取消工作队列中挂起的工作项*/
5. 释放工作队列
当你结束对一个工作队列的使用后,你可以使用下面的函数释放相关资源:
void destroy_workqueue(struct workqueue_struct *queue);
/*释放工作队列*/
缺点:
1. 公共的共享工作队列中,其中一个阻塞,其他工作项将不能被执行
2. MT的工作队列导致内核线程数增加得很快,占用pid
3. 有导致死锁的倾向
在 2.6.36 之前的工作队列,其核心是每个工作队列都有专有的内核线程为其服务——系统范围内的 ST 或每个 CPU 都有一个内核线程的 MT。新的 cmwq 在实现上摒弃了这一点,不再有专有的线程与每个工作队列关联,事实上,现在变成了 Online CPU number + 1 个线程池来为工作队列服务,这样将线程的管理权实际上从工作队列的使用者交还给了内核。当一个工作项被创建以及排队,将在合适的时机被传递给其中一个线程,而 cmwq 最有意思的改变是:被提交到相同工作队列,相同 CPU 的工作项可能并发执行,这也是命名为并发可管理工作队列的原因。
struct workqueue_struct
*alloc_workqueue(char *name, unsigned int flags, int max_active);
/*cmwq中创建工作队列的后端接口*/
name:为工作队列的名字,而不像 2.6.36 之前实际是为工作队列服务的内核线程的名字。
flag 指明工作队列的属性,可以设定的标记如下:
- WQ_NON_REENTRANT:但该标志标明在多个 CPU 上也是不可重入的,工作项将在一个不可重入工作队列中排队,并确保至多在一个系统范围内的工作者线程被执行。
- WQ_UNBOUND:该客户工作者线程没有被限定到特定的 CPU, 试图尽可能快的执行工作项
- WQ_FREEZEABLE: 可冻结 wq 参与系统的暂停操作。
- WQ_MEM_RECLAIM :所有的工作队列可能在内存回收路径上被使用。使用该标志则保证至少有一个执行上下文而不管在任何内存压力之下。
- WQ_HIGHPRI: 高优先级的工作项将被排练在队列头上,并且执行时不考虑并发级别;换句话说,只要资源可用,高优先级的工作项将尽可能快的执行。高优先工作项之间依据提交的顺序被执行。
- WQ_CPU_INTENSIVE:CPU 密集的工作项对并发级别并无贡献。
max_active:决定了一个 wq 在 per-CPU 上能执行的最大工作项。
#define create_workqueue(name) \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
#define create_freezeable_workqueue(name) \
alloc_workqueue((name), WQ_FREEZEABLE | WQ_UNBOUND | WQ_MEM_RECLAIM, 1)
#define create_singlethread_workqueue(name) \
alloc_workqueue((name), WQ_UNBOUND | WQ_MEM_RECLAIM, 1)