workqueues学习

linux workqueues学习

https://www.ibm.com/developerworks/cn/linux/l-cn-cncrrc-mngd-wkq/index.html
内核中提供了许多机制来提供延迟执行,如中断的下半部处理可延迟中断上下文中的部分工作;定时器可指定延迟一定时间后执行某工作;工作队列则允许在进程上下文环境下延迟执行等。

工作队列 (workqueues)

  • workqueues:所有工作项被 ( 需要被执行的工作 ) 排列于该队列,因此称作工作队列 (workqueues) 。
  • worker thread:工作者线程 (worker thread) 是一个用于执行工作队列中各个工作项的内核线程,当工作队列中没有工作项时,该线程将变为 idle 状态。
  • single threaded(ST)::工作者线程的表现形式之一,在系统范围内,只有一个工作者线程为工作队列服务
  • multi threaded(MT):工作者线程的表现形式之一,在多 CPU 系统上每个 CPU 上都有一个工作者线程为工作队列服务

对于使用者,基本上只需要做 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. 有导致死锁的倾向



并发可管理工作队列 (Concurrency-managed workqueues)

在 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值