工作队列¶
- 日期:
2010 年 9 月
- 作者:
Tejun Heo <tj@kernel.org>
- 作者:
Florian Mickler <florian@mickler.org>
引言¶
在许多情况下,需要异步进程执行上下文,而工作队列(wq)API 是此类情况最常用的机制。
当需要此类异步执行上下文时,会将描述要执行的函数的“工作项”放入队列中。一个独立的工作线程充当异步执行上下文。该队列称为“工作队列”,该线程称为“工作者”。
当工作队列中有工作项时,工作者会一个接一个地执行与这些工作项关联的函数。当工作队列中没有剩余工作项时,工作者会变为空闲状态。当新的工作项被排队时,工作者会再次开始执行。
为什么选择并发管理工作队列?¶
在原始的工作队列(wq)实现中,多线程(MT)工作队列每个 CPU 有一个工作线程,而单线程(ST)工作队列在系统范围内只有一个工作线程。一个 MT 工作队列需要维护与 CPU 数量相同的工作者数量。多年来,内核中出现了许多 MT 工作队列用户,随着 CPU 核心数量的持续增长,一些系统在启动时就耗尽了默认的 32k PID 空间。
尽管 MT 工作队列浪费了大量资源,但其提供的并发级别并不令人满意。这种限制对 ST 和 MT 工作队列都普遍存在,尽管在 MT 上不那么严重。每个工作队列都维护自己独立的工作线程池。一个 MT 工作队列每个 CPU 只能提供一个执行上下文,而一个 ST 工作队列整个系统只有一个。工作项必须争夺这些非常有限的执行上下文,导致各种问题,包括围绕单个执行上下文容易发生死锁。
所提供的并发级别与资源使用之间的矛盾,也迫使工作队列用户做出不必要的权衡,例如 libata 选择使用 ST 工作队列进行轮询 PIO,并接受了两个轮询 PIO 不能同时进行的不必要限制。由于 MT 工作队列没有提供更好的并发性,需要更高并发级别的用户,如 async 或 fscache,不得不实现自己的线程池。
并发管理工作队列(cmwq)是工作队列的重新实现,重点关注以下目标。
保持与原始工作队列 API 的兼容性。
使用所有工作队列共享的每 CPU 统一工作线程池,以按需提供灵活的并发级别,同时不浪费大量资源。
自动调节工作线程池和并发级别,使 API 用户无需担心此类细节。
设计¶
为了简化函数的异步执行,引入了一个新的抽象——工作项。
工作项是一个简单的结构体,它包含一个指向将要异步执行的函数的指针。每当驱动程序或子系统希望异步执行某个函数时,它必须设置一个指向该函数的工作项,并将该工作项排队到工作队列中。
工作项可以在线程上下文或 BH(软中断)上下文中执行。
对于线程化工作队列,专门的线程,称为 [k]工作线程,一个接一个地执行队列中的函数。如果没有工作排队,工作线程将变为空闲状态。这些工作线程在工作线程池中进行管理。
cmwq 的设计区分了子系统和驱动程序用于排队工作项的用户面向工作队列,以及管理工作线程池和处理已排队工作项的后端机制。
每个可能的 CPU 有两个工作线程池,一个用于普通工作项,另一个用于高优先级工作项;还有一些额外的工作线程池用于服务排队到非绑定工作队列的工作项——这些后端池的数量是动态的。
BH 工作队列使用相同的框架。然而,由于只能有一个并发执行上下文,因此无需担心并发性。每个每 CPU BH 工作线程池只包含一个伪工作者,它代表 BH 执行上下文。BH 工作队列可以被视为软中断的便捷接口。
子系统和驱动程序可以根据需要通过特殊的工作队列 API 函数创建和排队工作项。它们可以通过在工作队列上设置标志来影响工作项的执行方式。这些标志包括 CPU 局部性、并发限制、优先级等。要获取详细概述,请参考下方 alloc_workqueue()
的 API 描述。
当工作项排队到工作队列时,目标工作线程池会根据队列参数和工作队列属性确定,并附加到工作线程池的共享工作列表中。例如,除非特别覆盖,绑定工作队列的工作项将排队到与发起者运行的 CPU 相关联的普通或高优先级工作线程池的工作列表中。
对于任何线程池实现,管理并发级别(有多少执行上下文是活动的)是一个重要问题。cmwq 试图将并发性保持在最小但足够的水平。最小是为了节省资源,足够是确保系统以其最大容量运行。
每个绑定到实际 CPU 的工作线程池通过挂接到调度器来实现并发管理。每当活跃的工作者唤醒或休眠时,工作线程池都会收到通知,并跟踪当前可运行的工作者数量。通常,工作项不应占用大量 CPU 并消耗大量周期。这意味着保持足够的并发性以防止工作处理停滞是最佳的。只要 CPU 上有一个或多个可运行的工作者,工作线程池就不会开始执行新的工作,但是,当最后一个正在运行的工作者进入睡眠状态时,它会立即调度一个新的工作者,以便在有待处理工作项时 CPU 不会空闲。这允许使用最少数量的工作者而不会损失执行带宽。
保留空闲工作者除了 kworker 线程的内存空间外没有其他成本,因此 cmwq 会在终止它们之前保留空闲工作者一段时间。
对于非绑定工作队列,后端池的数量是动态的。可以使用 apply_workqueue_attrs()
为非绑定工作队列分配自定义属性,工作队列将自动创建与这些属性匹配的后端工作线程池。调节并发级别的责任在于用户。还有一个标志可以标记绑定工作队列以忽略并发管理。详情请参考 API 部分。
前进保障依赖于在需要更多执行上下文时可以创建工作者,这反过来又通过使用救援工作者得到保障。所有可能在处理内存回收的代码路径中使用的工作项都必须排队到为内存压力下的执行预留了救援工作者的工作队列中。否则,工作线程池可能会因为等待执行上下文释放而死锁。
应用程序编程接口 (API)¶
alloc_workqueue()
分配一个工作队列。原始的 create_*workqueue()
函数已被弃用,并计划移除。alloc_workqueue()
接受三个参数——@name
、@flags
和 @max_active
。@name
是工作队列的名称,如果存在救援线程,它也用作救援线程的名称。
工作队列不再管理执行资源,而是作为前进保障、刷新和工作项属性的域。@flags
和 @max_active
控制工作项如何分配执行资源、调度和执行。
flags
¶
WQ_BH
BH 工作队列可以被视为软中断的便捷接口。BH 工作队列始终是每 CPU 的,所有 BH 工作项都按排队顺序在排队 CPU 的软中断上下文中执行。
所有 BH 工作队列的
max_active
必须为 0,并且WQ_HIGHPRI
是唯一允许的附加标志。BH 工作项不能睡眠。所有其他功能,如延迟排队、刷新和取消,都受支持。
WQ_UNBOUND
排队到非绑定工作队列的工作项由特殊的工作线程池服务,这些池托管不绑定到任何特定 CPU 的工作者。这使得工作队列表现为一个简单的执行上下文提供者,不带并发管理。非绑定工作线程池会尽快尝试启动工作项的执行。非绑定工作队列牺牲了局部性,但适用于以下情况。
预计并发级别需求会有较大波动,并且使用绑定工作队列可能会导致在发起者在不同 CPU 之间跳转时,跨不同 CPU 创建大量大部分未使用的工作者。
长时间运行的 CPU 密集型工作负载,可以由系统调度器更好地管理。
WQ_FREEZABLE
可冻结工作队列参与系统挂起操作的冻结阶段。工作队列上的工作项将被耗尽,并且在解冻之前不会有新的工作项开始执行。
WQ_MEM_RECLAIM
所有可能在内存回收路径中使用的工作队列必须设置此标志。无论内存压力如何,工作队列都保证至少有一个执行上下文。
WQ_HIGHPRI
高优先级工作队列的工作项被排队到目标 CPU 的高优先级工作线程池。高优先级工作线程池由具有更高 nice 级别的工作线程服务。
请注意,普通和高优先级工作线程池之间不相互作用。每个都维护自己独立的工作者池,并在其工作者之间实现并发管理。
WQ_CPU_INTENSIVE
CPU 密集型工作队列的工作项不计入并发级别。换句话说,可运行的 CPU 密集型工作项不会阻止同一工作线程池中的其他工作项开始执行。这对于预期会占用大量 CPU 周期并由系统调度器调节其执行的绑定工作项很有用。
尽管 CPU 密集型工作项不计入并发级别,但它们的执行开始仍然受并发管理调节,并且可运行的非 CPU 密集型工作项可能会延迟 CPU 密集型工作项的执行。
此标志对于非绑定工作队列无意义。
max_active
¶
@max_active
决定了每个 CPU 可以分配给工作队列的工作项的最大执行上下文数量。例如,当 @max_active
为 16 时,每个 CPU 最多可以同时执行 16 个工作队列的工作项。这始终是每 CPU 属性,即使对于非绑定工作队列也是如此。
@max_active
的最大限制是 2048,当指定为 0 时使用的默认值是 1024。这些值被选择得足够高,以便它们不是限制因素,同时在失控情况下提供保护。
工作队列的活跃工作项数量通常由工作队列的用户调节,更具体地说,由用户可以同时排队的工作项数量决定。除非有特定的需要来限制活跃工作项的数量,否则建议指定“0”。
一些用户依赖严格的执行顺序,即在任何给定时间只有一个工作项正在执行,并且工作项按排队顺序处理。虽然过去通过组合 @max_active
为 1 和 WQ_UNBOUND
可以实现这种行为,但现在不再是这样。请改用 alloc_ordered_workqueue()
。
执行场景示例¶
以下执行场景示例试图说明 cmwq 在不同配置下的行为。
工作项 w0、w1、w2 被排队到同一 CPU 上的绑定工作队列 q0。w0 占用 CPU 5 毫秒,然后休眠 10 毫秒,然后在完成前再次占用 CPU 5 毫秒。w1 和 w2 占用 CPU 5 毫秒,然后休眠 10 毫秒。
忽略所有其他任务、工作和处理开销,并假设简单的 FIFO 调度,以下是原始工作队列可能事件序列的一个高度简化版本。
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
的工作队列 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_MEM_RECLAIM
。每个设置了WQ_MEM_RECLAIM
的工作队列都为其保留了一个执行上下文。如果在内存回收期间使用的多个工作项之间存在依赖关系,它们应该被排队到各自独立的、都设置了WQ_MEM_RECLAIM
的工作队列中。除非需要严格的排序,否则没有必要使用 ST 工作队列。
除非有特定需求,否则建议将 @max_active 设置为 0。在大多数用例中,并发级别通常远低于默认限制。
工作队列充当前进保障(
WQ_MEM_RECLAIM
)、刷新和工作项属性的域。不涉及内存回收、不需要作为一组工作项的一部分进行刷新,且不需要任何特殊属性的工作项,可以使用系统工作队列之一。使用专用工作队列和系统工作队列在执行特性上没有区别。注意:如果某些东西可能生成超过 @max_active 的未完成工作项(请对您的生产者进行压力测试),它可能会使系统工作队列饱和,并可能导致死锁。它应该使用自己的专用工作队列,而不是系统工作队列。
除非工作项预计会消耗大量 CPU 周期,否则使用绑定工作队列通常是有益的,因为这会增加工作队列操作和工作项执行的局部性。
亲和范围¶
非绑定工作队列根据其亲和范围对 CPU 进行分组,以提高缓存局部性。例如,如果一个工作队列使用默认的“cache”亲和范围,它将根据最后一级缓存边界对 CPU 进行分组。排队到该工作队列的工作项将被分配给与发起 CPU 共享最后一级缓存的某个 CPU 上的工作者。一旦启动,工作者是否允许移出该范围取决于该范围的 affinity_strict
设置。
工作队列目前支持以下亲和范围。
default(默认)
使用模块参数
workqueue.default_affinity_scope
中的范围,该参数始终设置为以下范围之一。cpu
CPU 不分组。在一个 CPU 上发出的工作项由同一 CPU 上的工作者处理。这使得非绑定工作队列表现为不带并发管理的每 CPU 工作队列。
smt
CPU 根据 SMT 边界分组。这通常意味着每个物理 CPU 核心的逻辑线程被分组在一起。
cache(缓存)
CPU 根据缓存边界分组。使用哪个具体的缓存边界由架构代码决定。在许多情况下使用 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,表示亲和范围不严格。当一个工作项开始执行时,工作队列会尽力确保工作者在其亲和范围之内,这被称为“归位”(repatriation)。一旦启动,调度器可以根据需要自由地将工作者移动到系统中的任何位置。这使得在受益于范围局部性的同时,如果必要且可用,仍能利用其他 CPU。
如果设置为 1,则保证该范围内的所有工作者始终在该范围之内。这在跨越亲和范围可能产生其他影响时可能有用,例如在功耗或工作负载隔离方面。严格的 NUMA 范围也可以用于匹配旧内核的工作队列行为。
亲和范围与性能¶
如果非绑定工作队列的行为在绝大多数用例中无需进一步调优即可达到最佳,那将是理想的。不幸的是,在当前内核中,局部性和利用率之间存在显著的权衡,这使得在大量使用工作队列时需要明确配置。
更高的局部性带来更高的效率,即在消耗相同 CPU 周期数的情况下完成更多工作。然而,如果工作项没有被发起者充分分散到各个亲和范围中,更高的局部性也可能导致整体系统利用率降低。以下使用 dm-crypt 进行的性能测试清楚地说明了这种权衡。
测试在一颗拥有 12 核心/24 线程、分布在四个 L3 缓存(AMD Ryzen 9 3900x)上的 CPU 上运行。为保持一致性,CPU 时钟加速已关闭。/dev/dm-0
是在 NVME SSD(三星 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 |
cache (严格) |
1166.00 ±0.71 |
99.35 ±0.01 |
当有足够多的发起者分布在整个系统时,“cache”模式,无论是严格的还是非严格的,都没有缺点。所有三种配置都使整个机器达到饱和,但由于局部性提高,缓存亲和性配置的性能优于其他配置 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 |
cache (严格) |
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 |
cache (严格) |
828.20 ±4.49 |
66.84 ±0.29 |
现在,局部性与利用率之间的权衡更加清晰。“cache”模式相比“system”模式显示 2% 的带宽损失,而“cache (严格)”模式则高达 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 用于改善非绑定工作项的执行局部性。存在多种 pod 类型,每种 wq_affn_scope 对应一种,系统中每个 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_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 为一的非绑定工作队列。
返回
成功时返回指向已分配工作队列的指针,失败时返回 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 的 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
排队前等待的 jiffies 数量
描述
相当于 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
排队前等待的 jiffies 数量
描述
在本地 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
参数
pool
迭代游标
pi
用于迭代的整数
描述
这必须在持有 wq_pool_mutex 或 RCU 读锁定下调用。如果池需要在当前锁定失效后继续使用,调用者有责任保证该池保持在线。
if/else 子句仅用于 lockdep 断言,可以忽略。
-
for_each_pool_worker¶
for_each_pool_worker (worker, pool)
遍历 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 的工作者的指针,否则为 NULL
。
-
void move_linked_works(struct work_struct *work, struct list_head *head, struct work_struct **nextp)¶
将关联工作项移动到列表
参数
struct work_struct *work
要调度的系列工作项的起始
struct list_head *head
要将 work 追加到的目标列表
struct work_struct **nextp
用于嵌套工作列表遍历的输出参数
描述
将从 work 开始的关联工作项调度到 head。要调度的系列工作项从 work 开始,并包含其前身设置了 WORK_STRUCT_LINKED 标志的任何连续工作项。有关 nextp 的详细信息,请参阅 assign_work()
。
上下文
raw_spin_lock_irq(pool->lock)。
-
bool assign_work(struct work_struct *work, struct worker *worker, struct work_struct **nextp)¶
将工作项及其关联的工作项分配给一个工作者
参数
struct work_struct *work
要分配的工作项
struct worker *worker
要分配到的工作者
struct work_struct **nextp
用于嵌套工作列表遍历的输出参数
描述
将 work 及其关联的工作项分配给 worker。如果 work 已经在同一工作池中的另一个工作者执行,它将被转移到那里。
如果 nextp 不为 NULL,则更新它以指向上次调度工作的下一个工作项。这允许 assign_work()
嵌套在 list_for_each_entry_safe()
中。
如果 work 成功分配给 worker,则返回 true
。如果 work 被转移到已经执行它的另一个工作者,则返回 false
。
-
bool kick_pool(struct worker_pool *pool)¶
如有必要,唤醒一个空闲工作者
参数
struct worker_pool *pool
要踢动的工作池
描述
pool 可能有待处理的工作项。如有必要,唤醒工作者。返回是否唤醒了工作者。
-
void wq_worker_running(struct task_struct *task)¶
一个工作者再次运行
参数
struct task_struct *task
正在唤醒的任务
描述
当一个工作者从 schedule() 返回时调用此函数
-
void wq_worker_sleeping(struct task_struct *task)¶
一个工作者即将进入睡眠
参数
struct task_struct *task
即将进入睡眠的任务
描述
当一个繁忙的工作者即将进入睡眠时,此函数从 schedule() 调用。
-
void wq_worker_tick(struct task_struct *task)¶
当一个 kworker 运行时发生了调度器时钟中断
参数
struct task_struct *task
当前正在运行的任务
描述
从 sched_tick() 调用。我们处于 IRQ 上下文,并且可以安全地访问遵循“K”锁定规则的当前工作者的字段。
-
work_func_t wq_worker_last_func(struct task_struct *task)¶
检索工作者的最后一个工作函数
参数
struct task_struct *task
要检索其最后一个工作函数的任务。
描述
确定工作者执行的最后一个函数。此函数从调度器调用,以获取工作者最后已知身份。
当一个 kworker 即将进入睡眠时,此函数在 schedule() 期间调用。它被 psi 用于在出队期间识别聚合工作者,以便当该工作者是系统或 cgroup 中最后一个进入睡眠的任务时,允许周期性聚合关闭。
由于此函数不涉及任何工作队列相关的锁定,因此只有在从调度器的入队和出队路径内部调用时,它才返回稳定值,此时 task(必须是 kworker)保证没有处理任何工作。
上下文
raw_spin_lock_irq(rq->lock)
返回
作为工作者执行的最后一个工作函数 current
,如果尚未执行任何工作,则为 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 工作队列返回
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
要更新的工作队列
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
要拔出其最旧 pwq 的 workqueue_struct
描述
此函数仅应为有序工作队列调用,其中只有最旧的 pwq 被拔出,其他 pwq 被插入以暂停执行,从而确保正确的工作项顺序。
dfl_pwq --------------+ [P] - plugged
|
v
pwqs -> A -> B [P] -> C [P] (newest)
| | |
1 3 5
| | |
2 4 6
当最旧的 pwq 被耗尽并移除时,应调用此函数以拔出下一个最旧的 pwq,以开始其工作项执行。请注意,pwq 按照从旧到新的顺序链接到 wq->pwqs 中,因此列表中的第一个是最旧的。
-
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)¶
从工作列表中窃取工作项并禁用中断
参数
struct work_struct *work
要窃取的工作项
u32 cflags
WORK_CANCEL_
标志unsigned long *irq_flags
存储中断状态的位置
描述
尝试获取 work 的 PENDING 位。此函数可以处理处于任何稳定状态的 work - 空闲、在计时器上或在工作列表中。
成功返回时,如果 >= 0,则中断被禁用,调用者负责使用 local_irq_restore(*irq_flags) 释放它。
此函数可以从包括 IRQ 处理程序在内的任何上下文安全调用。
返回
1
如果 work 待处理并且我们成功窃取了 PENDING
0
如果 work 空闲并且我们声明了 PENDING
-EAGAIN
如果 PENDING 当前无法获取,则可安全地忙重试
注意
在 >= 0 返回时,调用者拥有 work 的 PENDING 位。为避免在持有 PENDING 且 work 不在队列中时被中断,入口处必须禁用中断。这与 delayed_work->timer 是中断安全的结合,确保我们在有限的短时间内返回 -EAGAIN。
-
bool work_grab_pending(struct work_struct *work, u32 cflags, unsigned long *irq_flags)¶
从工作列表中窃取工作项并禁用中断
参数
struct work_struct *work
要窃取的工作项
u32 cflags
WORK_CANCEL_
标志unsigned long *irq_flags
存储 IRQ 状态的位置
描述
获取 work 的 PENDING 位。work 可以处于任何稳定状态 - 空闲、在计时器上或在工作列表中。
可以从任何上下文调用。返回时中断被禁用,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_* 标志
描述
将属于 pwq 的 work 插入到 head 之后。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
排队前等待的 jiffies 数量
描述
我们将 delayed_work 排队到特定 CPU,对于非零延迟,调用者必须确保它在线且不会消失。未能确保这一点的调用者,可能会导致 dwork->timer 排队到离线 CPU,这将阻止 dwork->work 的排队,除非离线 CPU 再次上线。
返回
如果 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
排队前等待的 jiffies 数量
描述
如果 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)¶
准备 pwq 以进行工作队列刷新
参数
struct workqueue_struct *wq
正在刷新的工作队列
int flush_color
新的刷新颜色,< 0 表示无操作
int work_color
新的工作颜色,< 0 表示无操作
描述
准备 pwq 以进行工作队列刷新。
如果 flush_color 非负,则所有 pwq 上的 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 非负,则所有 pwq 都应具有与 work_color 之前的相同的 work_color,并且所有 pwq 都将前进到 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。
此函数可以从包括 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]()。只有当 work 的禁用计数为 0 时才能排队。
可以从任何上下文调用。如果禁用计数达到 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)¶
释放一个工作队列属性
-
struct workqueue_attrs *alloc_workqueue_attrs(void)¶
分配一个工作队列属性
参数
void
无参数
描述
分配一个新的工作队列属性,用默认设置初始化并返回。
返回
成功时返回分配的新工作队列属性。失败时返回 NULL
。
-
int init_worker_pool(struct worker_pool *pool)¶
初始化一个新分配并零初始化的工作者池
参数
struct worker_pool *pool
要初始化的工作者池
描述
初始化一个新分配并零初始化的 pool。它还会分配 pool->attrs。
返回
成功时返回 0,失败时返回 -errno。即使失败,pool 内部的所有字段也已初始化,并且可以安全地对 pool 调用 put_unbound_pool()
来释放它。
-
void put_unbound_pool(struct worker_pool *pool)¶
释放一个工作者池
参数
struct worker_pool *pool
要释放的工作者池
描述
释放 pool。如果其引用计数(refcnt)达到零,它将以 RCU 安全的方式销毁。get_unbound_pool()
在其失败路径上调用此函数,并且此函数应该能够释放已通过(无论成功与否)init_worker_pool()
的池。
应在持有 wq_pool_mutex 的情况下调用。
-
struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)¶
获取具有指定属性的工作者池
参数
const struct workqueue_attrs *attrs
要获取的工作者池的属性
描述
获取一个与 attrs 具有相同属性的工作者池,增加引用计数并返回它。如果已存在匹配的工作者池,则使用现有池;否则,此函数尝试创建一个新池。
应在持有 wq_pool_mutex 的情况下调用。
返回
成功时,返回与 attrs 具有相同属性的工作者池。失败时返回 NULL
。
-
void wq_calc_pod_cpumask(struct workqueue_attrs *attrs, int cpu)¶
计算一个工作队列属性(wq_attrs)的 CPU 掩码(cpumask),用于一个 pod
参数
struct workqueue_attrs *attrs
目标工作队列的默认 pwq 的 wq_attrs
int cpu
目标 CPU
描述
计算具有 attrs 的工作队列应在 pod 上使用的 CPU 掩码。结果存储在 attrs->__pod_cpumask 中。
如果未启用 pod 亲和性,则始终使用 attrs->cpumask。如果已启用,且 pod 包含 attrs 所请求的在线 CPU,则返回的 CPU 掩码是 pod 的可能 CPU 与 attrs->cpumask 的交集。
调用者负责确保 pod 的 CPU 掩码保持稳定。
-
int apply_workqueue_attrs(struct workqueue_struct *wq, const struct workqueue_attrs *attrs)¶
将新的工作队列属性应用于无绑定工作队列
参数
struct workqueue_struct *wq
目标工作队列
const struct workqueue_attrs *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 对于一个跨多个 pod 的 CPU 掩码(cpumask)的工作队列离线时,已经为该工作队列执行工作项的工作者将失去其 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
目标工作队列
描述
安全地销毁一个工作队列。所有当前待处理的工作将首先完成。
此函数不保证使用 queue_delayed_work()
及类似函数提交的非待处理工作会在销毁工作队列之前完成。根本问题是,目前工作队列无法访问非待处理的延迟工作(delayed_work)。延迟工作只在定时器侧链接。因此,在调用此函数之前,所有延迟工作都必须被取消。
待办事项:如果上述问题不存在,并且 destroy_workqueue()
能够干净地取消所有待处理和非待处理的延迟工作,那会更好。
-
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 context)中调用。
-
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
。
使用此函数将 min_active 值调整到 0 和当前 max_active 之间。
-
struct work_struct *current_work(void)¶
检索
current
任务的工作结构体
参数
void
无参数
描述
判断 current
任务是否为工作队列工作者以及它正在处理什么。这有助于查明 current
任务运行的上下文。
返回
如果 current
任务是工作队列工作者,则返回工作结构体;否则返回 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_workqueue,每个都有自己的拥堵状态。一个工作队列在一个 CPU 上拥堵并不意味着该工作队列在其他任何 CPU 上也拥堵。
返回
如果拥堵,则返回 true
;否则返回 false
。
-
unsigned int work_busy(struct work_struct *work)¶
测试一个工作当前是待处理还是正在运行
参数
struct work_struct *work
要测试的工作
描述
测试 work 当前是待处理还是正在运行。此函数没有同步机制,测试结果不可靠,仅作为参考或用于调试。
返回
WORK_BUSY_* 位的或操作位掩码。
-
void set_worker_desc(const char *fmt, ...)¶
为当前工作项设置描述
参数
const char *fmt
printf 风格的格式字符串
...
格式字符串的参数
描述
此函数可以由正在运行的工作函数调用,以描述工作项的用途。如果工作者任务被转储,此信息将一起打印出来以帮助调试。描述的最大长度为 WORKER_DESC_LEN,包括末尾的 '0'。
-
void print_worker_info(const char *log_lvl, struct task_struct *task)¶
打印工作者信息和描述
参数
const char *log_lvl
打印时使用的日志级别
struct task_struct *task
目标任务
描述
如果 task 是工作者且当前正在执行工作项,则打印出正在服务的工工作队列名称以及当前执行的工作项通过 set_worker_desc()
设置的工作者描述。
只要 task_struct 本身可访问,此函数就可以安全地在任何任务上调用。虽然安全,但此函数未同步,可能会打印出有限长度的混合或垃圾信息。
-
void show_one_workqueue(struct workqueue_struct *wq)¶
转储指定工作队列的状态
参数
struct workqueue_struct *wq
将打印其状态的工作队列
-
void show_one_worker_pool(struct worker_pool *pool)¶
转储指定工作者池的状态
参数
struct worker_pool *pool
将打印其状态的工作者池
-
void show_all_workqueues(void)¶
转储工作队列状态
参数
void
无参数
描述
由系统请求处理程序调用,并打印出所有忙碌的工作队列和池。
-
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 - 为 attrs 或 pwqs 分配内存失败。
-
int workqueue_sysfs_register(struct workqueue_struct *wq)¶
使工作队列在 sysfs 中可见
参数
struct workqueue_struct *wq
要注册的工作队列
描述
在 sysfs 的 /sys/bus/workqueue/devices 目录下公开 wq。如果设置了 WQ_SYSFS,alloc_workqueue*() 会自动调用此函数,这是首选方法。
工作队列用户只有在希望在工作队列在 sysfs 中可见之前应用 workqueue_attrs 时,才应直接使用此函数;否则,apply_workqueue_attrs()
可能会与用户空间更新属性的操作发生竞争。
返回
成功时返回 0,失败时返回 -errno。
-
void workqueue_sysfs_unregister(struct workqueue_struct *wq)¶
撤销
workqueue_sysfs_register()
的操作
-
void workqueue_init_early(void)¶
工作队列子系统的早期初始化
参数
void
无参数
描述
这是三阶段工作队列子系统初始化的第一步,在内存分配、CPU 掩码和 IDR 等基本要素就绪后立即调用。它设置所有数据结构和系统工作队列,并允许早期启动代码创建工作队列以及排队/取消工作项。实际工作项的执行仅在内核线程(kthreads)可以创建和调度,且就在早期初始化调用(early initcalls)之前开始。
-
void workqueue_init(void)¶
使工作队列子系统完全上线
参数
void
无参数
描述
这是三阶段工作队列子系统初始化的第二步,一旦内核线程(kthreads)可以创建和调度就立即调用。工作队列已经创建,工作项也已排队,但目前还没有工作者执行这些工作项。此函数用初始工作者填充工作者池,并启用未来的内核工作者创建。
-
void workqueue_init_topology(void)¶
为无绑定工作队列初始化 CPU pod
参数
void
无参数
描述
这是三阶段工作队列子系统初始化的第三步,在对称多处理(SMP)和拓扑信息完全初始化后调用。它相应地初始化无绑定 CPU pod。