0001 .. SPDX-License-Identifier: GPL-2.0
0002 .. include:: ../disclaimer-zh_CN.rst
0003
0004 :Original: Documentation/core-api/workqueue.rst
0005
0006 :翻译:
0007
0008 司延腾 Yanteng Si <siyanteng@loongson.cn>
0009 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>
0010
0011 .. _cn_workqueue.rst:
0012
0013 =========================
0014 并发管理的工作队列 (cmwq)
0015 =========================
0016
0017 :日期: September, 2010
0018 :作者: Tejun Heo <tj@kernel.org>
0019 :作者: Florian Mickler <florian@mickler.org>
0020
0021
0022 简介
0023 ====
0024
0025 在很多情况下,需要一个异步进程的执行环境,工作队列(wq)API是这种情况下
0026 最常用的机制。
0027
0028 当需要这样一个异步执行上下文时,一个描述将要执行的函数的工作项(work,
0029 即一个待执行的任务)被放在队列中。一个独立的线程作为异步执行环境。该队
0030 列被称为workqueue,线程被称为工作者(worker,即执行这一队列的线程)。
0031
0032 当工作队列上有工作项时,工作者会一个接一个地执行与工作项相关的函数。当
0033 工作队列中没有任何工作项时,工作者就会变得空闲。当一个新的工作项被排入
0034 队列时,工作者又开始执行。
0035
0036
0037 为什么要cmwq?
0038 =============
0039
0040 在最初的wq实现中,多线程(MT)wq在每个CPU上有一个工作者线程,而单线程
0041 (ST)wq在全系统有一个工作者线程。一个MT wq需要保持与CPU数量相同的工
0042 作者数量。这些年来,内核增加了很多MT wq的用户,随着CPU核心数量的不断
0043 增加,一些系统刚启动就达到了默认的32k PID的饱和空间。
0044
0045 尽管MT wq浪费了大量的资源,但所提供的并发性水平却不能令人满意。这个限
0046 制在ST和MT wq中都有,只是在MT中没有那么严重。每个wq都保持着自己独立的
0047 工作者池。一个MT wq只能为每个CPU提供一个执行环境,而一个ST wq则为整个
0048 系统提供一个。工作项必须竞争这些非常有限的执行上下文,从而导致各种问题,
0049 包括在单一执行上下文周围容易发生死锁。
0050
0051 (MT wq)所提供的并发性水平和资源使用之间的矛盾也迫使其用户做出不必要的权衡,比
0052 如libata选择使用ST wq来轮询PIO,并接受一个不必要的限制,即没有两个轮
0053 询PIO可以同时进行。由于MT wq并没有提供更好的并发性,需要更高层次的并
0054 发性的用户,如async或fscache,不得不实现他们自己的线程池。
0055
0056 并发管理工作队列(cmwq)是对wq的重新实现,重点是以下目标。
0057
0058 * 保持与原始工作队列API的兼容性。
0059
0060 * 使用由所有wq共享的每CPU统一的工作者池,在不浪费大量资源的情况下按
0061 * 需提供灵活的并发水平。
0062
0063 * 自动调节工作者池和并发水平,使API用户不需要担心这些细节。
0064
0065
0066 设计
0067 ====
0068
0069 为了简化函数的异步执行,引入了一个新的抽象概念,即工作项。
0070
0071 一个工作项是一个简单的结构,它持有一个指向将被异步执行的函数的指针。
0072 每当一个驱动程序或子系统希望一个函数被异步执行时,它必须建立一个指
0073 向该函数的工作项,并在工作队列中排队等待该工作项。(就是挂到workqueue
0074 队列里面去)
0075
0076 特定目的线程,称为工作线程(工作者),一个接一个地执行队列中的功能。
0077 如果没有工作项排队,工作者线程就会闲置。这些工作者线程被管理在所谓
0078 的工作者池中。
0079
0080 cmwq设计区分了面向用户的工作队列,子系统和驱动程序在上面排队工作,
0081 以及管理工作者池和处理排队工作项的后端机制。
0082
0083 每个可能的CPU都有两个工作者池,一个用于正常的工作项,另一个用于高
0084 优先级的工作项,还有一些额外的工作者池,用于服务未绑定工作队列的工
0085 作项目——这些后备池的数量是动态的。
0086
0087 当他们认为合适的时候,子系统和驱动程序可以通过特殊的
0088 ``workqueue API`` 函数创建和排队工作项。他们可以通过在工作队列上
0089 设置标志来影响工作项执行方式的某些方面,他们把工作项放在那里。这些
0090 标志包括诸如CPU定位、并发限制、优先级等等。要获得详细的概述,请参
0091 考下面的 ``alloc_workqueue()`` 的 API 描述。
0092
0093 当一个工作项被排入一个工作队列时,目标工作池将根据队列参数和工作队
0094 列属性确定,并被附加到工作池的共享工作列表上。例如,除非特别重写,
0095 否则一个绑定的工作队列的工作项将被排在与发起线程运行的CPU相关的普
0096 通或高级工作工作者池的工作项列表中。
0097
0098 对于任何工作者池的实施,管理并发水平(有多少执行上下文处于活动状
0099 态)是一个重要问题。最低水平是为了节省资源,而饱和水平是指系统被
0100 充分使用。
0101
0102 每个与实际CPU绑定的worker-pool通过钩住调度器来实现并发管理。每当
0103 一个活动的工作者被唤醒或睡眠时,工作者池就会得到通知,并跟踪当前可
0104 运行的工作者的数量。一般来说,工作项不会占用CPU并消耗很多周期。这
0105 意味着保持足够的并发性以防止工作处理停滞应该是最优的。只要CPU上有
0106 一个或多个可运行的工作者,工作者池就不会开始执行新的工作,但是,当
0107 最后一个运行的工作者进入睡眠状态时,它会立即安排一个新的工作者,这
0108 样CPU就不会在有待处理的工作项目时闲置。这允许在不损失执行带宽的情
0109 况下使用最少的工作者。
0110
0111 除了kthreads的内存空间外,保留空闲的工作者并没有其他成本,所以cmwq
0112 在杀死它们之前会保留一段时间的空闲。
0113
0114 对于非绑定的工作队列,后备池的数量是动态的。可以使用
0115 ``apply_workqueue_attrs()`` 为非绑定工作队列分配自定义属性,
0116 workqueue将自动创建与属性相匹配的后备工作者池。调节并发水平的责任在
0117 用户身上。也有一个标志可以将绑定的wq标记为忽略并发管理。
0118 详情请参考API部分。
0119
0120 前进进度的保证依赖于当需要更多的执行上下文时可以创建工作者,这也是
0121 通过使用救援工作者来保证的。所有可能在处理内存回收的代码路径上使用
0122 的工作项都需要在wq上排队,wq上保留了一个救援工作者,以便在内存有压
0123 力的情况下下执行。否则,工作者池就有可能出现死锁,等待执行上下文释
0124 放出来。
0125
0126
0127 应用程序编程接口 (API)
0128 ======================
0129
0130 ``alloc_workqueue()`` 分配了一个wq。原来的 ``create_*workqueue()``
0131 函数已被废弃,并计划删除。 ``alloc_workqueue()`` 需要三个
0132 参数 - ``@name`` , ``@flags`` 和 ``@max_active`` 。
0133 ``@name`` 是wq的名称,如果有的话,也用作救援线程的名称。
0134
0135 一个wq不再管理执行资源,而是作为前进进度保证、刷新(flush)和
0136 工作项属性的域。 ``@flags`` 和 ``@max_active`` 控制着工作
0137 项如何被分配执行资源、安排和执行。
0138
0139
0140 ``flags``
0141 ---------
0142
0143 ``WQ_UNBOUND``
0144 排队到非绑定wq的工作项由特殊的工作者池提供服务,这些工作者不
0145 绑定在任何特定的CPU上。这使得wq表现得像一个简单的执行环境提
0146 供者,没有并发管理。非绑定工作者池试图尽快开始执行工作项。非
0147 绑定的wq牺牲了局部性,但在以下情况下是有用的。
0148
0149 * 预计并发水平要求会有很大的波动,使用绑定的wq最终可能会在不
0150 同的CPU上产生大量大部分未使用的工作者,因为发起线程在不同
0151 的CPU上跳转。
0152
0153 * 长期运行的CPU密集型工作负载,可以由系统调度器更好地管理。
0154
0155 ``WQ_FREEZABLE``
0156 一个可冻结的wq参与了系统暂停操作的冻结阶段。wq上的工作项被
0157 排空,在解冻之前没有新的工作项开始执行。
0158
0159 ``WQ_MEM_RECLAIM``
0160 所有可能在内存回收路径中使用的wq都必须设置这个标志。无论内
0161 存压力如何,wq都能保证至少有一个执行上下文。
0162
0163 ``WQ_HIGHPRI``
0164 高优先级wq的工作项目被排到目标cpu的高优先级工作者池中。高
0165 优先级的工作者池由具有较高级别的工作者线程提供服务。
0166
0167 请注意,普通工作者池和高优先级工作者池之间并不相互影响。他
0168 们各自维护其独立的工作者池,并在其工作者之间实现并发管理。
0169
0170 ``WQ_CPU_INTENSIVE``
0171 CPU密集型wq的工作项对并发水平没有贡献。换句话说,可运行的
0172 CPU密集型工作项不会阻止同一工作者池中的其他工作项开始执行。
0173 这对于那些预计会占用CPU周期的绑定工作项很有用,这样它们的
0174 执行就会受到系统调度器的监管。
0175
0176 尽管CPU密集型工作项不会对并发水平做出贡献,但它们的执行开
0177 始仍然受到并发管理的管制,可运行的非CPU密集型工作项会延迟
0178 CPU密集型工作项的执行。
0179
0180 这个标志对于未绑定的wq来说是没有意义的。
0181
0182
0183 ``max_active``
0184 --------------
0185
0186 ``@max_active`` 决定了每个CPU可以分配给wq的工作项的最大执行上
0187 下文数量。例如,如果 ``@max_active为16`` ,每个CPU最多可以同
0188 时执行16个wq的工作项。
0189
0190 目前,对于一个绑定的wq, ``@max_active`` 的最大限制是512,当指
0191 定为0时使用的默认值是256。对于非绑定的wq,其限制是512和
0192 4 * ``num_possible_cpus()`` 中的较高值。这些值被选得足够高,所
0193 以它们不是限制性因素,同时会在失控情况下提供保护。
0194
0195 一个wq的活动工作项的数量通常由wq的用户来调节,更具体地说,是由用
0196 户在同一时间可以排列多少个工作项来调节。除非有特定的需求来控制活动
0197 工作项的数量,否则建议指定 为"0"。
0198
0199 一些用户依赖于ST wq的严格执行顺序。 ``@max_active`` 为1和 ``WQ_UNBOUND``
0200 的组合用来实现这种行为。这种wq上的工作项目总是被排到未绑定的工作池
0201 中,并且在任何时候都只有一个工作项目处于活动状态,从而实现与ST wq相
0202 同的排序属性。
0203
0204 在目前的实现中,上述配置只保证了特定NUMA节点内的ST行为。相反,
0205 ``alloc_ordered_queue()`` 应该被用来实现全系统的ST行为。
0206
0207
0208 执行场景示例
0209 ============
0210
0211 下面的示例执行场景试图说明cmwq在不同配置下的行为。
0212
0213 工作项w0、w1、w2被排到同一个CPU上的一个绑定的wq q0上。w0
0214 消耗CPU 5ms,然后睡眠10ms,然后在完成之前再次消耗CPU 5ms。
0215
0216 忽略所有其他的任务、工作和处理开销,并假设简单的FIFO调度,
0217 下面是一个高度简化的原始wq的可能事件序列的版本。::
0218
0219 TIME IN MSECS EVENT
0220 0 w0 starts and burns CPU
0221 5 w0 sleeps
0222 15 w0 wakes up and burns CPU
0223 20 w0 finishes
0224 20 w1 starts and burns CPU
0225 25 w1 sleeps
0226 35 w1 wakes up and finishes
0227 35 w2 starts and burns CPU
0228 40 w2 sleeps
0229 50 w2 wakes up and finishes
0230
0231 And with cmwq with ``@max_active`` >= 3, ::
0232
0233 TIME IN MSECS EVENT
0234 0 w0 starts and burns CPU
0235 5 w0 sleeps
0236 5 w1 starts and burns CPU
0237 10 w1 sleeps
0238 10 w2 starts and burns CPU
0239 15 w2 sleeps
0240 15 w0 wakes up and burns CPU
0241 20 w0 finishes
0242 20 w1 wakes up and finishes
0243 25 w2 wakes up and finishes
0244
0245 如果 ``@max_active`` == 2, ::
0246
0247 TIME IN MSECS EVENT
0248 0 w0 starts and burns CPU
0249 5 w0 sleeps
0250 5 w1 starts and burns CPU
0251 10 w1 sleeps
0252 15 w0 wakes up and burns CPU
0253 20 w0 finishes
0254 20 w1 wakes up and finishes
0255 20 w2 starts and burns CPU
0256 25 w2 sleeps
0257 35 w2 wakes up and finishes
0258
0259 现在,我们假设w1和w2被排到了不同的wq q1上,这个wq q1
0260 有 ``WQ_CPU_INTENSIVE`` 设置::
0261
0262 TIME IN MSECS EVENT
0263 0 w0 starts and burns CPU
0264 5 w0 sleeps
0265 5 w1 and w2 start and burn CPU
0266 10 w1 sleeps
0267 15 w2 sleeps
0268 15 w0 wakes up and burns CPU
0269 20 w0 finishes
0270 20 w1 wakes up and finishes
0271 25 w2 wakes up and finishes
0272
0273
0274 指南
0275 ====
0276
0277 * 如果一个wq可能处理在内存回收期间使用的工作项目,请不
0278 要忘记使用 ``WQ_MEM_RECLAIM`` 。每个设置了
0279 ``WQ_MEM_RECLAIM`` 的wq都有一个为其保留的执行环境。
0280 如果在内存回收过程中使用的多个工作项之间存在依赖关系,
0281 它们应该被排在不同的wq中,每个wq都有 ``WQ_MEM_RECLAIM`` 。
0282
0283 * 除非需要严格排序,否则没有必要使用ST wq。
0284
0285 * 除非有特殊需要,建议使用0作为@max_active。在大多数使用情
0286 况下,并发水平通常保持在默认限制之下。
0287
0288 * 一个wq作为前进进度保证(WQ_MEM_RECLAIM,冲洗(flush)和工
0289 作项属性的域。不涉及内存回收的工作项,不需要作为工作项组的一
0290 部分被刷新,也不需要任何特殊属性,可以使用系统中的一个wq。使
0291 用专用wq和系统wq在执行特性上没有区别。
0292
0293 * 除非工作项预计会消耗大量的CPU周期,否则使用绑定的wq通常是有
0294 益的,因为wq操作和工作项执行中的定位水平提高了。
0295
0296
0297 调试
0298 ====
0299
0300 因为工作函数是由通用的工作者线程执行的,所以需要一些手段来揭示一些行为不端的工作队列用户。
0301
0302 工作者线程在进程列表中显示为: ::
0303
0304 root 5671 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/0:1]
0305 root 5672 0.0 0.0 0 0 ? S 12:07 0:00 [kworker/1:2]
0306 root 5673 0.0 0.0 0 0 ? S 12:12 0:00 [kworker/0:0]
0307 root 5674 0.0 0.0 0 0 ? S 12:13 0:00 [kworker/1:0]
0308
0309 如果kworkers失控了(使用了太多的cpu),有两类可能的问题:
0310
0311 1. 正在迅速调度的事情
0312 2. 一个消耗大量cpu周期的工作项。
0313
0314 第一个可以用追踪的方式进行跟踪: ::
0315
0316 $ echo workqueue:workqueue_queue_work > /sys/kernel/debug/tracing/set_event
0317 $ cat /sys/kernel/debug/tracing/trace_pipe > out.txt
0318 (wait a few secs)
0319
0320 如果有什么东西在工作队列上忙着做循环,它就会主导输出,可以用工作项函数确定违规者。
0321
0322 对于第二类问题,应该可以只检查违规工作者线程的堆栈跟踪。 ::
0323
0324 $ cat /proc/THE_OFFENDING_KWORKER/stack
0325
0326 工作项函数在堆栈追踪中应该是微不足道的。
0327
0328 不可重入条件
0329 ============
0330
0331 工作队列保证,如果在工作项排队后满足以下条件,则工作项不能重入:
0332
0333
0334 1. 工作函数没有被改变。
0335 2. 没有人将该工作项排到另一个工作队列中。
0336 3. 该工作项尚未被重新启动。
0337
0338 换言之,如果上述条件成立,则保证在任何给定时间最多由一个系统范围内的工作程序执行
0339 该工作项。
0340
0341 请注意,在self函数中将工作项重新排队(到同一队列)不会破坏这些条件,因此可以安全
0342 地执行此操作。否则在破坏工作函数内部的条件时需要小心。
0343
0344
0345 内核内联文档参考
0346 ================
0347
0348 该API在以下内核代码中:
0349
0350 include/linux/workqueue.h
0351
0352 kernel/workqueue.c