工作队列的设计思想可以类比于现实中的生产流水线 :流水线相当于工作队列中的worklist链表,加工部件相当于中断发生 时所产生的工作序列,工人就是工作者线程。当中断发生时,内核将本次中断延后执行的工作序列,放到worklist工作链表中,唤醒 工作者线程执行工作。工作者线程在执行时可能阻塞;当 worklist 中的工作处理完毕后,工作者线程进入空闲状态。从 Linux 2.5.41内核引入工作队列直到2.6.35内核, 其运行机制没有大的改动,主要缺点是,在有N个CPU的计算机中,每当创建一个工作队 列时,就创建N条流水线,为每条流水线创建一个工作者线程,内核可以向这N条流水线提交该种类的工作(由响应中断的CPU决 定)。因此,如果创建X个工作队列,则需要创建N * X个工作者线程,但是只有当流水线上有工作时,线程才运行,其余流水线上的线程都处于空闲状态。大量的线程需要消耗线程的ID资源和大量内存,同时也会增加调度器的负担。这种情况在拥有大量 CPU的超级计算机上显得尤为浪费。另一方面,一条流水线上只有一个工作者线程,因此同一流水线上工作的处理是严格串行 的,严重制约处理的效率。 为适应大规模多处理器硬件平台,提高工作队列的处理效率,从2.6.36内核开始对工作队列进行彻底的改造。在新的工作队列 机制中,内核始终维持N + 1条“工作流水线”,即全局每CPU工作队列gcwq(Global Percpu Workqueue,详见1.1节)。新机制的流水 线是通用的,所有来自同一个CPU的中断所产生的工作序列,都放在这条流水线上。每条流水线上的工作者线程“按需分配”,即当 一个工作者线程阻塞时,可以让另一个工作者线程来处理该流水线上剩余的工作。当流水线上需要新的工作者线程时,就创建新线 程;而当流水线上线程过多时,就销毁线程。同一条流水线上可以有多个工作同时被指派给多个工作者线程,当然任何时刻一条流 水线上只有一个工作者线程在运行。这种新的工作队列机制称为受控并发工作队列(Concurrency Managed Workqueue).
如果在workqueue中添加dump_stack,打印信息中将会有一行workqueue信息,格式是
Workqueue: $worqueue_name $work_function_name
比如,系统queue名为events,work函数为work_for_cpu_fn
![](https://img-blog.csdnimg.cn/img_convert/66a225731fa3929a0fe6496ce614c4f5.png)
模块自定义workqueue:kacpi_hotplug,函数为 acpi_hotplug_work_fn
![](https://img-blog.csdnimg.cn/img_convert/be6dca4f6022210cb97bba44f2a0222c.png)
driver自定义workqueue
![](https://img-blog.csdnimg.cn/img_convert/509d6cf41cead088e14f55330b49b0ab.png)
系统workqueue:
![](https://img-blog.csdnimg.cn/img_convert/fab77df40f10ea74d0e660789bb348a6.png)
默认的schedule_work_on,schedule_delayed_work_on,flush_scheduled_work,schedule_work都是在默认的系统workqueue events上执行的。
dump stack信息也是来源于dump_stack->__dump_stack->dump_stack_print_info->print_worker_info.
print_worker_info函数会对任务进行判断,如果不是worker线程,直接退出,worker线程的标志是任务的struct task_struct->flags成员设置了PF_WQ_WORKER标志。
![](https://img-blog.csdnimg.cn/img_convert/f99b07cd6c590abe6a8dabef9d1a79b1.png)
内核中很多特殊线程有特殊标志(Per process flags),比如IDLE任务,kswapd任务,虚拟CPU任务,内核任务,SWAPWRITE任务, VCPU(KVM Virtualization) thread等等,在内核中要加以利用。
![](https://img-blog.csdnimg.cn/img_convert/3ce7f21c80555ba6bf05fca8c3040ba2.png)
workqueue和CPU的绑定是如何操作的?
注意下面的kthread_bind_mask函数
![](https://img-blog.csdnimg.cn/img_convert/68b34f8dd6b79d63488d795deea690b1.png)
实质上是通过kthreadd内核线程(PID为2,地位特殊)来创建内核woker线程,kthreadd是所有内核线程的父线程,具体可参考博客:Linux内核进程,线程,进程组,会话组织模型以及进程管理_linux内核态有几个进程_papaofdoudou的博客-CSDN博客
![](https://img-blog.csdnimg.cn/img_convert/d36a0c6dd107949450446b45a1c1f1c9.png)
系统启动的时候逐个创建每个CPU上的worker thread
![](https://img-blog.csdnimg.cn/img_convert/74472dcdddf53270460371c45d2cb1fd.png)
优先级/affinity如何设置?
内核线程默认的调度测试是NORMAL(其实就是CFS,优先级为0)设置完优先级,继续使用用户传进来的参数cpu_all_mask来设置cpu affinity.
![](https://img-blog.csdnimg.cn/img_convert/bafd0dbcdd92b5abf319fc9a96550980.png)
虽然创建会设置优先级,但这并不意味着内核线程不可以更改优先级和调度策略。在线程内部可以继续执行sched_setscheduler_nocheck函数来修改默认的调度策略和优先级。
![](https://img-blog.csdnimg.cn/img_convert/18e888768935ebd39490f78c0509025a.png)
比如中断线程化使用的内核线程,调度类型被设为FIFO,优先级也被提高。
![](https://img-blog.csdnimg.cn/img_convert/a1f9beae7ace5414cb75cb264bfc9e25.png)
创建内核线程的接口:
常用的kthead_run,kthread_create其实是有实现与被实现,调用与被调用,封装与被封装的关系的。不外乎几种:
![](https://img-blog.csdnimg.cn/img_convert/ee9bdf5177a7fe3028f6a06159116be3.png)
workqueue的优先级
使用命令
$ ps -eo class,pid,ppid,args
查看进程的调度策略
![](https://img-blog.csdnimg.cn/img_convert/192db529b428bb327a9b483363cc50b1.png)
所以基本上可以看到,所有的工作队列任务都是CFS调度方法
![](https://img-blog.csdnimg.cn/img_convert/ca9275b9aabd43771cf2705fb7267314.png)
![](https://img-blog.csdnimg.cn/img_convert/d55b0cc8ca1898d2bdb3ab9490555308.png)
worker初始化过程
start_kernel->workqueue_init_early创建初始化system_wq等工作队列。
![](https://img-blog.csdnimg.cn/img_convert/82619c924afe395072de05a91d0cd460.png)
然后在workqueue_init中为此workqueue创建worker thread.
workqueue_init->wq_update_unbound_numa->alloc_unbound_pwq->get_unbound_pool->create_worker->worker->task = kthread_create_on_node(worker_thread,...);
![](https://img-blog.csdnimg.cn/img_convert/1bd04344170e75640c2d9a216af18074.png)
新创建的worker线程加入到work pool的idle_list中
![](https://img-blog.csdnimg.cn/img_convert/ad6bd7c9fb6e88491844e6da26efe3f7.png)
create_worker 后设置affinity, 优先级。每个pool一个优先级,一类affinity.归属于同一个POOL的worker有着相同的优先级和affinity属性。
![](https://img-blog.csdnimg.cn/img_convert/de905f736c4e5a9989e721888c808adb.png)
linux系统有哪些workqueue
Linux Kernel提供了alloc_workqueue函数用于创建workqueue,内核bringup阶段创建了一些系统级workqueue.用户驱动也可以根据需要申请自己的workqueue.
所有的workqueue被链入全局workqueues中,但是由于它是静态结构,无法从外部访问,再加上workqueue的类型struct workqueue_struct是内核私有类型,所以只能通过HACK的方式获取LIST上的内容:
正常运行时,内和的WORKQUEU列表如下
[ 154.326426] proc_seq_read line 10799 enter, read pos 0 size 131072.
[ 154.326431] list_all_workqueues line 5266, workqueue events.
[ 154.326433] list_all_workqueues line 5266, workqueue events_highpri.
[ 154.326434] list_all_workqueues line 5266, workqueue events_long.
[ 154.326436] list_all_workqueues line 5266, workqueue events_unbound.
[ 154.326437] list_all_workqueues line 5266, workqueue events_freezable.
[ 154.326438] list_all_workqueues line 5266, workqueue events_power_efficient.
[ 154.326440] list_all_workqueues line 5266, workqueue events_freezable_power_.
[ 154.326441] list_all_workqueues line 5266, workqueue rcu_gp.
[ 154.326442] list_all_workqueues line 5266, workqueue rcu_par_gp.
[ 154.326444] list_all_workqueues line 5266, workqueue mm_percpu_wq.
[ 154.326446] list_all_workqueues line 5266, workqueue cpuset_migrate_mm.
[ 154.326448] list_all_workqueues line 5266, workqueue netns.
[ 154.326450] list_all_workqueues line 5266, workqueue pm.
[ 154.326452] list_all_workqueues line 5266, workqueue cgroup_destroy.
[ 154.326453] list_all_workqueues line 5266, workqueue cgroup_pidlist_destroy.
[ 154.326455] list_all_workqueues line 5266, workqueue writeback.
[ 154.326456] list_all_workqueues line 5266, workqueue cgwb_release.
[ 154.326458] list_all_workqueues line 5266, workqueue memcg_kmem_cache.
[ 154.326459] list_all_workqueues line 5266, workqueue kintegrityd.
[ 154.326461] list_all_workqueues line 5266, workqueue kblockd.
[ 154.326462] list_all_workqueues line 5266, workqueue blkcg_punt_bio.
[ 154.326464] list_all_workqueues line 5266, workqueue kacpid.
[ 154.326466] list_all_workqueues line 5266, workqueue kacpi_notify.
[ 154.326467] list_all_workqueues line 5266, workqueue kacpi_hotplug.
[ 154.326468] list_all_workqueues line 5266, workqueue kec.
[ 154.326470] list_all_workqueues line 5266, workqueue kec_query.
[ 154.326471] list_all_workqueues line 5266, workqueue tpm_dev_wq.
[ 154.326473] list_all_workqueues line 5266, workqueue ata_sff.
[ 154.326474] list_all_workqueues line 5266, workqueue usb_hub_wq.
[ 154.326476] list_all_workqueues line 5266, workqueue md.
[ 154.326477] list_all_workqueues line 5266, workqueue md_misc.
[ 154.326479] list_all_workqueues line 5266, workqueue edac-poller.
[ 154.326480] list_all_workqueues line 5266, workqueue efi_rts_wq.
[ 154.326481] list_all_workqueues line 5266, workqueue devfreq_wq.
[ 154.326483] list_all_workqueues line 5266, workqueue tc_filter_workqueue.
[ 154.326484] list_all_workqueues line 5266, workqueue inode_switch_wbs.
[ 154.326486] list_all_workqueues line 5266, workqueue kthrotld.
[ 154.326487] list_all_workqueues line 5266, workqueue acpi_thermal_pm.
[ 154.326489] list_all_workqueues line 5266, workqueue vfio-irqfd-cleanup.
[ 154.326490] list_all_workqueues line 5266, workqueue kdmremove.
[ 154.326492] list_all_workqueues line 5266, workqueue sock_diag_events.
[ 154.326493] list_all_workqueues line 5266, workqueue ipv6_addrconf.
[ 154.326495] list_all_workqueues line 5266, workqueue kstrp.
[ 154.326496] list_all_workqueues line 5266, workqueue fscrypt_read_queue.
[ 154.326497] list_all_workqueues line 5266, workqueue fsverity_read_queue.
[ 154.326499] list_all_workqueues line 5266, workqueue charger_manager.
[ 154.326501] list_all_workqueues line 5266, workqueue scsi_tmf_0.
[ 154.326502] list_all_workqueues line 5266, workqueue scsi_tmf_1.
[ 154.326504] list_all_workqueues line 5266, workqueue scsi_tmf_2.
[ 154.326505] list_all_workqueues line 5266, workqueue scsi_tmf_3.
[ 154.326507] list_all_workqueues line 5266, workqueue ext4-rsv-conversion.
[ 154.326509] list_all_workqueues line 5266, workqueue rpciod.
[ 154.326510] list_all_workqueues line 5266, workqueue xprtiod.
[ 154.326512] list_all_workqueues line 5266, workqueue kmemstick.
[ 154.326513] list_all_workqueues line 5266, workqueue cfg80211.
[ 154.326515] list_all_workqueues line 5266, workqueue cryptd.
[ 154.326516] list_all_workqueues line 5266, workqueue kvm-irqfd-cleanup.
[ 154.326518] list_all_workqueues line 5266, workqueue i915.
[ 154.326519] list_all_workqueues line 5266, workqueue i915-dp.
[ 154.326521] list_all_workqueues line 5266, workqueue i915_modeset.
[ 154.326522] list_all_workqueues line 5266, workqueue i915-userptr-acquire.
[ 154.326524] list_all_workqueues line 5266, workqueue phy0.
[ 154.326526] proc_seq_read line 10801 exit, read pos 0 size 131072,ret = 0.
you can also get the workqueue information from "sys/devices/virtual/workqueue"
only the workqueue that set the "WQ_SYSFS" has the sysfs file entry:
创建了设备节点的workqueue会在/sys/devices/virtual/下创建workqueue目录,目录中的文件是以所有具备WQ_SYSFS属性的WORKQUEU名为文件名的设备目录,并且/sys/bus/workqueue/devices下的链接指向这些设备目录:
/sys/bus/workqueue/drivers目录为空,代表这些workqueue设备是伪设备,不需要驱动。
为workqueue创建设备文件的目的是可以通过sysfs对workqueue进行动态配置,比如配置工作队列writeback的affinity属性,可以如下操作:
worker和worker_pool之间的关系
worker_pool是worker的容器,可以从worker_pool中找到空闲的WORKER:
struct worker是对一个执行worker的内核线程对象的抽象,struct worker对象中保存有对应内核线程的struct task_struct对象
在没有工作执行的时候,struct worker对应的内核线程会进入休眠状态:
struct work_struct被添加到哪里?
work被添加到struct worker_pool的worklist链表中,而 worker_pool 中有很多可以执行work的kernel thread.
新创建的worker会被加入到worker_pool的worker(attach)和idle_list字段中:
挑选wok并执行:
挑选从worker_pool的worklist中进行,得到一个work后调用process_one_work执行。
查看系统workqueue信息
echo t > /proc/sysrq-trigger
内核实现函数是show_workqueue_state:
参考资料
workqueue浅析_workqueue pid-CSDN博客
https://core.ac.uk/download/41458911.pdf