linux workqueue实现原理

2 篇文章 0 订阅

工作队列的设计思想可以类比于现实中的生产流水线 :流水线相当于工作队列中的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

模块自定义workqueue:kacpi_hotplug,函数为 acpi_hotplug_work_fn

driver自定义workqueue

系统workqueue:

默认的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标志。

内核中很多特殊线程有特殊标志(Per process flags),比如IDLE任务,kswapd任务,虚拟CPU任务,内核任务,SWAPWRITE任务, VCPU(KVM Virtualization) thread等等,在内核中要加以利用。


workqueue和CPU的绑定是如何操作的?

注意下面的kthread_bind_mask函数

实质上是通过kthreadd内核线程(PID为2,地位特殊)来创建内核woker线程,kthreadd是所有内核线程的父线程,具体可参考博客:Linux内核进程,线程,进程组,会话组织模型以及进程管理_linux内核态有几个进程_papaofdoudou的博客-CSDN博客

系统启动的时候逐个创建每个CPU上的worker thread

优先级/affinity如何设置?

内核线程默认的调度测试是NORMAL(其实就是CFS,优先级为0)设置完优先级,继续使用用户传进来的参数cpu_all_mask来设置cpu affinity.

虽然创建会设置优先级,但这并不意味着内核线程不可以更改优先级和调度策略。在线程内部可以继续执行sched_setscheduler_nocheck函数来修改默认的调度策略和优先级。

比如中断线程化使用的内核线程,调度类型被设为FIFO,优先级也被提高。

创建内核线程的接口:

常用的kthead_run,kthread_create其实是有实现与被实现,调用与被调用,封装与被封装的关系的。不外乎几种:

workqueue的优先级

使用命令


$ ps -eo class,pid,ppid,args

查看进程的调度策略

所以基本上可以看到,所有的工作队列任务都是CFS调度方法

worker初始化过程

start_kernel->workqueue_init_early创建初始化system_wq等工作队列。

然后在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,...);

新创建的worker线程加入到work pool的idle_list中

create_worker 后设置affinity, 优先级。每个pool一个优先级,一类affinity.归属于同一个POOL的worker有着相同的优先级和affinity属性。

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

结束!
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值