工作队列¶
- 日期:
2010 年 9 月
- 作者:
Tejun Heo <tj@kernel.org>
- 作者:
Florian Mickler <florian@mickler.org>
介绍¶
在许多情况下,需要异步进程执行上下文,而工作队列 (wq) API 是此类情况最常用的机制。
当需要这样的异步执行上下文时,将描述要执行的函数的“工作项”放入队列中。一个独立的线程充当异步执行上下文。该队列称为“工作队列”,而该线程称为“工作者”。
当工作队列上有工作项时,工作者会逐个执行与工作项关联的函数。当工作队列上没有剩余工作项时,工作者将变为空闲。当新的工作项入队时,工作者将再次开始执行。
为什么使用并发管理的工作队列?¶
在原始的 wq 实现中,一个多线程 (MT) wq 每个 CPU 有一个工作线程,而一个单线程 (ST) wq 在整个系统只有一个工作线程。一个 MT wq 需要保留与 CPU 数量相同的工作者数量。多年来,内核增长了很多 MT wq 用户,并且随着 CPU 核心数量的不断增加,一些系统在启动时就饱和了默认的 32k PID 空间。
尽管 MT wq 浪费了很多资源,但提供的并发级别并不令人满意。ST 和 MT wq 都存在这种限制,尽管在 MT 上不太严重。每个 wq 都维护自己的单独工作者池。MT wq 每个 CPU 只能提供一个执行上下文,而 ST wq 在整个系统中只能提供一个执行上下文。工作项必须争夺这些非常有限的执行上下文,从而导致各种问题,包括围绕单个执行上下文的死锁倾向。
所提供的并发级别与资源使用之间的紧张关系也迫使用户做出不必要的权衡,例如 libata 选择使用 ST wq 进行轮询 PIO,并接受不必要的限制,即两个轮询 PIO 不能同时进行。由于 MT wq 没有提供更好的并发性,因此需要更高并发性的用户(例如异步或 fscache)必须实现自己的线程池。
并发管理的工作队列 (cmwq) 是 wq 的重新实现,重点关注以下目标。
保持与原始工作队列 API 的兼容性。
使用所有 wq 共享的每个 CPU 统一工作者池,以按需提供灵活的并发级别,而不会浪费大量资源。
自动调节工作者池和并发级别,以便 API 用户不必担心这些细节。
设计¶
为了简化函数的异步执行,引入了一个新的抽象概念,即工作项。
工作项是一个简单的结构,其中包含指向要异步执行的函数的指针。每当驱动程序或子系统希望异步执行函数时,都必须设置一个指向该函数的工作项,并将该工作项排队到工作队列中。
可以在线程或 BH (softirq) 上下文中执行工作项。
对于线程化的工作队列,称为 [k]workers 的专用线程会逐个执行队列中的函数。如果没有工作排队,则工作线程将变为空闲。这些工作线程在工作者池中进行管理。
cmwq 设计区分了子系统和驱动程序将工作项排队的面向用户的工作队列,以及管理工作者池和处理排队的工作项的后端机制。
每个可能的 CPU 有两个工作者池,一个用于普通工作项,另一个用于高优先级工作项,以及一些额外的工作者池,用于服务于在未绑定工作队列上排队的工作项 - 这些后备池的数量是动态的。
BH 工作队列使用相同的框架。但是,由于只能有一个并发执行上下文,因此无需担心并发性。每个 CPU 的 BH 工作者池仅包含一个伪工作者,该伪工作者代表 BH 执行上下文。可以将 BH 工作队列视为 softirq 的便捷接口。
子系统和驱动程序可以通过特殊的工作队列 API 函数创建和排队工作项,只要它们认为合适。它们可以通过设置放置工作项的工作队列上的标志来影响工作项的执行方式的某些方面。这些标志包括 CPU 局部性、并发限制、优先级等等。要获得详细的概述,请参阅下面 alloc_workqueue()
的 API 说明。
当工作项排队到工作队列时,将根据队列参数和工作队列属性确定目标工作者池,并将其附加到工作者池的共享工作列表中。例如,除非另有明确规定,否则,绑定工作队列的工作项将被排队到与发出者正在运行的 CPU 关联的普通或高优先级工作者池的工作列表上。
对于任何线程池实现,管理并发级别(有多少个执行上下文处于活动状态)都是一个重要问题。cmwq 尝试将并发性保持在最小但足够的水平。最小是为了节省资源,而足够则表示系统已充分发挥其全部容量。
绑定到实际 CPU 的每个工作者池都通过挂钩到调度程序来实现并发管理。每当活动工作者唤醒或睡眠时,都会通知工作者池,并跟踪当前可运行的工作者数量。通常,不希望工作项占用 CPU 并消耗许多周期。这意味着保持足够的并发性以防止工作处理停滞应该是最佳的。只要 CPU 上有一个或多个可运行的工作者,工作者池就不会开始执行新的工作,但是,当最后一个正在运行的工作者进入睡眠状态时,它会立即调度一个新的工作者,以便在有待处理的工作项时 CPU 不会闲置。这允许使用最少数量的工作者,而不会损失执行带宽。
保持空闲的工作者存在不会产生其他成本,除了 kthread 的内存空间外,因此 cmwq 会保留空闲的工作者一段时间,然后才会将其杀死。
对于未绑定的工作队列,后备池的数量是动态的。可以使用 apply_workqueue_attrs()
为未绑定工作队列分配自定义属性,并且工作队列将自动创建与属性匹配的后备工作者池。调节并发级别的责任由用户承担。还有一个标志可以标记绑定的 wq 以忽略并发管理。有关详细信息,请参阅 API 部分。
向前进度保证依赖于在需要更多执行上下文时可以创建工作者,这反过来通过使用救援工作者来保证。在处理内存回收的代码路径上可能使用的所有工作项都必须排队到具有保留的救援工作者以在内存压力下执行的 wq 上。否则,工作者池可能会死锁,等待执行上下文释放。
应用程序编程接口 (API)¶
alloc_workqueue()
分配一个 wq。原始的 create_*workqueue()
函数已被弃用,并计划删除。alloc_workqueue()
接受三个参数 - @name
、@flags
和 @max_active
。@name
是 wq 的名称,如果存在救援线程,则也用作救援线程的名称。
wq 不再管理执行资源,而是充当向前进度保证、刷新和工作项属性的域。@flags
和 @max_active
控制工作项如何分配执行资源、调度和执行。
flags
¶
WQ_BH
BH 工作队列可以被视为 softirq 的便捷接口。BH 工作队列始终是每个 CPU 的,并且所有 BH 工作项都按排队顺序在排队 CPU 的 softirq 上下文中执行。
所有 BH 工作队列都必须具有 0
max_active
并且WQ_HIGHPRI
是唯一允许的附加标志。BH 工作项无法睡眠。支持所有其他功能,例如延迟排队、刷新和取消。
WQ_UNBOUND
提交到无界 wq 的工作项由特殊的 worker 池提供服务,这些 worker 池托管未绑定到任何特定 CPU 的 worker。这使得 wq 的行为类似于一个简单的执行上下文提供程序,而没有并发管理。无界 worker 池会尝试尽快开始执行工作项。无界 wq 牺牲了局部性,但对于以下情况很有用。
预计并发级别需求会大幅波动,并且当发布者在不同 CPU 之间跳跃时,使用有界 wq 可能会最终在不同 CPU 上创建大量大部分未使用的 worker。
长时间运行的 CPU 密集型工作负载,可以通过系统调度程序更好地管理。
WQ_FREEZABLE
可冻结的 wq 参与系统挂起操作的冻结阶段。wq 上的工作项会被耗尽,并且在解冻之前不会开始执行新的工作项。
WQ_MEM_RECLAIM
所有可能在内存回收路径中使用的 wq 必须 设置此标志。无论内存压力如何,wq 都保证至少有一个执行上下文。
WQ_HIGHPRI
高优先级 wq 的工作项会排队到目标 CPU 的高优先级 worker 池。高优先级 worker 池由具有提升的 nice 级别的工作线程提供服务。
请注意,普通和高优先级 worker 池不会相互交互。每个池都维护其单独的 worker 池,并在其 worker 之间实现并发管理。
WQ_CPU_INTENSIVE
CPU 密集型 wq 的工作项不会影响并发级别。换句话说,可运行的 CPU 密集型工作项不会阻止同一 worker 池中的其他工作项开始执行。这对于预计会占用大量 CPU 周期的有界工作项很有用,以便它们的执行由系统调度程序调节。
尽管 CPU 密集型工作项不会影响并发级别,但其执行的开始仍然受并发管理控制,并且可运行的非 CPU 密集型工作项可能会延迟 CPU 密集型工作项的执行。
此标志对于无界 wq 没有意义。
max_active
¶
@max_active
确定每个 CPU 可以分配给 wq 工作项的最大执行上下文数。例如,当 @max_active
为 16 时,每个 CPU 最多可以同时执行该 wq 的 16 个工作项。这始终是一个按 CPU 属性,即使对于无界工作队列也是如此。
@max_active
的最大限制为 2048,当指定 0 时使用的默认值为 1024。这些值选择得足够高,以便它们不是限制因素,同时在失控情况下提供保护。
wq 的活动工作项数量通常由 wq 的用户控制,更具体地说,由用户可能同时排队多少个工作项控制。除非有特定的限制活动工作项数量的需求,否则建议指定 “0”。
一些用户依赖于严格的执行顺序,其中任何给定时间只有一个工作项在运行,并且工作项按排队顺序处理。虽然 @max_active
为 1 和 WQ_UNBOUND
的组合过去用于实现此行为,但现在情况并非如此。请改用 alloc_ordered_workqueue()
。
示例执行场景¶
以下示例执行场景尝试说明 cmwq 在不同配置下的行为方式。
工作项 w0、w1、w2 在同一 CPU 上排队到有界 wq q0。w0 占用 CPU 5 毫秒,然后休眠 10 毫秒,然后再次占用 CPU 5 毫秒,然后完成。w1 和 w2 占用 CPU 5 毫秒,然后休眠 10 毫秒。
忽略所有其他任务、工作和处理开销,并假设简单的 FIFO 调度,以下是原始 wq 可能发生的简化事件序列之一。
TIME IN MSECS EVENT
0 w0 starts and burns CPU
5 w0 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 starts and burns CPU
25 w1 sleeps
35 w1 wakes up and finishes
35 w2 starts and burns CPU
40 w2 sleeps
50 w2 wakes up and finishes
使用 @max_active
>= 3 的 cmwq,
TIME IN MSECS EVENT
0 w0 starts and burns CPU
5 w0 sleeps
5 w1 starts and burns CPU
10 w1 sleeps
10 w2 starts and burns CPU
15 w2 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 wakes up and finishes
25 w2 wakes up and finishes
如果 @max_active
== 2,
TIME IN MSECS EVENT
0 w0 starts and burns CPU
5 w0 sleeps
5 w1 starts and burns CPU
10 w1 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 wakes up and finishes
20 w2 starts and burns CPU
25 w2 sleeps
35 w2 wakes up and finishes
现在,假设 w1 和 w2 排队到设置了 WQ_CPU_INTENSIVE
的不同 wq q1,
TIME IN MSECS EVENT
0 w0 starts and burns CPU
5 w0 sleeps
5 w1 and w2 start and burn CPU
10 w1 sleeps
15 w2 sleeps
15 w0 wakes up and burns CPU
20 w0 finishes
20 w1 wakes up and finishes
25 w2 wakes up and finishes
指南¶
如果 wq 可能会处理在内存回收期间使用的工作项,请不要忘记使用
WQ_MEM_RECLAIM
。每个设置了WQ_MEM_RECLAIM
的 wq 都有一个为其保留的执行上下文。如果内存回收期间使用的多个工作项之间存在依赖关系,则应将它们排队到单独的 wq,每个 wq 都设置了WQ_MEM_RECLAIM
。除非需要严格的排序,否则无需使用 ST wq。
除非有特定需求,否则建议对 @max_active 使用 0。在大多数用例中,并发级别通常保持在默认限制之下。
wq 用作前进保证的域(
WQ_MEM_RECLAIM
、刷新和工作项属性)。未参与内存回收且不需要作为一组工作项的一部分刷新的工作项,并且不需要任何特殊属性的工作项,可以使用系统 wq 之一。使用专用 wq 和系统 wq 之间的执行特性没有差异。注意:如果某些东西可能生成超过 @max_active 的未完成工作项(请对您的生产者进行压力测试),则可能会使系统 wq 饱和,并可能导致死锁。它应该使用自己的专用工作队列,而不是系统 wq。
除非工作项预计会消耗大量的 CPU 周期,否则由于 wq 操作和工作项执行的局部性更高,因此使用有界 wq 通常是有益的。
关联范围¶
无界工作队列会根据其关联范围对 CPU 进行分组,以提高缓存局部性。例如,如果工作队列使用默认的“cache”关联范围,它将根据最后一级缓存边界对 CPU 进行分组。在工作队列上排队的工作项将被分配给与发布 CPU 共享最后一级缓存的 CPU 之一上的 worker。启动后,根据范围的 affinity_strict
设置,worker 可能允许或不允许移出范围。
工作队列当前支持以下关联范围。
default
使用模块参数
workqueue.default_affinity_scope
中的范围,该参数始终设置为以下范围之一。cpu
CPU 不分组。在一个 CPU 上发布的工作项由同一 CPU 上的 worker 处理。这使得无界工作队列的行为类似于没有并发管理的每个 CPU 工作队列。
smt
CPU 根据 SMT 边界进行分组。这通常意味着每个物理 CPU 核心的逻辑线程被分组在一起。
cache
CPU 根据缓存边界进行分组。使用哪个特定的缓存边界由 arch 代码确定。在许多情况下使用 L3。这是默认的关联范围。
numa
CPU 根据 NUMA 边界进行分组。
system
所有 CPU 都放在同一组中。工作队列不会努力在靠近发布 CPU 的 CPU 上处理工作项。
可以使用模块参数 workqueue.default_affinity_scope
更改默认关联范围,并可以使用 apply_workqueue_attrs()
更改特定工作队列的关联范围。
如果设置了 WQ_SYSFS
,则工作队列在其 /sys/devices/virtual/workqueue/WQ_NAME/
目录下将具有以下与关联范围相关的接口文件。
affinity_scope
读取以查看当前关联范围。写入以进行更改。
当 default 是当前范围时,读取此文件还将显示括号中的当前有效范围,例如,
default (cache)
。affinity_strict
默认为 0,表示关联范围不严格。当工作项开始执行时,工作队列会尽最大努力确保 worker 在其关联范围内,这称为遣返。启动后,调度程序可以根据需要自由地将 worker 移动到系统中的任何位置。这使得在仍然能够在必要和可用的情况下利用其他 CPU 的同时,可以从范围局部性中受益。
如果设置为 1,则保证该范围内的所有 worker 始终在该范围内。当跨越关联范围具有其他含义时,例如在功耗或工作负载隔离方面,这可能会很有用。严格的 NUMA 范围也可以用于匹配较旧内核的工作队列行为。
关联范围和性能¶
如果无界工作队列的行为在绝大多数用例中都是最佳的,而无需进一步调整,那就太理想了。不幸的是,在当前的内核中,局部性和利用率之间存在明显的权衡,当工作队列被大量使用时,需要进行显式配置。
更高的局部性会带来更高的效率,即在相同的 CPU 周期消耗量下执行更多的工作。但是,如果发布者没有将工作项足够地分布在关联范围中,更高的局部性也可能导致较低的整体系统利用率。以下使用 dm-crypt 进行的性能测试清楚地说明了这种权衡。
这些测试在具有 12 核/24 线程的 CPU 上运行,这些线程分布在四个 L3 缓存(AMD Ryzen 9 3900x)中。为了保持一致性,关闭了 CPU 时钟加速。 /dev/dm-0
是在 NVME SSD (Samsung 990 PRO) 上创建的 dm-crypt 设备,并使用默认设置的 cryptsetup
打开。
场景 1:足够的发布者和工作分布在整个机器上¶
使用的命令
$ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k --ioengine=libaio \
--iodepth=64 --runtime=60 --numjobs=24 --time_based --group_reporting \
--name=iops-test-job --verify=sha512
有 24 个发布者,每个发布者同时发布 64 个 IO。--verify=sha512
使 fio
每次生成并读回内容,这使得发布者和 kcryptd
之间的执行局部性很重要。以下是根据 kcryptd
上不同的关联范围设置在五次运行中测得的读取带宽和 CPU 利用率。带宽以 MiBps 为单位,CPU 利用率以百分比为单位。
关联 |
带宽 (MiBps) |
CPU 利用率 (%) |
---|---|---|
system |
1159.40 ±1.34 |
99.31 ±0.02 |
cache |
1166.40 ±0.89 |
99.34 ±0.01 |
缓存(严格) |
1166.00 ±0.71 |
99.35 ±0.01 |
在系统中分布足够多的发行者的情况下,“缓存”无论是否严格,都没有缺点。所有三种配置都会使整个机器饱和,但由于改进的局部性,缓存关联的配置性能提高了 0.6%。
场景 2:发行者较少,但足以使饱和的工作量¶
使用的命令
$ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k \
--ioengine=libaio --iodepth=64 --runtime=60 --numjobs=8 \
--time_based --group_reporting --name=iops-test-job --verify=sha512
与前一个场景的唯一区别是 --numjobs=8
。发行者的数量是原来的三分之一,但总工作量仍然足以使系统饱和。
关联 |
带宽 (MiBps) |
CPU 利用率 (%) |
---|---|---|
system |
1155.40 ±0.89 |
97.41 ±0.05 |
cache |
1154.40 ±1.14 |
96.15 ±0.09 |
缓存(严格) |
1112.00 ±4.64 |
93.26 ±0.35 |
这足以使系统饱和。“system”和“cache”都接近使机器饱和,但没有完全饱和。“cache”使用的 CPU 较少,但更高的效率使其带宽与“system”相同。
八个发行者在四个 L3 缓存范围内移动仍然允许“cache(严格)”在很大程度上使机器饱和,但现在工作守恒的损失开始造成损害,带宽损失了 3.7%。
场景 3:发行者更少,不足以使饱和的工作量¶
使用的命令
$ fio --filename=/dev/dm-0 --direct=1 --rw=randrw --bs=32k \
--ioengine=libaio --iodepth=64 --runtime=60 --numjobs=4 \
--time_based --group_reporting --name=iops-test-job --verify=sha512
同样,唯一的区别是 --numjobs=4
。随着发行者数量减少到四个,现在没有足够的工作量使整个系统饱和,带宽变得依赖于完成延迟。
关联 |
带宽 (MiBps) |
CPU 利用率 (%) |
---|---|---|
system |
993.60 ±1.82 |
75.49 ±0.06 |
cache |
973.40 ±1.52 |
74.90 ±0.07 |
缓存(严格) |
828.20 ±4.49 |
66.84 ±0.29 |
现在,局部性和利用率之间的权衡更加清晰。“cache”的带宽比“system”低 2%,“cache(struct)”的带宽则大幅降低了 20%。
结论和建议¶
在上述实验中,“cache”关联范围相对于“system”的效率优势虽然一致且明显,但很小。然而,这种影响取决于范围之间的距离,并且在具有更复杂拓扑结构的处理器中可能更为明显。
虽然在某些情况下工作守恒的损失会造成损害,但它比“cache(严格)”好得多,并且最大化工作队列利用率不太可能是常见情况。因此,“cache”是无界池的默认关联范围。
由于没有一个选项在大多数情况下都非常出色,因此建议可能消耗大量 CPU 的工作队列用法使用
apply_workqueue_attrs()
配置工作队列和/或启用WQ_SYSFS
。具有严格“cpu”关联范围的无界工作队列的行为与
WQ_CPU_INTENSIVE
每 CPU 工作队列相同。后者没有真正的优势,而无界工作队列提供了更大的灵活性。关联范围是在 Linux v6.5 中引入的。要模拟之前的行为,请使用严格的“numa”关联范围。
非严格关联范围中工作守恒的损失可能源于调度程序。没有理论上的理由说明为什么内核不能在大多数情况下做正确的事情并保持工作守恒。因此,未来的调度程序改进可能会使大多数这些可调参数变得不必要。
检查配置¶
使用 tools/workqueue/wq_dump.py 来检查无界 CPU 关联配置、工作线程池以及工作队列如何映射到池
$ tools/workqueue/wq_dump.py
Affinity Scopes
===============
wq_unbound_cpumask=0000000f
CPU
nr_pods 4
pod_cpus [0]=00000001 [1]=00000002 [2]=00000004 [3]=00000008
pod_node [0]=0 [1]=0 [2]=1 [3]=1
cpu_pod [0]=0 [1]=1 [2]=2 [3]=3
SMT
nr_pods 4
pod_cpus [0]=00000001 [1]=00000002 [2]=00000004 [3]=00000008
pod_node [0]=0 [1]=0 [2]=1 [3]=1
cpu_pod [0]=0 [1]=1 [2]=2 [3]=3
CACHE (default)
nr_pods 2
pod_cpus [0]=00000003 [1]=0000000c
pod_node [0]=0 [1]=1
cpu_pod [0]=0 [1]=0 [2]=1 [3]=1
NUMA
nr_pods 2
pod_cpus [0]=00000003 [1]=0000000c
pod_node [0]=0 [1]=1
cpu_pod [0]=0 [1]=0 [2]=1 [3]=1
SYSTEM
nr_pods 1
pod_cpus [0]=0000000f
pod_node [0]=-1
cpu_pod [0]=0 [1]=0 [2]=0 [3]=0
Worker Pools
============
pool[00] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 0
pool[01] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 0
pool[02] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 1
pool[03] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 1
pool[04] ref= 1 nice= 0 idle/workers= 4/ 4 cpu= 2
pool[05] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 2
pool[06] ref= 1 nice= 0 idle/workers= 3/ 3 cpu= 3
pool[07] ref= 1 nice=-20 idle/workers= 2/ 2 cpu= 3
pool[08] ref=42 nice= 0 idle/workers= 6/ 6 cpus=0000000f
pool[09] ref=28 nice= 0 idle/workers= 3/ 3 cpus=00000003
pool[10] ref=28 nice= 0 idle/workers= 17/ 17 cpus=0000000c
pool[11] ref= 1 nice=-20 idle/workers= 1/ 1 cpus=0000000f
pool[12] ref= 2 nice=-20 idle/workers= 1/ 1 cpus=00000003
pool[13] ref= 2 nice=-20 idle/workers= 1/ 1 cpus=0000000c
Workqueue CPU -> pool
=====================
[ workqueue \ CPU 0 1 2 3 dfl]
events percpu 0 2 4 6
events_highpri percpu 1 3 5 7
events_long percpu 0 2 4 6
events_unbound unbound 9 9 10 10 8
events_freezable percpu 0 2 4 6
events_power_efficient percpu 0 2 4 6
events_freezable_pwr_ef percpu 0 2 4 6
rcu_gp percpu 0 2 4 6
rcu_par_gp percpu 0 2 4 6
slub_flushwq percpu 0 2 4 6
netns ordered 8 8 8 8 8
...
有关详细信息,请参阅命令的帮助消息。
监控¶
使用 tools/workqueue/wq_monitor.py 来监控工作队列操作
$ tools/workqueue/wq_monitor.py events
total infl CPUtime CPUhog CMW/RPR mayday rescued
events 18545 0 6.1 0 5 - -
events_highpri 8 0 0.0 0 0 - -
events_long 3 0 0.0 0 0 - -
events_unbound 38306 0 0.1 - 7 - -
events_freezable 0 0 0.0 0 0 - -
events_power_efficient 29598 0 0.2 0 0 - -
events_freezable_pwr_ef 10 0 0.0 0 0 - -
sock_diag_events 0 0 0.0 0 0 - -
total infl CPUtime CPUhog CMW/RPR mayday rescued
events 18548 0 6.1 0 5 - -
events_highpri 8 0 0.0 0 0 - -
events_long 3 0 0.0 0 0 - -
events_unbound 38322 0 0.1 - 7 - -
events_freezable 0 0 0.0 0 0 - -
events_power_efficient 29603 0 0.2 0 0 - -
events_freezable_pwr_ef 10 0 0.0 0 0 - -
sock_diag_events 0 0 0.0 0 0 - -
...
有关详细信息,请参阅命令的帮助消息。
调试¶
由于工作函数是由通用工作线程执行的,因此需要一些技巧来阐明行为不当的工作队列用户。
工作线程在进程列表中显示为
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),则可能存在两种类型的问题
以快速连续的方式调度某些内容
消耗大量 CPU 周期的单个工作项
第一个问题可以使用跟踪来跟踪
$ echo workqueue:workqueue_queue_work > /sys/kernel/tracing/set_event
$ cat /sys/kernel/tracing/trace_pipe > out.txt
(wait a few secs)
^C
如果某些内容在工作队列中忙于循环,它将主导输出,并且可以使用工作项函数来确定违规者。
对于第二种类型的问题,应该可以直接检查违规工作线程的堆栈跟踪。
$ cat /proc/THE_OFFENDING_KWORKER/stack
工作项的函数应该在堆栈跟踪中很容易看到。
非重入条件¶
如果工作项在排队后满足以下条件,则工作队列保证工作项不能重入
工作函数没有被更改。
没有人将工作项排队到另一个工作队列。
工作项没有被重新初始化。
换句话说,如果满足上述条件,则保证在任何给定时间,最多只能由一个系统范围的工作线程执行该工作项。
请注意,在自我函数中将工作项(排队到同一队列)重新排队不会违反这些条件,因此这样做是安全的。否则,在工作函数中打破条件时需要谨慎。
内核内联文档参考¶
-
struct workqueue_attrs¶
工作队列属性的结构。
定义:
struct workqueue_attrs {
int nice;
cpumask_var_t cpumask;
cpumask_var_t __pod_cpumask;
bool affn_strict;
enum wq_affn_scope affn_scope;
bool ordered;
};
成员
nice
nice 级别
cpumask
允许的 CPU
此工作队列中的工作项与这些 CPU 关联,并且不允许在其他 CPU 上执行。为工作队列提供服务的工作池必须具有相同的 cpumask。
__pod_cpumask
用于创建每个 pod 池的内部属性
仅供内部使用。
每个 pod 的无界工作线程池用于提高局部性。始终是 ->cpumask 的子集。一个工作队列可以与具有不相交的 __pod_cpumask 的多个工作线程池关联。对池的 __pod_cpumask 的强制执行是否严格取决于 affn_strict。
affn_strict
关联范围是严格的
如果清除,工作队列将尽最大努力在 __pod_cpumask 内启动工作线程,但调度程序可以随意将其迁移到外部。
如果设置,则只允许工作线程在 __pod_cpumask 内运行。
affn_scope
无界 CPU 关联范围
CPU pod 用于提高无界工作项的执行局部性。每个 wq_affn_scope 都有多种 pod 类型,并且系统中的每个 CPU 都属于每种 pod 类型中的一个 pod。属于同一 pod 的 CPU 共享工作线程池。例如,选择
WQ_AFFN_NUMA
会使工作队列为每个 NUMA 节点使用单独的工作线程池。ordered
工作项必须按照排队顺序一个接一个地执行
描述
这可以用于更改无界工作队列的属性。
-
work_pending¶
work_pending (work)
找出工作项当前是否正在挂起
参数
work
有问题的的工作项
-
delayed_work_pending¶
delayed_work_pending (w)
找出可延迟的工作项当前是否正在挂起
参数
w
有问题的的工作项
-
struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, ...)¶
分配工作队列
参数
const char *fmt
工作队列名称的 printf 格式
unsigned int flags
WQ_* 标志
int max_active
最大正在运行的工作项数,0 表示默认值
...
fmt 的参数
描述
对于每个 CPU 的工作队列,max_active 限制了每个 CPU 正在运行的工作项数。例如,max_active 为 1 表示每个 CPU 对于该工作队列最多可以执行一个工作项。
对于无界工作队列,max_active 限制了整个系统正在运行的工作项数。例如,max_active 为 16 表示在整个系统中,最多可以有 16 个工作项正在为该工作队列执行。
由于在多个 NUMA 节点之间为无界工作队列共享相同的活动计数器可能代价高昂,因此 max_active 会根据在线 CPU 数量的比例分布到每个 NUMA 节点,并独立强制执行。
根据在线 CPU 分布,一个节点最终可能会得到每个节点的 max_active,该值远低于 max_active,如果每个节点的并发限制低于该工作队列的最大互依赖工作项数,则可能导致死锁。
为了保证无论在线 CPU 分布如何都取得进展,保证每个节点上的并发限制等于或大于 min_active,后者设置为 min(max_active, WQ_DFL_MIN_ACTIVE
)。这意味着每个节点的 max_active 的总和可能大于 max_active。
有关 WQ_*
标志的详细信息,请参阅 工作队列。
返回
成功时指向已分配工作队列的指针,失败时为 NULL
。
-
struct workqueue_struct *alloc_workqueue_lockdep_map(const char *fmt, unsigned int flags, int max_active, struct lockdep_map *lockdep_map, ...)¶
分配具有用户定义的 lockdep_map 的工作队列
参数
const char *fmt
工作队列名称的 printf 格式
unsigned int flags
WQ_* 标志
int max_active
最大正在运行的工作项数,0 表示默认值
struct lockdep_map *lockdep_map
用户定义的 lockdep_map
...
fmt 的参数
描述
与 alloc_workqueue 相同,但使用用户定义的 lockdep_map。对于具有相同目的创建的工作队列,以及避免在每次创建工作队列时泄漏 lockdep_map 非常有用。
返回
成功时指向已分配工作队列的指针,失败时为 NULL
。
-
alloc_ordered_workqueue_lockdep_map¶
alloc_ordered_workqueue_lockdep_map (fmt, flags, lockdep_map, args...)
分配具有用户定义的 lockdep_map 的有序工作队列
参数
fmt
工作队列名称的 printf 格式
flags
WQ_* 标志(只有 WQ_FREEZABLE 和 WQ_MEM_RECLAIM 有意义)
lockdep_map
用户定义的 lockdep_map
args...
fmt 的参数
描述
与 alloc_ordered_workqueue 相同,但使用用户定义的 lockdep_map。对于具有相同目的创建的工作队列,以及避免在每次创建工作队列时泄漏 lockdep_map 非常有用。
返回
成功时指向已分配工作队列的指针,失败时为 NULL
。
-
alloc_ordered_workqueue¶
alloc_ordered_workqueue (fmt, flags, args...)
分配一个有序工作队列
参数
fmt
工作队列名称的 printf 格式
flags
WQ_* 标志(只有 WQ_FREEZABLE 和 WQ_MEM_RECLAIM 有意义)
args...
fmt 的参数
描述
分配一个有序工作队列。 有序工作队列在任何给定时间按照排队的顺序最多执行一个工作项。 它们被实现为具有 max_active 为 1 的无界工作队列。
返回
成功时指向已分配工作队列的指针,失败时为 NULL
。
-
bool queue_work(struct workqueue_struct *wq, struct work_struct *work)¶
在工作队列上排队工作
参数
struct workqueue_struct *wq
要使用的工作队列
struct work_struct *work
要排队的工作
描述
如果 work 已经在队列中,则返回 false
,否则返回 true
。
我们将工作排队到提交它的 CPU 上,但是如果 CPU 发生故障,它可以由另一个 CPU 处理。
内存排序属性:如果它返回 true
,则保证在程序顺序中在调用 queue_work()
之前的所有存储操作,在 work 执行时,对将执行 work 的 CPU 可见,例如:
{ x 最初为 0 }
CPU0 CPU1
WRITE_ONCE(x, 1); [ 正在执行 work ] r0 = queue_work(wq, work); r1 = READ_ONCE(x);
禁止:r0 == true && r1 == 0
-
bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)¶
延迟后在工作队列上排队工作
参数
struct workqueue_struct *wq
要使用的工作队列
struct delayed_work *dwork
要排队的延迟工作
unsigned long delay
排队前要等待的节拍数
描述
等效于 queue_delayed_work_on()
,但尝试使用本地 CPU。
-
bool mod_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)¶
修改延迟或排队一个延迟的工作
参数
struct workqueue_struct *wq
要使用的工作队列
struct delayed_work *dwork
要排队的工作
unsigned long delay
排队前要等待的节拍数
描述
在本地 CPU 上执行 mod_delayed_work_on()
。
-
bool schedule_work_on(int cpu, struct work_struct *work)¶
将工作任务放在特定的 CPU 上
参数
int cpu
将工作任务放置在其上的 CPU
struct work_struct *work
要完成的任务
描述
这会将一个任务放在特定的 CPU 上
-
bool schedule_work(struct work_struct *work)¶
将工作任务放在全局工作队列中
参数
struct work_struct *work
要完成的任务
描述
如果 work 已经在内核全局工作队列中,则返回 false
,否则返回 true
。
如果一个任务尚未排队,则将其放入内核全局工作队列中,否则将其保留在内核全局工作队列中的相同位置。
与 queue_work()
共享相同的内存排序属性,请参阅 queue_work()
的 DocBook 标头。
-
bool enable_and_queue_work(struct workqueue_struct *wq, struct work_struct *work)¶
在特定的工作队列上启用并排队一个工作项
参数
struct workqueue_struct *wq
目标工作队列
struct work_struct *work
要启用和排队的工作项
描述
此函数组合了 enable_work()
和 queue_work()
的操作,提供了一种在单个调用中启用和排队工作项的便捷方法。它在 work 上调用 enable_work()
,然后在禁用深度达到 0 时将其排队。如果禁用深度达到 0 且 work 已排队,则返回 true
,否则返回 false
。
请注意,当禁用深度达到零时,始终会排队 work。如果所需的行为是在 work 被禁用时仅在发生某些事件时才排队,则用户应实现必要的状态跟踪并在 enable_work()
之后执行显式条件排队。
-
bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork, unsigned long delay)¶
在延迟后将工作放入CPU的全局工作队列
参数
int cpu
要使用的CPU
struct delayed_work *dwork
要完成的任务
unsigned long delay
等待的jiffies数
描述
在等待给定时间后,此函数将一个作业放入指定CPU上的内核全局工作队列中。
-
bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)¶
在延迟后将工作任务放入全局工作队列
参数
struct delayed_work *dwork
要完成的任务
unsigned long delay
等待的jiffies数,或0表示立即执行
描述
在等待给定时间后,此函数将一个作业放入内核全局工作队列中。
-
for_each_pool¶
for_each_pool (pool, pi)
遍历系统中的所有worker_pool
参数
池
迭代游标
pi
用于迭代的整数
描述
必须在持有wq_pool_mutex或RCU读取锁定的情况下调用此函数。如果需要在锁定的范围之外使用池,则调用者负责保证池保持在线。
if/else子句仅用于lockdep断言,可以忽略。
-
for_each_pool_worker¶
for_each_pool_worker (worker, pool)
遍历worker_pool的所有工作线程
参数
工作线程
迭代游标
池
要迭代工作线程的worker_pool
描述
必须使用wq_pool_attach_mutex调用此函数。
if/else子句仅用于lockdep断言,可以忽略。
-
for_each_pwq¶
for_each_pwq (pwq, wq)
遍历指定工作队列的所有pool_workqueue
参数
pwq
迭代游标
wq
目标工作队列
描述
必须在持有wq->mutex或RCU读取锁定的情况下调用此函数。如果需要在锁定的范围之外使用pwq,则调用者负责保证pwq保持在线。
if/else子句仅用于lockdep断言,可以忽略。
-
int worker_pool_assign_id(struct worker_pool *pool)¶
分配ID并将其分配给**pool**
参数
struct worker_pool *pool
感兴趣的池指针
描述
如果成功分配并分配了[0, WORK_OFFQ_POOL_NONE)中的ID,则返回0,失败时返回-errno。
-
struct cpumask *unbound_effective_cpumask(struct workqueue_struct *wq)¶
未绑定工作队列的有效cpumask
参数
struct workqueue_struct *wq
感兴趣的工作队列
描述
**wq->unbound_attrs->cpumask**包含用户请求的cpumask,该cpumask使用wq_unbound_cpumask进行掩码,以确定有效的cpumask。默认的pwq始终映射到具有当前有效cpumask的池。
-
struct worker_pool *get_work_pool(struct work_struct *work)¶
返回给定工作关联的worker_pool
参数
struct work_struct *work
感兴趣的工作项
描述
池在wq_pool_mutex下创建和销毁,并允许在RCU读取锁下进行读取访问。因此,应该在wq_pool_mutex下或在rcu_read_lock()
区域内调用此函数。
只要上述锁定生效,就可以访问返回池的所有字段。如果需要在临界区之外使用返回的池,则调用者负责确保返回的池在线并保持在线。
返回
与**work**最后关联的worker_pool。如果没有,则为NULL
。
参数
struct worker *worker
自身
unsigned int flags
要设置的标志
描述
在**worker->flags**中设置**flags**,并相应地调整nr_running。
参数
struct worker *worker
自身
unsigned int flags
要清除的标志
描述
在**worker->flags**中清除**flags**,并相应地调整nr_running。
参数
struct worker *worker
正在进入空闲状态的工作线程
描述
**worker**正在进入空闲状态。如果需要,更新统计信息和空闲计时器。
锁定:raw_spin_lock_irq(pool->lock)。
参数
struct worker *worker
正在离开空闲状态的工作线程
描述
**worker**正在离开空闲状态。更新统计信息。
锁定:raw_spin_lock_irq(pool->lock)。
-
struct worker *find_worker_executing_work(struct worker_pool *pool, struct work_struct *work)¶
查找正在执行工作的工作线程
参数
struct worker_pool *pool
感兴趣的池
struct work_struct *work
要查找工作线程的工作
描述
通过搜索以**work**地址为键的**pool->busy_hash**,查找在**pool**上执行**work**的工作线程。为了使工作线程匹配,其当前执行应与**work**的地址及其工作函数匹配。这是为了避免在仍然执行时通过回收的工作项在不相关的工作执行之间产生不必要的依赖关系。
这有点棘手。工作项可能会在执行开始后被释放,并且没有任何东西阻止将释放的区域回收用于另一个工作项。如果相同的工作项地址在原始执行完成之前最终被重用,则工作队列将识别回收的工作项为当前正在执行,并使其等待直到当前执行完成,从而引入不必要的依赖关系。
此函数检查工作项地址和工作函数以避免误报。请注意,这并不完整,因为人们可能会构造一个工作函数,该函数可以通过回收的工作项将依赖关系引入自身。好吧,如果有人想如此糟糕地自食其果,我们只能做这么多,如果真的发生这种死锁,应该很容易找到罪魁祸首的工作函数。
上下文
raw_spin_lock_irq(pool->lock)。
返回
指向正在执行 work 的 worker 的指针,如果未找到则为 NULL
。
-
void move_linked_works(struct work_struct *work, struct list_head *head, struct work_struct **nextp)¶
将链接的 work 移动到列表
参数
struct work_struct *work
要调度的 work 系列的开始
struct list_head *head
要将 work 附加到的目标列表
struct work_struct **nextp
用于嵌套 worklist 遍历的 out 参数
描述
将从 work 开始的链接 work 调度到 head。要调度的 work 系列从 work 开始,并且包含其前驱中设置了 WORK_STRUCT_LINKED 的任何连续 work。有关 nextp 的详细信息,请参阅 assign_work()
。
上下文
raw_spin_lock_irq(pool->lock)。
-
bool assign_work(struct work_struct *work, struct worker *worker, struct work_struct **nextp)¶
将一个 work 项及其链接的 work 项分配给一个 worker
参数
struct work_struct *work
要分配的 work
struct worker *worker
要分配到的 worker
struct work_struct **nextp
用于嵌套 worklist 遍历的 out 参数
描述
将 work 及其链接的 work 项分配给 worker。如果 work 已经在同一池中的另一个 worker 执行,它将被转移到那里。
如果 nextp 不为 NULL,则它将被更新为指向最后一个调度的 work 的下一个 work。这允许 assign_work()
嵌套在 list_for_each_entry_safe()
中。
如果 work 成功分配给 worker,则返回 true
。如果 work 被转移到另一个已经在执行它的 worker,则返回 false
。
-
bool kick_pool(struct worker_pool *pool)¶
如有必要,唤醒一个空闲的 worker
参数
struct worker_pool *pool
要踢的池
描述
pool 可能有待处理的 work 项。如有必要,唤醒 worker。返回是否唤醒了一个 worker。
-
void wq_worker_running(struct task_struct *task)¶
一个 worker 再次运行
参数
struct task_struct *task
正在唤醒的任务
描述
当 worker 从 schedule() 返回时,将调用此函数
-
void wq_worker_sleeping(struct task_struct *task)¶
一个 worker 即将进入睡眠
参数
struct task_struct *task
即将进入睡眠的任务
描述
当繁忙的 worker 即将进入睡眠时,会从 schedule() 调用此函数。
-
void wq_worker_tick(struct task_struct *task)¶
当 kworker 运行时,发生了一个调度程序节拍
参数
struct task_struct *task
当前正在运行的任务
描述
从 sched_tick() 调用。我们在 IRQ 上下文中,可以安全地访问遵循“K”锁定规则的当前 worker 的字段。
-
work_func_t wq_worker_last_func(struct task_struct *task)¶
检索 worker 的最后一个 work 函数
参数
struct task_struct *task
要检索其最后一个 work 函数的任务。
描述
确定 worker 执行的最后一个函数。这是从调度程序调用的,以获取 worker 的最后已知身份。
此函数在 schedule() 期间当 kworker 即将进入睡眠时调用。psi 使用它来识别出队期间的聚合 worker,以便当该 worker 是系统中或 cgroup 中最后一个进入睡眠的任务时,允许定期聚合关闭。
由于此函数不涉及任何与 workqueue 相关的锁定,因此只有当从调度程序的排队和出队路径内部调用时,它才会返回稳定值,此时保证 task(必须是 kworker)不会处理任何 work。
上下文
raw_spin_lock_irq(rq->lock)
返回
current
作为 worker 执行的最后一个 work 函数,如果尚未执行任何 work,则为 NULL。
-
struct wq_node_nr_active *wq_node_nr_active(struct workqueue_struct *wq, int node)¶
确定要使用的 wq_node_nr_active
参数
struct workqueue_struct *wq
感兴趣的工作队列
int node
NUMA 节点,可以为
NUMA_NO_NODE
描述
确定要在 node 上为 wq 使用的 wq_node_nr_active。返回
对于每个 cpu 的 workqueue,
NULL
,因为它们不需要使用共享的 nr_active。如果 node 为
NUMA_NO_NODE
,则为 node_nr_active[nr_node_ids]。否则为 node_nr_active[node]。
-
void wq_update_node_max_active(struct workqueue_struct *wq, int off_cpu)¶
更新要使用的每个节点 max_actives
参数
struct workqueue_struct *wq
要更新的 workqueue
int off_cpu
正在关闭的 CPU,如果 CPU 没有关闭则为 -1
描述
更新 wq->node_nr_active**[]->max。**wq 必须是未绑定的。max_active 根据在线 cpu 数量的比例分布在各个节点之间。结果始终介于 wq->min_active 和 max_active 之间。
-
void get_pwq(struct pool_workqueue *pwq)¶
在指定的 pool_workqueue 上获取额外的引用
参数
struct pool_workqueue *pwq
要获取的 pool_workqueue
描述
在 pwq 上获取额外的引用。调用者应保证 pwq 具有正的 refcnt 并持有匹配的 pool->lock。
-
void put_pwq(struct pool_workqueue *pwq)¶
放置一个 pool_workqueue 引用
参数
struct pool_workqueue *pwq
要放置的 pool_workqueue
描述
删除 pwq 的引用。如果其 refcnt 达到零,则安排销毁。调用者应持有匹配的 pool->lock。
-
bool pwq_tryinc_nr_active(struct pool_workqueue *pwq, bool fill)¶
尝试为 pwq 递增 nr_active
参数
struct pool_workqueue *pwq
感兴趣的 pool_workqueue
bool fill
max_active 可能已增加,尝试提高并发级别
描述
尝试为 pwq 增加 nr_active。如果成功获取 nr_active 计数,则返回 true
。否则返回 false
。
-
bool pwq_activate_first_inactive(struct pool_workqueue *pwq, bool fill)¶
激活 pwq 上的第一个非活动工作项
参数
struct pool_workqueue *pwq
感兴趣的 pool_workqueue
bool fill
max_active 可能已增加,尝试提高并发级别
描述
如果可用且 max_active 限制允许,则激活 pwq 的第一个非活动工作项。
如果已激活非活动工作项,则返回 true
。如果未找到非活动工作项或达到 max_active 限制,则返回 false
。
-
void unplug_oldest_pwq(struct workqueue_struct *wq)¶
拔出最旧的 pool_workqueue
参数
struct workqueue_struct *wq
workqueue_struct,其中最旧的 pwq 将被拔出
描述
此函数应仅针对有序工作队列调用,其中仅拔出最旧的 pwq,其他 pwq 被插入以暂停执行,以确保正确的工作项排序
dfl_pwq --------------+ [P] - plugged
|
v
pwqs -> A -> B [P] -> C [P] (newest)
| | |
1 3 5
| | |
2 4 6
当最旧的 pwq 被清空并删除时,应调用此函数以拔出下一个最旧的 pwq,以开始其工作项执行。请注意,pwq 使用最早的 pwq 优先的方式链接到 wq->pwqs 中,因此列表中的第一个 pwq 是最旧的。
-
void node_activate_pending_pwq(struct wq_node_nr_active *nna, struct worker_pool *caller_pool)¶
在 wq_node_nr_active 上激活待处理的 pwq
参数
struct wq_node_nr_active *nna
要为其激活待处理 pwq 的 wq_node_nr_active
struct worker_pool *caller_pool
调用者正在锁定的 worker_pool
描述
激活 nna->pending_pwqs 中的 pwq。使用锁定的 caller_pool 调用。可以解锁并重新锁定 caller_pool 以锁定其他 worker_pool。
-
void pwq_dec_nr_active(struct pool_workqueue *pwq)¶
撤回活动计数
参数
struct pool_workqueue *pwq
感兴趣的 pool_workqueue
描述
减少 pwq 的 nr_active 并尝试激活第一个非活动工作项。对于无界工作队列,此函数可能会暂时放弃 pwq->pool->lock。
-
void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, unsigned long work_data)¶
减少 pwq 的 nr_in_flight
参数
struct pool_workqueue *pwq
感兴趣的 pwq
unsigned long work_data
离开队列的工作的 work_data
描述
工作已完成或从待处理队列中删除,减少其 pwq 的 nr_in_flight 并处理工作队列刷新。
注意
对于无界工作队列,此函数可能会暂时放弃 pwq->pool->lock,因此应在完成正在进行中的工作项的所有其他状态更新后调用。
上下文
raw_spin_lock_irq(pool->lock)。
-
int try_to_grab_pending(struct work_struct *work, u32 cflags, unsigned long *irq_flags)¶
从工作列表窃取工作项并禁用 irq
参数
struct work_struct *work
要窃取的工作项
u32 cflags
WORK_CANCEL_
标志unsigned long *irq_flags
存储 irq 状态的位置
描述
尝试获取 work 的 PENDING 位。此函数可以处理任何稳定状态下的 work - 空闲、在计时器上或在工作列表上。
如果成功返回,>= 0,则禁用 irq,并且调用者负责使用 local_irq_restore(*irq_flags) 释放它。
可以从任何上下文(包括 IRQ 处理程序)安全地调用此函数。
返回
1
如果 work 处于待处理状态并且我们成功窃取了 PENDING
0
如果 work 处于空闲状态并且我们声明了 PENDING
-EAGAIN
如果目前无法获取 PENDING,则可以安全地忙等待重试
注意
如果返回 >= 0,则调用者拥有 work 的 PENDING 位。为了避免在保持 PENDING 和 work 离开队列时被中断,必须在进入时禁用 irq。这与 delayed_work->timer 是 irqsafe 的情况相结合,确保我们在有限的短时间内返回 -EAGAIN。
-
bool work_grab_pending(struct work_struct *work, u32 cflags, unsigned long *irq_flags)¶
从工作列表窃取工作项并禁用 irq
参数
struct work_struct *work
要窃取的工作项
u32 cflags
WORK_CANCEL_
标志unsigned long *irq_flags
存储 IRQ 状态的位置
描述
获取 work 的 PENDING 位。work 可以处于任何稳定状态 - 空闲、在计时器上或在工作列表上。
可以从任何上下文中调用。返回时禁用 IRQ,IRQ 状态存储在 *irq_flags 中。调用者负责使用 local_irq_restore() 重新启用它。
如果 work 处于待处理状态,则返回 true
。如果处于空闲状态,则返回 false
。
-
void insert_work(struct pool_workqueue *pwq, struct work_struct *work, struct list_head *head, unsigned int extra_flags)¶
将工作插入池中
参数
struct pool_workqueue *pwq
work 所属的 pwq
struct work_struct *work
要插入的工作
struct list_head *head
插入点
unsigned int extra_flags
要设置的额外 WORK_STRUCT_* 标志
描述
在 head 之后插入属于 pwq 的 work。extra_flags 与 work_struct 标志进行或运算。
上下文
raw_spin_lock_irq(pool->lock)。
-
bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)¶
在特定 CPU 上排队工作
参数
int cpu
要在其上执行工作的 CPU 编号
struct workqueue_struct *wq
要使用的工作队列
struct work_struct *work
要排队的工作
描述
我们将工作排队到特定的 CPU,调用者必须确保它不会消失。未能确保指定 CPU 不会消失的调用者将在随机选择的 CPU 上执行。但是请注意,指定从未上线的 CPU 的调用者将收到错误提示。
返回
如果 work 已经在队列中,则返回 false
,否则返回 true
。
-
int select_numa_node_cpu(int node)¶
基于 NUMA 节点选择 CPU
参数
int node
我们要从中选择 CPU 的 NUMA 节点 ID
描述
此函数将尝试在给定的节点上找到一个可用的“随机”CPU。如果给定节点上没有可用的 CPU,则它将返回 WORK_CPU_UNBOUND,表明如果我们需要调度此工作,则应该将其调度到任何可用的 CPU。
-
bool queue_work_node(int node, struct workqueue_struct *wq, struct work_struct *work)¶
为给定的 NUMA 节点在“随机”CPU 上排队工作
参数
int node
我们针对该工作所属的 NUMA 节点
struct workqueue_struct *wq
要使用的工作队列
struct work_struct *work
要排队的工作
描述
我们将工作排队到给定 NUMA 节点中的“随机”CPU。这里的基本思想是提供一种将工作与给定 NUMA 节点关联起来的方法。
此函数仅尽力将任务放到正确的 NUMA 节点上。如果未请求任何节点或请求的节点处于离线状态,则会回退到标准的 queue_work 行为。
目前,“随机” CPU 最终会成为 cpu_online_mask 和节点 cpumask 交集中的第一个可用 CPU,除非我们正在该节点上运行。在这种情况下,我们只使用当前 CPU。
返回
如果 work 已经在队列中,则返回 false
,否则返回 true
。
-
bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)¶
在延迟后在特定 CPU 上排队工作
参数
int cpu
要在其上执行工作的 CPU 编号
struct workqueue_struct *wq
要使用的工作队列
struct delayed_work *dwork
要排队的工作
unsigned long delay
排队前要等待的节拍数
返回
如果 work 已经在队列中,则为 false
,否则为 true
。 如果 delay 为零且 dwork 处于空闲状态,则会立即安排执行。
-
bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)¶
修改延迟或在特定 CPU 上排队延迟工作
参数
int cpu
要在其上执行工作的 CPU 编号
struct workqueue_struct *wq
要使用的工作队列
struct delayed_work *dwork
要排队的工作
unsigned long delay
排队前要等待的节拍数
描述
如果 dwork 处于空闲状态,则等同于 queue_delayed_work_on()
;否则,修改 dwork 的计时器,使其在 delay 后过期。 如果 delay 为零,则无论其当前状态如何,都保证立即调度 work。
此函数可以从任何上下文(包括 IRQ 处理程序)安全调用。有关详细信息,请参阅 try_to_grab_pending()
。
返回
如果 dwork 处于空闲状态并已排队,则为 false
;如果 dwork 处于挂起状态且其计时器已修改,则为 true
。
-
bool queue_rcu_work(struct workqueue_struct *wq, struct rcu_work *rwork)¶
在 RCU 宽限期后排队工作
参数
struct workqueue_struct *wq
要使用的工作队列
struct rcu_work *rwork
要排队的工作
返回
如果 rwork 已经挂起,则为 false
,否则为 true
。请注意,仅在返回 true
后才能保证完整的 RCU 宽限期。虽然保证在返回 false
后执行 rwork,但执行可能会在完整的 RCU 宽限期过去之前发生。
参数
struct worker *worker
要附加的工作程序
struct worker_pool *pool
目标池
描述
将 worker 附加到 pool。附加后,worker 的 WORKER_UNBOUND
标志和 CPU 绑定会与池在 CPU 热插拔期间保持协调。
参数
struct worker *worker
已附加到其池中的工作程序
描述
撤销在 worker_attach_to_pool()
中完成的附加操作。调用者工作程序在分离后不应访问池,除非它对池有其他引用。
-
struct worker *create_worker(struct worker_pool *pool)¶
创建一个新的工作队列工作程序
参数
struct worker_pool *pool
新工作程序将属于的池
描述
创建并启动附加到 pool 的新工作程序。
上下文
可能会休眠。执行 GFP_KERNEL 分配。
返回
指向新创建的工作程序的指针。
参数
struct worker *worker
要销毁的工作程序
struct list_head *list
将工作程序从其 pool->idle_list 中移出并放入 list
描述
标记要销毁的 worker 并相应地调整 pool 统计信息。 工作程序应该处于空闲状态。
上下文
raw_spin_lock_irq(pool->lock)。
-
void idle_worker_timeout(struct timer_list *t)¶
检查是否可以删除一些空闲工作程序。
参数
struct timer_list *t
刚过期的池的 idle_timer
描述
计时器在 worker_enter_idle()
中启用。 请注意,它不会在 worker_leave_idle()
中禁用,因为当池处于 too_many_workers() 临界点时,在空闲和活动之间切换的工作程序会导致过多的计时器维护开销。 由于 IDLE_WORKER_TIMEOUT 足够长,我们只需让它过期并重新评估情况即可。
-
void idle_cull_fn(struct work_struct *work)¶
剔除空闲时间过长的工作程序。
参数
struct work_struct *work
池的用于处理这些空闲工作的工作
描述
这将遍历池的空闲工作程序,并清除那些空闲时间至少为 IDLE_WORKER_TIMEOUT 秒的工作程序。
我们不想因为剔除 pcpu kworker 而干扰隔离的 CPU,因此这也重置了工作程序亲和性。这需要一个可休眠的上下文,因此需要在计时器回调和工作项之间进行拆分。
-
void maybe_create_worker(struct worker_pool *pool)¶
如有必要,创建新的工作线程
参数
struct worker_pool *pool
用于创建新工作线程的池
描述
如有必要,为 pool 创建新的工作线程。保证从该函数返回时,pool 至少有一个空闲的工作线程。如果创建新工作线程的时间超过 MAYDAY_INTERVAL,则会向所有在 pool 上安排了工作任务的救援者发送求救信号,以解决可能出现的分配死锁。
返回时,保证 need_to_create_worker() 为 false
,并且 may_start_working() 为 true
。
锁:raw_spin_lock_irq(pool->lock),可能会释放和重新获取多次。执行 GFP_KERNEL 分配。仅从管理器调用。
参数
struct worker *worker
自身
描述
承担管理角色,并管理工作线程 worker 所属的工作线程池。在任何给定时间,每个池只能有零个或一个管理器。此排除由该函数自动处理。
调用者可以安全地在返回 false 时开始处理工作任务。如果返回 true,则保证 need_to_create_worker() 为 false,并且 may_start_working() 为 true。
上下文
raw_spin_lock_irq(pool->lock),可能会释放和重新获取多次。执行 GFP_KERNEL 分配。
返回
如果池不需要管理,并且调用者可以安全地开始处理工作任务,则返回 false
;如果执行了管理函数,并且调用者在调用该函数之前验证的条件可能不再为真,则返回 true
。
参数
struct worker *worker
自身
struct work_struct *work
要处理的工作任务
描述
处理 work。此函数包含处理单个工作任务所需的所有逻辑,包括与同一 CPU 上的其他工作线程同步和交互、排队和刷新。只要满足上下文要求,任何工作线程都可以调用此函数来处理工作任务。
上下文
raw_spin_lock_irq(pool->lock),会被释放和重新获取。
参数
struct worker *worker
自身
描述
处理所有已计划的工作任务。请注意,已计划的列表在处理工作任务时可能会更改,因此此函数会重复从顶部获取工作任务并执行它。
上下文
raw_spin_lock_irq(pool->lock),可能会释放和重新获取多次。
-
int worker_thread(void *__worker)¶
工作线程函数
参数
void *__worker
自身
描述
工作线程函数。所有工作线程都属于一个 worker_pool - 要么是每个 CPU 一个,要么是动态无绑定的。这些工作线程处理所有工作项,而无需考虑其特定的目标工作队列。唯一的例外是属于具有救援者的工作队列的工作项,这将在 rescuer_thread()
中进行解释。
返回
0
-
int rescuer_thread(void *__rescuer)¶
救援者线程函数
参数
void *__rescuer
自身
描述
工作队列救援者线程函数。每个设置了 WQ_MEM_RECLAIM 的工作队列都有一个救援者。
池上的常规工作处理可能会阻塞尝试创建使用 GFP_KERNEL 分配的新工作线程,如果同一队列上的一些工作需要被处理才能满足 GFP_KERNEL 分配,这有可能会发展成死锁。这就是救援者要解决的问题。
当存在这种可能的情况时,池会召唤所有工作队列的救援者,这些工作队列在该池上排队了工作任务,并让他们处理这些工作任务,以确保向前进展。
这种情况应该很少发生。
返回
0
-
void check_flush_dependency(struct workqueue_struct *target_wq, struct work_struct *target_work, bool from_cancel)¶
检查刷新依赖项的健全性
参数
struct workqueue_struct *target_wq
正在刷新的工作队列
struct work_struct *target_work
正在刷新的工作项(工作队列刷新为 NULL)
bool from_cancel
我们是否从工作取消路径调用
描述
current
正在尝试刷新整个 target_wq 或其上的 target_work。如果这不是取消路径(这意味着正在刷新的工作要么已经在运行,要么根本不会运行),请检查 target_wq 是否没有 WQ_MEM_RECLAIM
,并验证 current
是否没有回收内存,或者在没有 WQ_MEM_RECLAIM
的工作队列上运行,因为这可能会破坏向前进展的保证,从而导致死锁。
-
void insert_wq_barrier(struct pool_workqueue *pwq, struct wq_barrier *barr, struct work_struct *target, struct worker *worker)¶
插入一个屏障工作
参数
struct pool_workqueue *pwq
要将屏障插入的 pwq
struct wq_barrier *barr
要插入的 wq_barrier
struct work_struct *target
要将 barr 附加到的目标工作
struct worker *worker
当前正在执行 target 的工作线程,如果 target 没有执行,则为 NULL
描述
barr 链接到 target,使得 barr 仅在 target 完成执行后才会完成。请注意,排序保证仅相对于 target 且在本地 CPU 上观察到。
目前,排队的屏障无法取消。这是因为 try_to_grab_pending()
无法确定要获取的工作是否在队列的头部,因此无法清除先前设置了 LINKED 标志的工作的标志,而设置了 LINKED 标志的工作之后必须有一个有效的下一个工作。
请注意,当 worker 为非 NULL 时,target 可能会在我们不知情的情况下被修改,因此我们无法从 target 可靠地确定 pwq。
上下文
raw_spin_lock_irq(pool->lock)。
-
bool flush_workqueue_prep_pwqs(struct workqueue_struct *wq, int flush_color, int work_color)¶
为工作队列刷新准备 pwqs
参数
struct workqueue_struct *wq
正在刷新的工作队列
int flush_color
新的刷新颜色,对于无操作,则 < 0
int work_color
新的工作颜色,对于无操作,则 < 0
描述
为工作队列刷新准备 pwqs。
如果 flush_color 为非负数,则所有 pwqs 上的 flush_color 应为 -1。如果在指定的颜色处,没有 pwq 有正在进行的命令,则所有 pwq->flush_color 保持为 -1,并返回 false
。如果任何 pwq 有正在进行的命令,则其 pwq->flush_color 设置为 flush_color,wq->nr_pwqs_to_flush 会相应更新,pwq 唤醒逻辑会被启用,并返回 true
。
调用者应在调用此函数之前,使用非负数 flush_color 初始化 wq->first_flusher。如果 flush_color 为负数,则不会进行刷新颜色更新,并返回 false
。
如果 work_color 为非负数,则所有 pwqs 应具有与 work_color 之前的相同 work_color,并且都将前进到 work_color。
上下文
mutex_lock(wq->mutex)。
返回
如果 flush_color >= 0 且有需要刷新的内容,则返回 true
。否则返回 false
。
-
void __flush_workqueue(struct workqueue_struct *wq)¶
确保任何已计划的工作都已运行完成。
参数
struct workqueue_struct *wq
要刷新的工作队列
描述
此函数会休眠,直到入口处排队的所有工作项都已完成执行,但不会被新的传入工作项实时锁定。
-
void drain_workqueue(struct workqueue_struct *wq)¶
排空工作队列
参数
struct workqueue_struct *wq
要排空的工作队列
描述
等待直到工作队列为空。在排空进行时,只允许链式排队。换句话说,只有 **wq** 上当前挂起或正在运行的工作项才能在其上排队更多的工作项。**wq** 会被重复刷新,直到它变为空。刷新的次数由链的深度决定,应该相对较短。如果耗时过长,则会发出警告。
-
bool flush_work(struct work_struct *work)¶
等待工作完成最后一次排队实例的执行
参数
struct work_struct *work
要刷新的工作
描述
等待直到 **work** 完成执行。如果自刷新开始以来,**work** 没有被重新排队,则保证返回时它是空闲的。
返回
如果 flush_work()
等待工作完成执行,则返回 true
,如果它已经空闲,则返回 false
。
-
bool flush_delayed_work(struct delayed_work *dwork)¶
等待 dwork 完成最后一次排队的执行
参数
struct delayed_work *dwork
要刷新的延迟工作
描述
延迟定时器被取消,挂起的工作被排队等待立即执行。与 flush_work()
一样,此函数仅考虑 **dwork** 的最后一次排队实例。
返回
如果 flush_work()
等待工作完成执行,则返回 true
,如果它已经空闲,则返回 false
。
-
bool flush_rcu_work(struct rcu_work *rwork)¶
等待 rwork 完成最后一次排队的执行
-
bool cancel_work_sync(struct work_struct *work)¶
取消一个工作并等待其完成
参数
struct work_struct *work
要取消的工作
描述
取消 **work** 并等待其执行完成。即使工作重新排队或迁移到另一个工作队列,也可以使用此函数。从该函数返回后,只要没有竞争的入队,就保证 **work** 不会挂起或在任何 CPU 上执行。
cancel_work_sync(delayed_work->work
) 不得用于 delayed_work。请改用 cancel_delayed_work_sync()
。
如果 **work** 上次在非 BH 工作队列上排队,则必须从可休眠的上下文中调用。如果 **work** 上次在 BH 工作队列上排队,也可以从非硬中断原子上下文中调用,包括 BH。
如果 **work** 处于挂起状态,则返回 true
,否则返回 false
。
-
bool cancel_delayed_work(struct delayed_work *dwork)¶
取消一个延迟工作
参数
struct delayed_work *dwork
要取消的 delayed_work
描述
终止一个挂起的 delayed_work。
可以从任何上下文(包括 IRQ 处理程序)安全地调用此函数。
返回
如果 **dwork** 处于挂起状态并被取消,则返回 true
;如果它没有挂起,则返回 false
。
注意
除非回调函数返回 true
并且工作不会重新启动自身,否则工作回调函数可能在返回时仍在运行。显式刷新或使用 cancel_delayed_work_sync()
来等待它。
-
bool cancel_delayed_work_sync(struct delayed_work *dwork)¶
取消一个延迟工作并等待其完成
参数
struct delayed_work *dwork
要取消的延迟工作
描述
这是用于延迟工作的 cancel_work_sync()
。
返回
如果 **dwork** 处于挂起状态,则返回 true
,否则返回 false
。
-
bool disable_work(struct work_struct *work)¶
禁用并取消一个工作项
参数
struct work_struct *work
要禁用的工作项
描述
通过递增其禁用计数来禁用 **work**,如果当前处于挂起状态,则取消它。只要禁用计数不为零,任何将 **work** 排队的尝试都将失败并返回 false
。支持的最大禁用深度是 2 的 WORK_OFFQ_DISABLE_BITS
次幂,目前是 65536。
可以从任何上下文中调用。如果 **work** 处于挂起状态,则返回 true
,否则返回 false
。
-
bool disable_work_sync(struct work_struct *work)¶
禁用、取消并排空一个工作项
参数
struct work_struct *work
要禁用的工作项
描述
类似于 disable_work()
,但也等待 **work** 完成(如果当前正在执行)。
如果 **work** 上次在非 BH 工作队列上排队,则必须从可休眠的上下文中调用。如果 **work** 上次在 BH 工作队列上排队,也可以从非硬中断原子上下文中调用,包括 BH。
如果 **work** 处于挂起状态,则返回 true
,否则返回 false
。
-
bool enable_work(struct work_struct *work)¶
启用一个工作项
参数
struct work_struct *work
要启用的工作项
描述
通过递减 **work** 的禁用计数来撤消 disable_work[_sync]()。只有当禁用计数为 0 时,才能将 **work** 排队。
可以从任何上下文中调用。如果禁用计数达到 0,则返回 true
。否则,返回 false
。
-
bool disable_delayed_work(struct delayed_work *dwork)¶
禁用并取消一个延迟工作项
-
bool disable_delayed_work_sync(struct delayed_work *dwork)¶
禁用、取消并清空一个延迟工作项。
-
bool enable_delayed_work(struct delayed_work *dwork)¶
启用一个延迟工作项。
-
int schedule_on_each_cpu(work_func_t func)¶
在每个在线 CPU 上同步执行一个函数。
参数
work_func_t func
要调用的函数。
描述
schedule_on_each_cpu()
使用系统工作队列在每个在线 CPU 上执行 func,并阻塞直到所有 CPU 完成。schedule_on_each_cpu()
非常慢。
返回
成功时返回 0,失败时返回 -errno。
-
int execute_in_process_context(work_func_t fn, struct execute_work *ew)¶
在用户上下文中可靠地执行例程。
参数
work_func_t fn
要执行的函数。
struct execute_work *ew
保证执行工作结构存储空间(必须在工作执行时可用)。
描述
如果进程上下文可用,则立即执行该函数,否则安排该函数延迟执行。
返回
- 0 - 函数已执行。
1 - 函数已安排执行。
-
void free_workqueue_attrs(struct workqueue_attrs *attrs)¶
释放一个 workqueue_attrs。
-
struct workqueue_attrs *alloc_workqueue_attrs(void)¶
分配一个 workqueue_attrs。
参数
void
无参数。
描述
分配一个新的 workqueue_attrs,使用默认设置进行初始化并返回它。
返回
成功时返回分配的新的 workqueue_attr,失败时返回 NULL
。
-
int init_worker_pool(struct worker_pool *pool)¶
初始化一个新分配的 worker_pool。
参数
struct worker_pool *pool
要初始化的 worker_pool。
描述
初始化一个新分配的 pool。它还会分配 pool->attrs。
返回
成功时返回 0,失败时返回 -errno。即使失败,pool 内部的所有字段都已初始化,并且可以安全地在 pool 上调用 put_unbound_pool()
来释放它。
-
void put_unbound_pool(struct worker_pool *pool)¶
释放一个 worker_pool。
参数
struct worker_pool *pool
要释放的 worker_pool。
描述
释放 pool。如果其引用计数达到零,它将以 RCU 安全的方式被销毁。get_unbound_pool()
在其失败路径上调用此函数,并且此函数应该能够释放已经成功或不成功地完成了 init_worker_pool()
的池。
应在持有 wq_pool_mutex 的情况下调用。
-
struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)¶
获取具有指定属性的 worker_pool。
参数
const struct workqueue_attrs *attrs
要获取的 worker_pool 的属性。
描述
获取一个与 attrs 具有相同属性的 worker_pool,增加引用计数并返回它。如果已经存在匹配的 worker_pool,将使用它;否则,此函数尝试创建一个新的。
应在持有 wq_pool_mutex 的情况下调用。
返回
成功时,返回一个与 attrs 具有相同属性的 worker_pool。失败时,返回 NULL
。
-
void wq_calc_pod_cpumask(struct workqueue_attrs *attrs, int cpu)¶
计算 pod 的 wq_attrs 的 cpumask。
参数
struct workqueue_attrs *attrs
目标工作队列的默认 pwq 的 wq_attrs。
int cpu
目标 CPU。
描述
计算具有 attrs 的工作队列应在 pod 上使用的 cpumask。结果存储在 attrs->__pod_cpumask 中。
如果未启用 pod 亲和性,则始终使用 attrs->cpumask。如果启用并且 pod 具有 attrs 请求的在线 CPU,则返回的 cpumask 是 pod 的可能 CPU 和 attrs->cpumask 的交集。
调用者负责确保 pod 的 cpumask 保持稳定。
-
int apply_workqueue_attrs(struct workqueue_struct *wq, const struct workqueue_attrs *attrs)¶
将新的 workqueue_attrs 应用到未绑定的工作队列。
参数
struct workqueue_struct *wq
目标工作队列
const struct workqueue_attrs *attrs
要应用的 workqueue_attrs,使用
alloc_workqueue_attrs()
分配。
描述
将 attrs 应用到未绑定的工作队列 wq。除非禁用,此函数会将单独的 pwq 映射到每个 CPU pod,其中在 attrs->cpumask 中具有可能的 CPU,以便工作项与其发出的 pod 保持亲和性。当正在进行的工作项完成时,旧的 pwq 将被释放。请注意,反复将自身重新入队的工作项将保留在其当前的 pwq 上。
执行 GFP_KERNEL 分配。
返回
成功时返回 0,失败时返回 -errno。
-
void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)¶
更新 CPU 热插拔的 pwq 插槽。
参数
struct workqueue_struct *wq
目标工作队列
int cpu
要更新 pwq 插槽的 CPU。
描述
此函数应从 CPU_DOWN_PREPARE
、CPU_ONLINE
和 CPU_DOWN_FAILED
中调用。cpu 与正在热插拔的 CPU 处于同一 pod 中。
如果由于内存分配失败而无法调整 pod 亲和性,它会回退到 wq->dfl_pwq,这可能不是最佳的,但始终是正确的。
请注意,当一个跨多个 pod 的工作队列的最后一个允许的 CPU 下线时,已经为该工作队列执行工作项的 worker 将会失去其 CPU 亲和性,并可能在任何 CPU 上执行。这类似于每个 CPU 工作队列在 CPU_DOWN 时的行为。如果工作队列用户想要严格的亲和性,用户有责任在 CPU_DOWN_PREPARE 中刷新工作项。
-
void wq_adjust_max_active(struct workqueue_struct *wq)¶
将工作队列的 max_active 更新为当前设置
参数
struct workqueue_struct *wq
目标工作队列
描述
如果 wq 没有冻结,则将 wq->max_active 设置为 saved_max_active,并相应地激活不活跃的工作项。如果 wq 正在冻结,则将 wq->max_active 清零。
-
void destroy_workqueue(struct workqueue_struct *wq)¶
安全地终止一个工作队列
参数
struct workqueue_struct *wq
目标工作队列
描述
安全地销毁一个工作队列。所有当前挂起的工作将首先完成。
-
void workqueue_set_max_active(struct workqueue_struct *wq, int max_active)¶
调整工作队列的 max_active
参数
struct workqueue_struct *wq
目标工作队列
int max_active
新的 max_active 值。
描述
将 wq 的 max_active 设置为 max_active。请参阅 alloc_workqueue()
函数的注释。
上下文
不要从 IRQ 上下文中调用。
-
void workqueue_set_min_active(struct workqueue_struct *wq, int min_active)¶
调整无绑定工作队列的 min_active
参数
struct workqueue_struct *wq
目标无绑定工作队列
int min_active
新的 min_active 值
描述
设置无绑定工作队列的 min_active。与其他类型的工作队列不同,无绑定工作队列不保证能够处理 max_active 个相互依赖的工作项。相反,无绑定工作队列保证能够处理 min_active 个相互依赖的工作项,默认情况下为 WQ_DFL_MIN_ACTIVE
。
使用此函数在 0 和当前 max_active 之间调整 min_active 值。
-
struct work_struct *current_work(void)¶
检索
current
任务的工作结构
参数
void
无参数。
描述
确定 current
任务是否是工作队列 worker 以及它正在处理什么。有助于找出 current
任务正在运行的上下文。
返回
如果 current
任务是工作队列 worker,则为工作结构,否则为 NULL
。
-
bool current_is_workqueue_rescuer(void)¶
current
是否是工作队列救援者?
参数
void
无参数。
描述
确定 current
是否是工作队列救援者。可以从工作函数中使用,以确定它是否正在救援者任务中运行。
返回
如果 current
是工作队列救援者,则为 true
。否则为 false
。
-
bool workqueue_congested(int cpu, struct workqueue_struct *wq)¶
测试工作队列是否拥塞
参数
int cpu
有问题的 CPU
struct workqueue_struct *wq
目标工作队列
描述
测试 wq 的 cpu 工作队列对于 cpu 是否拥塞。此函数周围没有同步,测试结果不可靠,仅可用作建议性提示或用于调试。
如果 cpu 是 WORK_CPU_UNBOUND,则在本地 CPU 上执行测试。
除了有序工作队列外,所有工作队列都具有每个 CPU 的 pool_workqueues,每个 pool_workqueues 都有自己的拥塞状态。一个工作队列在一个 CPU 上拥塞并不意味着该工作队列在任何其他 CPU 上都被争用。
返回
如果拥塞,则为 true
,否则为 false
。
-
unsigned int work_busy(struct work_struct *work)¶
测试工作当前是否正在挂起或运行
参数
struct work_struct *work
要测试的工作
描述
测试 work 当前是否正在挂起或运行。此函数周围没有同步,测试结果不可靠,仅可用作建议性提示或用于调试。
返回
WORK_BUSY_* 位的 OR 掩码。
-
void set_worker_desc(const char *fmt, ...)¶
设置当前工作项的描述
参数
const char *fmt
printf 样式的格式字符串
...
格式字符串的参数
描述
此函数可以由正在运行的工作函数调用,以描述工作项的用途。如果 worker 任务被转储,则此信息将一起打印出来以帮助调试。描述最多可以包含 WORKER_DESC_LEN(包括尾随的 '0')。
-
void print_worker_info(const char *log_lvl, struct task_struct *task)¶
打印 worker 信息和描述
参数
const char *log_lvl
打印时要使用的日志级别
struct task_struct *task
目标任务
描述
如果 task 是一个 worker,并且当前正在执行一个工作项,则打印出正在服务的工作队列的名称,以及由当前正在执行的工作项使用 set_worker_desc()
设置的 worker 描述。
此函数可以安全地在任何任务上调用,只要可以访问 task_struct 本身即可。虽然安全,但此函数不同步,可能会打印出混合或长度有限的垃圾信息。
-
void show_one_workqueue(struct workqueue_struct *wq)¶
转储指定工作队列的状态
参数
struct workqueue_struct *wq
将打印其状态的工作队列
-
void show_one_worker_pool(struct worker_pool *pool)¶
转储指定 worker 池的状态
参数
struct worker_pool *pool
将打印其状态的 worker 池
-
void show_all_workqueues(void)¶
转储工作队列状态
参数
void
无参数。
描述
从 sysrq 处理程序调用,并打印出所有繁忙的工作队列和池。
-
void show_freezable_workqueues(void)¶
转储可冻结工作队列状态
参数
void
无参数。
描述
从 try_to_freeze_tasks() 调用,并打印出所有仍处于繁忙状态的可冻结工作队列。
-
void rebind_workers(struct worker_pool *pool)¶
将池的所有工作线程重新绑定到关联的 CPU
参数
struct worker_pool *pool
感兴趣的池
描述
pool->cpu 正在上线。将所有工作线程重新绑定到 CPU。
-
void restore_unbound_workers_cpumask(struct worker_pool *pool, int cpu)¶
恢复未绑定工作线程的 CPU 掩码
参数
struct worker_pool *pool
感兴趣的未绑定池
int cpu
正在上线的 CPU
描述
未绑定池可能会最终获得一个没有任何在线 CPU 的 CPU 掩码。当该池的工作线程被调度时,调度程序会重置其 cpus_allowed。如果 cpu 在 pool 的 CPU 掩码中,而该掩码之前没有任何在线 CPU,则应恢复其所有工作线程的 cpus_allowed。
-
long work_on_cpu_key(int cpu, long (*fn)(void*), void *arg, struct lock_class_key *key)¶
在特定 CPU 上以线程上下文运行函数
参数
int cpu
要在其上运行的 CPU
long (*fn)(void *)
要运行的函数
void *arg
函数参数
struct lock_class_key *key
用于锁调试的锁类键
描述
调用方有责任确保 CPU 不会脱机。调用方不得持有任何阻止 fn 完成的锁。
返回
fn 返回的值。
-
long work_on_cpu_safe_key(int cpu, long (*fn)(void*), void *arg, struct lock_class_key *key)¶
在特定 CPU 上以线程上下文运行函数
参数
int cpu
要在其上运行的 CPU
long (*fn)(void *)
要运行的函数
void *arg
函数参数
struct lock_class_key *key
用于锁调试的锁类键
描述
禁用 CPU 热插拔并调用 work_on_cpu()。调用方不得持有任何阻止 fn 完成的锁。
返回
fn 返回的值。
-
void freeze_workqueues_begin(void)¶
开始冻结工作队列
参数
void
无参数。
描述
开始冻结工作队列。此函数返回后,所有可冻结的工作队列都会将新工作排队到其 inactive_works 列表,而不是 pool->worklist。
上下文
获取并释放 wq_pool_mutex、wq->mutex 和 pool->lock。
-
bool freeze_workqueues_busy(void)¶
可冻结工作队列是否仍处于繁忙状态?
参数
void
无参数。
描述
检查冻结是否完成。此函数必须在 freeze_workqueues_begin()
和 thaw_workqueues()
之间调用。
上下文
获取并释放 wq_pool_mutex。
返回
如果某些可冻结工作队列仍处于繁忙状态,则为 true
。如果冻结已完成,则为 false
。
-
void thaw_workqueues(void)¶
解冻工作队列
参数
void
无参数。
描述
解冻工作队列。恢复正常排队,所有收集的冻结工作都将转移到各自的池工作列表。
上下文
获取并释放 wq_pool_mutex、wq->mutex 和 pool->lock。
-
int workqueue_unbound_exclude_cpumask(cpumask_var_t exclude_cpumask)¶
从无界 CPU 掩码中排除给定的 CPU
参数
cpumask_var_t exclude_cpumask
要从 wq_unbound_cpumask 中排除的 CPU 掩码
描述
此函数可以从 cpuset 代码中调用,以提供一组应从 wq_unbound_cpumask 中排除的隔离 CPU。
-
int workqueue_set_unbound_cpumask(cpumask_var_t cpumask)¶
设置底层无界 CPU 掩码
参数
cpumask_var_t cpumask
要设置的 CPU 掩码
底层工作队列 CPU 掩码是一个全局 CPU 掩码,它限制所有无界工作队列的亲和性。此函数检查 cpumask 并将其应用于所有无界工作队列,并更新它们的所有 pwq。
返回
- 0 - 成功
-EINVAL - 无效的 cpumask -ENOMEM - 无法为属性或 pwq 分配内存。
-
int workqueue_sysfs_register(struct workqueue_struct *wq)¶
使工作队列在 sysfs 中可见
参数
struct workqueue_struct *wq
要注册的工作队列
描述
在 /sys/bus/workqueue/devices 下的 sysfs 中公开 wq。如果设置了 WQ_SYSFS,alloc_workqueue*() 会自动调用此函数,这是首选方法。
工作队列用户应仅在希望在使工作队列在 sysfs 中可见之前应用 workqueue_attrs 时直接使用此函数;否则,apply_workqueue_attrs()
可能会与用户态更新属性发生竞争。
返回
成功时返回 0,失败时返回 -errno。
-
void workqueue_sysfs_unregister(struct workqueue_struct *wq)¶
-
void workqueue_init_early(void)¶
工作队列子系统的早期初始化
参数
void
无参数。
描述
这是三阶段工作队列子系统初始化的第一步,并且在基本要素(内存分配、CPU 掩码和 idr)启动后立即调用。它设置所有数据结构和系统工作队列,并允许早期启动代码创建工作队列和排队/取消工作项。只有在可以创建和调度 kthread 且正好在早期 initcalls 之前,才会开始实际的工作项执行。
-
void workqueue_init(void)¶
使工作队列子系统完全上线
参数
void
无参数。
描述
这是三阶段工作队列子系统初始化的第二步,在可以创建和调度内核线程后立即调用。工作队列已创建,并且工作项已在它们上面排队,但还没有内核工作线程执行工作项。使用初始工作线程填充工作线程池,并启用未来的内核工作线程创建。
-
void workqueue_init_topology(void)¶
为无界工作队列初始化 CPU pod
参数
void
无参数。
描述
这是三阶段工作队列子系统初始化的第三步,在 SMP 和拓扑信息完全初始化后调用。它会相应地初始化无界 CPU pod。