WQ_UNBOUND
该flag设定的WQ不绑定到CPU,其work将被一个特殊的CGWQ进行服务,该CGWQ上的worker不绑定任何CPU。unbound WQ牺牲了CPU亲和性,主要用于下场景:
并发级别需求的波动非常大,如果使用bound WQ则会在不同CPU上创建大量的worker,并且这些worker大部分时间都是空闲的。
长时间运行的CPU密集型工作可以由系统调度程序更好的管理。
WQ_FREEZABLE
可冻结的WQ在系统suspend操作的freeze阶段,暂停新的work执行直到解冻。
WQ_MEM_RECLAIM
可能用于内存回收路径的WQ必须设置该flag。在内存紧张的时候也会保证至少有一个可执行的上下文用于该WQ。
WQ_HIGHPRI
高优先级的WQ的work会被放入GCWQ的高优先级线程池。高优先级的线程池的线程拥有高nice级别。普通的线程池和高优先级的线程池彼此独立,互相不影响。
WQ_CPU_INTENSIVE
设置为CPU密集型的WQ的work不会影响并发级别,即CPU密集型的work执行时并不会阻止同一个线程池里其他WQ的work的执行。这对希望独占CPU周期的work非常有用,由系统调度程序调度他们的执行。如果不设置该标记,则独占CPU周期的work会导致同一个线程池里其他WQ的work得不到执行。
由于同一由CMWQ的并发管理进行调度,当非密集型的WQ的work运行过程中,也会导致密集型的WQ的work被推迟。该flag仅适用于bound的WQ,对unbound的WQ无效。
max_active
max_active用于指定WQ在每个CPU上最大的执行上下文个数,即并发处理的work个数。目前对于bound WQ,max_active最大可以设置为512,如果max_active入参为0,则使用默认值256。对于unbound WQ,最大值为512和4*cpu核数两个里面较大的值。
对于希望使用STWQ的使用者,可以设置max_active为1,并且设置WQ_UNBOUND标识。这样整个系统里只有一个该WQ上的work正在执行。
struct workqueue_struct
函数alloc_workqueue()返回一个指向struct workqueue_struct的指针,代表一个workqueue。如下所示:
struct workqueue_struct *wq = alloc_workqueue("wq-name", WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
struct work_struct
数据结构struct work_struct定义了一个work,通过通过INIT_WORK系列宏定义初始化work,设置执行的函数。如下所示:
struct work_struct work;
void worker_func(struct work_struct *work);
INIT_WORK(&work, worker_func);
struct delayed_work
数据结构struct delayed_work定义了一个延迟work,延迟work通过设置定时器的方式延迟将work放入队列。如下所示为其数据结构定义:
struct delayed_work {
struct work_struct work;
struct timer_list timer;
/* target workqueue and CPU ->timer uses to queue ->work */
struct workqueue_struct *wq;
int cpu;
};
可以看到delayed_work由一个work和一个定时器组成。delayed_work通过INIT_DELAYED_WORK系列宏定义进行初始化。如下所示:
struct delayed_work work;
void worker_func(struct work_struct *work);
INIT_DELAYED_WORK(&work, worker_func);
queue_work_on()
函数queue_work_on()将work放入workqueue队列,其定义如下:
extern bool queue_work_on(int cpu, struct workqueue_struct *wq,
struct work_struct *work);
函数queue_delayed_work将delayed_work在延迟delay个jiffies之后放入workqueue队列,其定义如下:
staticinline bool queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsignedlong delay)
因为work是由通用的工作线程执行的,因此需要一些技巧来定位workqueue使用者的一些错误行为。
worker线程通过ps可以看到:
root 5671 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/0:1]
root 5672 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/1:2]
root 5673 0.0 0.0 0 0 ? S 12:12 0:00 [kworker/0:0]
root 5674 0.0 0.0 0 0 ? S 12:13 0:00 [kworker/1:0]
如果某个kworker疯了,占用CPU非常高,可能有如下两种原因:
大量的work正在正在提交调度;
某个work占用过多CPU;
第1种原因可以通过tracing机制来跟踪:
$ echo workqueue:workqueue_queue_work > /sys/kernel/debug/tracing/set_event
$ cat /sys/kernel/debug/tracing/trace_pipe > out.txt
如果某个worker忙于循环将大量work进行调度,通过输出的work里的函数可以找到谁提交了大量的work。
第2种原因可以通过打印出worker的堆栈空间来分析是哪个work的函数正在处理:
$ cat /proc/THE_OFFENDING_KWORKER/stack
总结