Workqueue 是内核里面很重要的一个机制,特别是内核驱动,一般的小型任务 (work) 都不会自己起一个线程来处理,而是扔到 Workqueue 中处理。Workqueue 的主要工作就是用进程上下文来处理内核中大量的小任务。
所以 Workqueue 的主要设计思想:一个是并行,多个 work 不要相互阻塞;另外一个是节省资源,多个 work 尽量共享资源 ( 进程、调度、内存 ),不要造成系统过多的资源浪费。
为了实现的设计思想,workqueue 的设计实现也更新了很多版本。最新的 workqueue 实现叫做 CMWQ(Concurrency Managed Workqueue),也就是用更加智能的算法来实现“并行和节省”。新版本的 workqueue 创建函数改成 alloc_workqueue(),旧版本的函数 create_workqueue() 逐渐会被被废弃。
使用工作队列分成两种情况:一种是利用系统自有的共享工作队列;另一种是创建自定义的工作队列。
函数解析:#struct workqueue_struct *alloc_workqueue(char *name, unsigned int flags, int max_active);(1)name:为工作队列的名字,而不像 2.6.36 之前实际是为工作队列服务的内核线程的名字。(2)flag 指明工作队列的属性,可以设定的标记如下:WQ_NON_REENTRANT:默认情况下,工作队列只是确保在同一CPU上不可重入,即工作项不能在同一CPU上被多个工作者线程并发执行,但容许在多个CPU上并发执行。但该标志标明在多个CPU上也是不可重入的,工作项将在一个不可重入工作队列中排队,并确保至多在一个系统范围内的工作者线程被执行。WQ_UNBOUND:工作项被放入一个由特定gcwq服务的未限定工作队列,该客户工作者线程没有被限定到特定的CPU,这样,未限定工作者队列就像简单的执行上下文一般,没有并发管理。未限定的 gcwq 试图尽可能快的执行工作项。WQ_FREEZEABLE:可冻结 wq 参与系统的暂停操作。该工作队列的工作项将被暂停,除非被唤醒,否者没有新的工作项被执行。WQ_MEM_RECLAIM:所有的工作队列可能在内存回收路径上被使用。使用该标志则保证至少有一个执行上下文而不管在任何内存压力之下。WQ_HIGHPRI:高优先级的工作项将被排练在队列头上,并且执行时不考虑并发级别;换句话说,只要资源可用,高优先级的工作项将尽可能快的执行。高优先工作项之间依据提交的顺序被执行。WQ_CPU_INTENSIVE:
所以 Workqueue 的主要设计思想:一个是并行,多个 work 不要相互阻塞;另外一个是节省资源,多个 work 尽量共享资源 ( 进程、调度、内存 ),不要造成系统过多的资源浪费。
为了实现的设计思想,workqueue 的设计实现也更新了很多版本。最新的 workqueue 实现叫做 CMWQ(Concurrency Managed Workqueue),也就是用更加智能的算法来实现“并行和节省”。新版本的 workqueue 创建函数改成 alloc_workqueue(),旧版本的函数 create_workqueue() 逐渐会被被废弃。
使用工作队列分成两种情况:一种是利用系统自有的共享工作队列;另一种是创建自定义的工作队列。
#系统共享的工作队列机制
(1)定义一个工作处理函数,函数声明为:
static void my_work_func(struct work_struct *work);
(2)定义一个工作,并为其指定工作处理函数:(有两种类型的work,如下)
/* ①定义普通的工作--work */
struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
/* ②定义可以延时提交的工作--delay work */
struct delayed_work my_delayed_work;
INIT_DELAYED_WORK(&my_delayed_work, my_work_func);
(3)将工作添加到系统的共享队列:
/* ①将普通的work添加到工作队列中,立即提交 */
schedule_work(&my_work);
/* ②延时hz个时钟滴答后再提交工作 */
int delay = 5;
unsigned int hz = round_jiffies_relative(HZ * delay);
schedule_delayed_work(&my_delayed_work, hz);
工作执行完成后会自动从系统的共享队列中删除。
(4)终止工作:
/* ①终止普通工作队列中的某一个work */
cancel_work_sync(&my_work);
/* ②终止延时工作队列中的某一个work */
cancel_delayed_work_sync(&my_delayed_work);
#自定义工作队列
(1)创建一个自定义工作队列
struct workqueue_struct *my_wq = alloc_workqueue(“my_work_queue”,WQ_UNBOUND|WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0)
(2)定义一个工作处理函数,函数声明为:
static void my_work_func(struct work_struct *work);
(3)定义一个工作,并为其指定工作处理函数:
/* ①定义普通的工作 */
struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
/* ②定义可以延时提交的工作 */
struct delayed_work my_delayed_work;
INIT_DELAYED_WORK(&my_delayed_work, my_work_func);
(4)将工作添加到自己的工作队列:
/* ①立即提交工作 */
queue_work(my_wq, &my_work);
/* ②延时hz个时钟滴答后再提交工作 */
int delay = 5;
unsigned int hz = round_jiffies_relative(HZ * delay);
queue_delayed_work(my_wq, &my_delayed_work, hz);
(5)销毁工作队列
/* 销毁工作队列 */
if (my_wq){
destroy_workqueue(my_wq);
my_wq = NULL;
}
(6)清理任务
/* 等待工作队列中的任务完成 */
flush_workqueue(my_wq);
/*******************************************************************************************************************/
例如:
#我们自己定义了一个请求工作,(其中work作为我们请求工作的一个成员)
struct readahead_req{
unsigned long ino;
pid_t pid;
struct address_space *mapping;
/*struct file_ra_state *ra;*/
struct file *filp;
struct page *page;
pgoff_t offset;
unsigned long req_size;
struct readahead_req *pre;
struct readahead_req *next;
//struct delayed_work my_delayed_work; /* 用于延时工作队列 */
struct work_struct my_work; /* 用于普通工作队列 */
};
#定义工作处理函数
int async_readahead(struct work_struct *work)
{
/* 用于延时工作队列 */
//struct readahead_req *req = container_of(work, struct readahead_req, my_delayed_work.work);
//注意第一个参数work不要动,第三个work.work为delayed_work.work
/* 用于普通工作队列 */
struct readahead_req *req = container_of(work, struct readahead_req, my_work);
}
#定义一个自定义工作队列
/* 定义延时工作队列 */
//struct workqueue_struct *my_delayed_wq = create_workqueue("my_delayed_queue");
/* 定义普通工作队列 */
struct workqueue_struct *my_wq = alloc_workqueue(“my_queue”,WQ_UNBOUND|WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0)
#初始化队列
struct readahead_req *req = (struct readahead_req *)kmalloc(struct readahead_req);
/* 用于延时工作队列 */
//INIT_DELAYED_WORK(&req->my_delayed_work, async_readahead);
/* 用于普通工作队列 */
INIT_WORK(&req->my_work, async_readahead);
#入队列
/* 用于延时工作队列 */
queue_delayed_work(&req->my_delayed_work);//好用,亲测
//schedule_delayed_work(&req->my_delayed_work); //未用过
/* 用于普通工作队列 */
queue_work(my_wq, &req->my_work);
#取消一个工作
/* 用于延时工作队列 */
cancel_delayed_work_sync(&req->my_delayed_work);
cancel_delayed_work(&req->my_delayed_work);
/* 用于普通工作队列 */
cancel_work_sync(&req->my_work);
#清理任务
/* 用于延时工作队列 */
flush_workqueue(my_delayed_wq);
flush_workqueue(my_wq);
#销毁工作队列
destroy_workqueue(my_delayed_wq);
destroy_workqueue(my_wq);
/* 一般如下使用 */
cancel_delayed_work_sync(&req->my_delayed_work);
flush_workqueue(my_delayed_wq);
if (my_delayed_wq)
{
destroy_workqueue(my_delayed_wq);
my_delayed_wq = NULL;
}
/**************************************************************************************************************/
函数解析:#struct workqueue_struct *alloc_workqueue(char *name, unsigned int flags, int max_active);(1)name:为工作队列的名字,而不像 2.6.36 之前实际是为工作队列服务的内核线程的名字。(2)flag 指明工作队列的属性,可以设定的标记如下:WQ_NON_REENTRANT:默认情况下,工作队列只是确保在同一CPU上不可重入,即工作项不能在同一CPU上被多个工作者线程并发执行,但容许在多个CPU上并发执行。但该标志标明在多个CPU上也是不可重入的,工作项将在一个不可重入工作队列中排队,并确保至多在一个系统范围内的工作者线程被执行。WQ_UNBOUND:工作项被放入一个由特定gcwq服务的未限定工作队列,该客户工作者线程没有被限定到特定的CPU,这样,未限定工作者队列就像简单的执行上下文一般,没有并发管理。未限定的 gcwq 试图尽可能快的执行工作项。WQ_FREEZEABLE:可冻结 wq 参与系统的暂停操作。该工作队列的工作项将被暂停,除非被唤醒,否者没有新的工作项被执行。WQ_MEM_RECLAIM:所有的工作队列可能在内存回收路径上被使用。使用该标志则保证至少有一个执行上下文而不管在任何内存压力之下。WQ_HIGHPRI:高优先级的工作项将被排练在队列头上,并且执行时不考虑并发级别;换句话说,只要资源可用,高优先级的工作项将尽可能快的执行。高优先工作项之间依据提交的顺序被执行。WQ_CPU_INTENSIVE:
CPU密集的工作项对并发级别并无贡献,换句话说,可运行的CPU密集型工作项将不阻止其它工作项。这对于限定得工作项非常有用,因为它期望更多的CPU时钟周期,所以将它们的执行调度交给系统调度器。
WQ_MAX_ACTIVE: I like 512, better ideas?WQ_MAX_UNBOUND_PER_CPU: 4 * #cpus for unbound wqWQ_DFL_ACTIVE:等于WQ_MAX_ACTIVE / 2,
(3)max_active:决定了一个 wq 在 per-CPU 上能执行的最大工作项。比如 max_active 设置为 16 表示一个工作队列上最多 16 个工作项能同时在 per-CPU 上同时执行。当前实行中,对所有限定工作队列,max_active 的最大值是 512,而设定为 0 时表示是 256;而对于未限定工作队列,该最大值为:MAX[512,4 * num_possible_cpus() ],除非有特别的理由需要限流或者其它原因,一般设定为 0 就可以了。参考资料:https://www.ibm.com/developerworks/cn/linux/l-cn-cncrrc-mngd-wkq/