背景

之前在触摸驱动(比如汇顶等)的代码里,会看到INIT_WORK等相关字眼,只知道是和工作队列相关,没有深入研究学习。
最近在看蓝牙HCI相关代码中,又看到了INIT_WORK等,觉得工作队列(workqueue)需要好好看看,并记录下

工作队列通常用于将耗时工作滞后处理,比如中断处理的下半部耗时操作,等, 中断相关的处理机制可见:《Kernel之中断处理底半部机制》。

最新的 workqueue 实现叫做 CMWQ(Concurrency Managed Workqueue),也就是用更加智能的算法来实现“并行和节省”。

Kernel: v5.4.18

几个概念

很容易混淆的几个概念:

  • work :工作。
  • workqueue :工作的集合。workqueue 和 work 是一对多的关系。
  • worker :工人。在代码中 worker 对应一个 work_thread() 内核线程。
  • worker_pool:工人的集合。worker_pool 和 worker 是一对多的关系。
  • pwq(pool_workqueue):中间人 / 中介,负责建立起 workqueue 和 worker_pool 之间的关系。workqueue 和 pwq 是一对多的关系,pwq 和 worker_pool 是一对一的关系。

相关关系图:
avatar

当前work内核线程

查看系统当前work线程
$ ps -ef | grep work

root         600       2  0 10:36 ?        00:00:00 [kworker/0:1H-kblockd]
root         741       2  0 10:36 ?        00:00:00 [kworker/9:1H-kblockd]
root         766       2  0 10:36 ?        00:00:00 [kworker/u25:2-rb_allocator]
root         785       2  0 10:36 ?        00:00:00 [kworker/6:1H-kblockd]
root         792       2  0 10:36 ?        00:00:00 [kworker/11:4-cgroup_destroy]
root         837       2  0 10:36 ?        00:00:00 [kworker/4:1H-kblockd]
root         838       2  0 10:36 ?        00:00:00 [kworker/u25:3-rb_allocator]

worker线程被命名成了”kworker/n:x”的格式,其中n是worker线程所在的CPU的编号,x是其在worker pool中的编号,如果带了”H”后缀,说明这是高优先级的worker pool
还有一些带”u”前缀的,它表示”unbound”,意思是这个worker线程不和任何的CPU绑定,而是被所有CPU共享,这种设计主要是为了增加灵活性。”u”后面的这个数字也不再表示CPU的编号,而是表示由这些unbound的worker线程组成的worker pool的ID号

主要相关函数

主要源代码: /kernel/workqueue.c

INIT_WORK系列, 工作初始化

定义了work和work对应的操作_func,将其绑定:

struct work_struct my_work; /* 定义一个work */
void my_work_func(struct work_struct *work)  /* 定义一个work处理函数 */

/* 将work和work处理函数绑定 */
INIT_WORK(&my_work, my_work_func)/* 延时work */
INIT_DELAYED_WORK(&my_work, my_work_func)
struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

work调度系列

/* 调度work执行 */
//对工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。
//工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。
static inline bool schedule_work(struct work_struct *work)
{
	return queue_work(system_wq, work); //调度在缺省系统队列
}

//延迟执行工作,调度在缺省系统队列
static inline bool schedule_delayed_work(struct delayed_work *dwork,
					 unsigned long delay)
{
	return queue_delayed_work(system_wq, dwork, delay);//延时调度在缺省系统队列
}

//延迟执行工作在指定的cpu,调度在缺省系统队列
static inline bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork,
					    unsigned long delay)
{
	return queue_delayed_work_on(cpu, system_wq, dwork, delay);
}

//类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
static inline bool queue_work(struct workqueue_struct *wq,
			      struct work_struct *work)
{
	return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}

//延迟执行工作, 调度在指定的工作队列, queue work on a workqueue after delay
static inline bool queue_delayed_work(struct workqueue_struct *wq,
				      struct delayed_work *dwork,
				      unsigned long delay)
{
	return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}

//延迟执行工作在指定的cpu,调度在指定的工作队列
bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay);

自定义工作队列

//自定义工作队列,创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。
//旧版本的创建函数 create_workqueue() 已经被新版本的 alloc_workqueue() 取代
#define create_workqueue(name)						\
	alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))

#define create_singlethread_workqueue(name)				\
	alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

//allocate an ordered workqueue
#define alloc_ordered_workqueue(fmt, flags, args...)			\
	alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED |		\
			__WQ_ORDERED_EXPLICIT | (flags), 1, ##args)

//allocate a workqueue
struct workqueue_struct *alloc_workqueue(const char *fmt,
					 unsigned int flags,
					 int max_active, ...);

刷新工作队列

//刷新缺省工作队列。此函数会一直等待,直到队列中的所有工作都被执行。
static inline void flush_scheduled_work(void)
{
	flush_workqueue(system_wq);  //刷新缺省的系统工作队列
}

//刷新指定工作队列。
void flush_workqueue(struct workqueue_struct *wq);

取消及注销工作和队列

//取消延迟工作
bool cancel_delayed_work(struct delayed_work *dwork);
//cancel a delayed work and wait for it to finish
bool cancel_delayed_work_sync(struct delayed_work *dwork);

//释放创建的工作队列。
void destroy_workqueue(struct workqueue_struct *wq);

//终止队列中的任务或者阻塞任务直到回调结束(如果处理程序已经在处理该任务)
bool cancel_work_sync(struct work_struct *work);;

参考

原理及实现机制:
https://zhuanlan.zhihu.com/p/94561631
http://kernel.meizu.com/linux-workqueue.html
http://www.wowotech.net/irq_subsystem/workqueue.html