
Linux 内核 Workqueue 完全指南cmwq 并发管理机制、alloc_workqueue API 与亲和性调优【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux工作队列workqueue是 Linux 内核中最常用的异步执行机制驱动与子系统把要异步执行的函数封装成工作项work item投入队列由内核线程工人worker代为执行。本指南以 Documentation/core-api/workqueue.rst 为核心结合 include/linux/workqueue.h 与 kernel/workqueue.c 的源码实现完整讲解并发管理工作队列cmwq的设计动机、alloc_workqueue()的 flags 与max_active语义、亲和性作用域affinity scope的性能权衡以及使用wq_dump.py/wq_monitor.py进行配置检查、监控与调试的实战方法。读完本文你将能够为驱动或子系统正确选择工作队列属性、避免内存回收路径死锁并根据机器拓扑对 CPU 密集型工作队列做精细化调优。引言什么是工作队列内核中大量场景需要一个异步进程执行上下文某些函数不应该在发起者自己的执行流里同步运行而应该放到后台去处理。工作队列workqueue简称 wqAPI 就是为此提供的最常用机制其基本模型非常朴素当需要一个异步执行上下文时把描述要执行哪个函数的工作项work item放到一个队列上一个独立的线程充当异步执行上下文这个队列称为工作队列这个线程称为工人worker只要队列里还有工作项worker 就按顺序一个一个地执行它们关联的函数队列空了 worker 转入空闲新的工作项被投入队列后worker 再次开始执行。工作项是一个简单的结构体保存指向待异步执行函数的指针即 include/linux/workqueue.h 中的struct work_struct通常通过INIT_WORK()初始化、queue_work()投入队列。驱动或子系统希望某个函数异步执行时只需设置好指向该函数的工作项并把它挂到某个工作队列上即可。工作项既可以在线程上下文执行也可以在 BHsoftirq上下文执行——后者由WQ_BH标志开启。为什么需要 cmwq并发管理工作队列原始实现的困境在 cmwqConcurrency Managed Workqueue并发管理工作队列出现之前工作队列的实现存在两个突出的问题资源浪费一个多线程MT工作队列为每个 CPU 各保留一个 worker 线程单线程ST工作队列则全系统只有一个 worker。随着内核中 MT 工作队列用户不断增加、CPU 核心数持续攀升某些系统仅在启动阶段就会耗尽默认的 32k PID 空间——每个 MT wq 都需要每 CPU 一个线程多 CPU 大系统上开销极其可观。并发度不足每个工作队列维护自己独立的 worker 池MT wq 每 CPU 只能提供一个执行上下文ST wq 整个系统只有一个。工作项必须竞争这些极为有限的执行上下文带来了各种问题包括围绕单一执行上下文的死锁倾向。例如 libata 在轮询 PIO 时选择使用 ST wq被迫接受两个轮询 PIO 不能同时进行的不必要限制而需要更高并发度的 async、fscache 等用户不得不自己实现线程池。cmwq 的三大目标cmwq 是工作队列的重写版本聚焦于以下目标保持与原有工作队列 API 的兼容性queue_work()等接口不变使用所有工作队列共享的每 CPU 统一 worker 池按需提供灵活的并发度避免浪费大量资源自动调节 worker 池规模与并发水平让 API 使用者不必关心这些细节。从源码结构看这一设计在 kernel/workqueue.c 中体现为两个层次的抽象面向用户的工作队列struct workqueue_struct以及后端统一管理的struct worker_pool第 195 行附近与struct pool_workqueue第 271 行附近。设计架构工作项、工人与 worker 池工作项work item为简化函数的异步执行cmwq 引入工作项这一抽象一个持有将被异步执行的函数指针的简单结构体。每当驱动或子系统需要异步执行某函数就设置一个指向该函数的工作项并将其投入工作队列。线程池与 BH 池对于线程化工作队列名为[k]worker的专用线程从队列中依次取出函数执行没有工作时 worker 线程进入空闲状态。这些 worker 线程由worker-poolworker 池统一管理。cmwq 的设计把两类东西明确分开用户可见的工作队列子系统与驱动向它投递工作项后端机制管理 worker 池、处理已入队工作项。对于每个可能的 CPU系统维护两个 worker 池一个服务普通工作项另一个服务高优先级highpri工作项此外还有若干服务于unbound无绑定工作队列的额外 worker 池这类后备池的数量是动态的。BH 工作队列复用同一套框架但因为 BHsoftirq同一时刻只能有一个并发执行上下文无需担心并发管理每个每 CPU BH worker 池只包含一个伪 worker代表 BH 执行上下文。因此可以认为 BH 工作队列是 softirq 的一个便捷接口include/linux/workqueue.h 中WQ_BH 1 0的注释即execute in bottom half (softirq) context。工作项的投递路径当工作项被投入某个工作队列时内核根据投递参数与工作队列属性确定目标 worker 池并把它追加到该池的共享工作列表worklist上。例如除非被显式覆盖一个 bound绑定工作队列的工作项会被投递到发起者当前所在 CPU关联的普通或 highpri worker 池工作列表上。并发管理让并发度最小且足够对任何线程池实现而言管理并发水平同时活跃的执行上下文数量都是核心问题。cmwq 的目标是把并发度维持在最小且足够最小以节省资源足够以让系统满负荷运转。每个绑定到真实 CPU 的 worker 池通过挂接调度器实现并发管理每当活跃 worker 被唤醒或睡眠worker 池都会收到通知并持续跟踪当前可运行的 worker 数量。一般而言工作项不会被期望占用 CPU 太久因此只要维持足够的并发度防止工作处理停滞就是最优的。具体规则是只要 CPU 上还有一个或多个可运行的 workerworker 池不启动新工作的执行当最后一个正在运行的 worker 进入睡眠时立即调度一个新 worker让 CPU 在有挂起工作项时不至于闲置。这保证了用最少数量的 worker 就能不损失执行带宽。空闲 worker 保留的成本仅是 kthread 占用的内存因此 cmwq 会让空闲 worker 存活一段时间再销毁避免频繁创建/销毁线程的开销。对于 unbound 工作队列后备池数量是动态的可通过apply_workqueue_attrs()为 unbound 工作队列指定自定义属性工作队列会自动创建匹配属性的后备 worker 池此时调节并发度的责任落在使用者身上。此外还有一个标志可以把 bound wq 标记为忽略并发管理即下文WQ_CPU_INTENSIVE。前向进展保证与 rescue workercmwq 的前向进展forward progress保证依赖需要更多执行上下文时可以创建新 worker而这又通过rescue worker救援工人机制保障所有可能被内存回收memory reclaim路径使用的工作项必须投递到带有保留救援 worker 的工作队列上即设置WQ_MEM_RECLAIM标志。否则在内存压力下worker 池可能因等待执行上下文释放而死锁。APIalloc_workqueue()alloc_workqueue()用于分配一个工作队列它是当前唯一推荐的创建接口原来的create_*workqueue()系列函数已被弃用并计划移除。函数原型为struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags, int max_active, ...);三个核心参数fmt名称工作队列的名字同时如果有 rescue 线程也会用作救援线程的名字flags控制工作项如何被分配执行资源、调度与执行max_active限制并发执行的工作项数量。从源码看alloc_workqueue()最终通过alloc_workqueue_noprof()→alloc_workqueue_va()→__alloc_workqueue()完成创建kernel/workqueue.c 第 6026-6054 行并在__alloc_workqueue()中为 wq 分配workqueue_attrs。在 cmwq 中wq 本身不再管理执行资源而是作为前向进展保证、flush冲刷与工作项属性的作用域domain。flags 详解下表汇总了 include/linux/workqueue.h第 372-421 行与文档中定义的全部用户可见标志标志位含义WQ_BH10BH 工作队列可视为 softirq 的便捷接口。总是 per-CPU所有 BH 工作项在投递 CPU 的 softirq 上下文中按投递顺序执行。所有 BH 工作队列必须使用 0max_active且WQ_HIGHPRI是唯一允许附加的标志。BH 工作项不能睡眠延迟投递、flush、取消等其他特性均支持WQ_UNBOUND11unbound wq 的工作项由不绑定任何特定 CPU的特殊 worker 池服务使 wq 表现为一个没有并发管理的简单执行上下文提供者。unbound worker 池会尽可能快地启动工作项执行。牺牲局部性但适合以下场景① 并发度需求剧烈波动用 bound wq 可能在不同 CPU 间产生大量闲置 worker② 长时 CPU 密集型负载更适合交给系统调度器管理WQ_FREEZABLE12可冻结工作队列参与系统挂起suspend的冻结阶段工作项被排空且在新的解冻thaw之前不会有新工作项启动执行WQ_MEM_RECLAIM13所有可能用于内存回收路径的 wq 必须设置此标志。设置了该标志的 wq 保证无论内存压力多大至少有一个执行上下文rescue worker可用WQ_HIGHPRI14highpri wq 的工作项被投递到目标 CPU 的 highpri worker 池该池由提升过 nice 值nice 级别更低的 worker 线程服务。普通与 highpri 池互不交互各自维护独立 worker 池并独立实施并发管理WQ_CPU_INTENSIVE15CPU 密集型 wq 的工作项不计入并发度可运行的 CPU 密集型工作项不会阻止同一 worker 池中其他工作项启动执行。适合预期会长时间占用 CPU 的 bound 工作项让其执行交由系统调度器调节。注意虽然不计入并发度其启动仍受并发管理约束可运行的非 CPU 密集型工作项可能延迟 CPU 密集型工作项的执行。该标志对 unbound wq 无意义WQ_SYSFS16使工作队列在 sysfs 中可见见下文章节WQ_PERCPU18投递到 per-cpu wq 的工作项绑定到特定 CPU。当 CPU 局部性重要时这是正确选择是WQ_UNBOUND的互补标志其中WQ_BH、WQ_PERCPU、WQ_UNBOUND、WQ_FREEZABLE、WQ_MEM_RECLAIM、WQ_HIGHPRI、WQ_CPU_INTENSIVE、WQ_SYSFS均在 include/linux/workqueue.h 第 372-421 行有对应位定义与注释另外内部标志__WQ_ORDERED 1 17用于标记有序工作队列。max_active并发执行上限max_active决定一个 wq 的工作项每 CPU 可获得的最大执行上下文数。例如max_active为 16 时该 wq 每 CPU 最多同时有 16 个工作项在执行。注意即使对 unbound 工作队列这也是一个 per-CPU 属性。上限 2048源码中WQ_MAX_ACTIVE 2048include/linux/workqueue.h 第 419 行__alloc_workqueue()中会用clamp_val(max_active, 1, WQ_MAX_ACTIVE)把传入值钳制到[1, 2048]区间kernel/workqueue.c 第 5773-5777 行默认值 1024当指定 0 时使用默认值源码中定义为WQ_DFL_ACTIVE WQ_MAX_ACTIVE / 2即 1024。这些取值足够高通常不会成为瓶颈同时又能防止失控runaway场景。一个 wq 的活跃工作项数量通常由 wq 的使用者自己调节——更确切地说由使用者同时投递多少个工作项决定。除非有明确的节流需求文档推荐直接指定 0 使用默认值。严格顺序执行的正确姿势某些用户依赖严格的执行顺序要求任意时刻只有一个工作项在途且按投递顺序处理。过去用max_active1加WQ_UNBOUND实现这一行为但现在不再成立应改用alloc_ordered_workqueue()。从源码看alloc_ordered_workqueue()正是展开为alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ...)——即强制 unbound、内部有序标志并把max_active固定为 1include/linux/workqueue.h 第 585-598 行。内核对 ordered wq 有专门的 attrs 处理路径apply_workqueue_attrs_locked(wq, ordered_wq_attrs[highpri])kernel/workqueue.c 第 5731 行。示例执行场景cmwq 的行为演示文档用一组精心构造的时序实验说明 cmwq 在不同配置下的行为差异。场景设定工作项 w0、w1、w2 被投递到同一 CPU 上的 bound wq q0w0 烧 CPU 5ms、睡眠 10ms、再烧 CPU 5ms 后结束w1、w2 各烧 CPU 5ms、睡眠 10ms。忽略其他任务与处理开销假设简单 FIFO 调度原始 wq 的行为串行执行总耗时 50msTIME 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 finishescmwq 且max_active 3并发管理让睡眠的 w0 让出执行上下文总耗时 25msTIME 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 finishescmwq 且max_active 2并发度被限制为 2w2 需等 w0 完成才能启动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 finishesw1、w2 被投递到设置了WQ_CPU_INTENSIVE的另一个 wq q1CPU 密集型工作项不计入并发度因此 w1、w2 可以同时启动执行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对比可清晰看出cmwq 的并发管理在工作项睡眠这种执行上下文让渡场景中能显著压缩总耗时而max_active与WQ_CPU_INTENSIVE则是调节并发度的两个关键旋钮。使用指南Guidelines不要忘记WQ_MEM_RECLAIM只要 wq 可能处理用于内存回收期间的工作项就必须设置。每个带WQ_MEM_RECLAIM的 wq 都保留一个专属执行上下文。如果多个用于内存回收的工作项之间存在依赖关系它们应被投递到各自独立且都带WQ_MEM_RECLAIM的 wq 中避免互相等待。除非严格要求顺序无需使用 ST单线程wq。max_active推荐用 0除非有特定需求绝大多数用例的并发水平远低于默认上限 1024。优先复用系统工作队列wq 是前向进展保证WQ_MEM_RECLAIM、flush 与工作项属性的作用域。不涉及内存回收、不需要作为一组工作项整体 flush、也不需要特殊属性的工作项可以直接使用系统 wq——专用 wq 与系统 wq 在执行特性上没有区别。但注意如果某生产者在某些情况下可能产生超过max_active的在途工作项务必对生产者做压力测试它可能占满系统 wq 并导致死锁此时应使用自己的专用工作队列。优先使用 bound wq除非工作项预期消耗大量 CPU 周期使用 bound wq 通常更有利因为 wq 操作与工作项执行的局部性更好。从源码可见内核为通用场景预先创建了一组系统工作队列kernel/workqueue.c 第 8224-8240 行eventssystem_wqper-cpu、events_highpri、events_long、events_unboundsystem_unbound_wq/system_dfl_wq、events_freezable、events_power_efficient、events_freezable_pwr_efficient、events_bh、events_bh_highpri以及events_dfl_long等驱动可以直接使用这些现成的执行域。亲和性作用域Affinity Scopesunbound 工作队列会根据其亲和性作用域对 CPU 分组以改善缓存局部性。例如使用默认作用域cache_shard时CPU 会被分成若干 sub-LLC末级缓存之下分片某个 CPU 上投递的工作项会被分配给同一分片内某个 CPU 上的 worker。worker 启动后是否允许移出作用域取决于该作用域的affinity_strict设置。workqueue 支持以下亲和性作用域作用域语义default使用模块参数workqueue.default_affinity_scope指定的作用域该参数始终被设置为下列之一cpuCPU 不分组。在某 CPU 投递的工作项由同一 CPU 上的 worker 处理使 unbound wq 表现得像无并发管理的 per-cpu wqsmt按 SMT 边界分组通常每个物理核的逻辑线程被分到一组cache按缓存边界分组具体使用哪级缓存由架构代码决定多数情况用 L3cache_shardCPU 被分为最多wq_cache_shard_size个核的 sub-LLC 分片默认 8可用workqueue.cache_shard_size启动参数调节分片总是在核SMT 组边界上切分。这是默认亲和性作用域numa按 NUMA 边界分组system所有 CPU 在同一组workqueue 不努力在靠近投递 CPU 的地方处理工作项源码佐证cache_shard_size在 kernel/workqueue.c 第 446-447 行定义为unsigned int wq_cache_shard_size 8并注册为模块参数cache_shard_size权限 0444分片数量的计算在wq_calc_node_cpumask附近的布局逻辑中按DIV_ROUND_CLOSEST(nr_cores, wq_cache_shard_size)估算第 8467 行且参数被强制要求大于 0否则回退为 1第 8559-8561 行。default_affinity_scope则是通过module_param_cb(default_affinity_scope, ...)注册的可写模块参数第 7321 行。默认亲和性作用域可通过模块参数workqueue.default_affinity_scope修改单个工作队列的作用域可通过apply_workqueue_attrs()修改。sysfs 接口如果设置了WQ_SYSFS工作队列会在/sys/devices/virtual/workqueue/WQ_NAME/目录下提供如下亲和性相关接口文件affinity_scope读取显示当前亲和性作用域写入可修改。当当前作用域是default时读取还会以括号显示实际生效的作用域例如default (cache)affinity_strict默认 0表示亲和性作用域非严格。工作项开始执行时workqueue 会尽力保证 worker 在其作用域内称为repatriation遣返一旦启动调度器可以自由地把 worker 移动到系统中任何 CPU从而在享受作用域局部性的同时必要时仍能利用其他 CPU。若设为 1则保证该作用域的所有 worker 始终留在作用域内——这在跨越亲和性作用域有额外影响如功耗、工作负载隔离时有用严格 NUMA 作用域还可以用来复现旧内核的 workqueue 行为。亲和性作用域与性能dm-crypt 实测理想情况下unbound wq 无需调优就能对绝大多数用例最优。但当前内核中局部性与利用率之间存在显著权衡当工作队列被重度使用时需要显式配置。局部性越高相同 CPU 周期完成的工作越多效率更高但局部性过高如果投递者没有把工作项充分分散到各作用域可能导致整体系统利用率下降。文档以下面这套 dm-crypt 实测12 核 24 线程、分布在四个 L3 缓存上AMD Ryzen 9 3900x关闭 CPU boost 保证一致性/dev/dm-0是 NVME SSD 上的 dm-crypt 设备清楚展示了这一权衡。测试关注kcryptd工作队列在不同亲和性作用域下的表现带宽单位为 MiBps、CPU 利用率单位为百分比每组测 5 次取均值场景 1投递者充足、工作分散全机fio --numjobs24 --iodepth64--verifysha512使每次生成并回读内容让投递者与kcryptd之间的执行局部性变得重要$ fio --filename/dev/dm-0 --direct1 --rwrandrw --bs32k --ioenginelibaio \ --iodepth64 --runtime60 --numjobs24 --time_based --group_reporting \ --nameiops-test-job --verifysha512AffinityBandwidth (MiBps)CPU util (%)system1159.40 ±1.3499.31 ±0.02cache1166.40 ±0.8999.34 ±0.01cache (strict)1166.00 ±0.7199.35 ±0.01投递者充足且遍布全系统时cache严格或不严格都没有劣势三种配置都能打满整机但 cache 亲和配置凭借更好的局部性还领先约 0.6%。场景 2投递者较少、但工作量足以饱和仅将--numjobs改为 8$ fio --filename/dev/dm-0 --direct1 --rwrandrw --bs32k \ --ioenginelibaio --iodepth64 --runtime60 --numjobs8 \ --time_based --group_reporting --nameiops-test-job --verifysha512AffinityBandwidth (MiBps)CPU util (%)system1155.40 ±0.8997.41 ±0.05cache1154.40 ±1.1496.15 ±0.09cache (strict)1112.00 ±4.6493.26 ±0.35工作量仍足以压满系统system与cache都接近饱和cache消耗更少 CPU 却因效率更高达到与system相同的带宽8 个投递者在四个 L3 作用域间移动时cache (strict)仍能基本打满但失去工作守恒work-conservation的代价开始显现——带宽损失约 3.7%。场景 3投递者更少、工作量不足以饱和--numjobs4$ fio --filename/dev/dm-0 --direct1 --rwrandrw --bs32k \ --ioenginelibaio --iodepth64 --runtime60 --numjobs4 \ --time_based --group_reporting --nameiops-test-job --verifysha512AffinityBandwidth (MiBps)CPU util (%)system993.60 ±1.8275.49 ±0.06cache973.40 ±1.5274.90 ±0.07cache (strict)828.20 ±4.4966.84 ±0.29此时局部性与利用率之间的权衡变得非常清晰cache相比system带宽损失约 2%而cache (strict)损失高达约 20%。结论与建议cache作用域相对system的效率优势虽然一致且可察觉但幅度较小其影响取决于各作用域之间的距离在拓扑更复杂的处理器上可能更显著。虽然某些场景下损失工作守恒有代价但远好于cache (strict)而且最大化工作队列利用率本来就不是常见需求因此cache是 unbound 池的默认亲和性作用域对应源码中cache_shard为默认 scope 的设计。可能消耗大量 CPU 的 workqueue 使用方建议用apply_workqueue_attrs()和/或开启WQ_SYSFS进行显式配置。严格cpu亲和性作用域的 unbound wq 行为等同于WQ_CPU_INTENSIVE的 per-cpu wq但前者没有真正的优势且 unbound wq 提供了多得多的灵活性。亲和性作用域在 Linux v6.5 引入要模拟旧内核行为使用严格numa亲和性作用域。非严格亲和性作用域中工作守恒的损失很可能源于调度器理论上内核在大多数情况下本可以既做对事又保持工作守恒因此未来调度器的改进可能让这些调优参数变得不再必要。检查配置wq_dump.py使用 tools/workqueue/wq_dump.py 可以检查 unbound CPU 亲和性配置、worker 池以及工作队列到池的映射关系$ tools/workqueue/wq_dump.py Affinity Scopes wq_unbound_cpumask0000000f CPU nr_pods 4 pod_cpus [0]00000001 [1]00000002 [2]00000004 [3]00000008 pod_node [0]0 [0]0 [1]1 [2]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 [0]0 [1]1 [2]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] ref42 nice 0 idle/workers 6/ 6 cpus0000000f pool[09] ref28 nice 0 idle/workers 3/ 3 cpus00000003 pool[10] ref28 nice 0 idle/workers 17/ 17 cpus0000000c pool[11] ref 1 nice-20 idle/workers 1/ 1 cpus0000000f pool[12] ref 2 nice-20 idle/workers 1/ 1 cpus00000003 pool[13] ref 2 nice-20 idle/workers 1/ 1 cpus0000000c 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 ...从这份输出可以直观读出CPU/SMT作用域各 4 个 pod、CACHE/NUMA各 2 个 pod、SYSTEM1 个 pod 的分组关系worker 池一节中cpu表示绑定池含 nice0 普通池与 nice-20 高优先级池cpus表示 unbound 池的 cpumask最后的映射表则展示了events系列系统 wq 如何按 CPU 或按默认池dfl列落到具体 pool 上其中netns这类 ordered wq 被映射到同一个默认 unbound 池pool 8。更详细的说明参见该命令的--help输出。监控wq_monitor.py使用 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 - - ...列含义total为该 wq 累计处理的工作项总数infl为当前在途inflight工作项数CPUtime为累计 CPU 时间CPUhog为 CPU 占用过久的工作项计数CMW/RPR为并发管理/遣返repatriation相关事件计数mayday为请求 rescue worker 的次数rescued为被 rescue worker 接管的工作项数。各列的具体口径参见脚本帮助信息。mayday/rescued出现非零值通常意味着内存压力下工作队列依赖了救援机制。调试技巧由于工作函数由通用的 worker 线程执行定位行为异常的工作队列用户需要一些技巧。worker 线程在进程列表中形如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 上的 worker 线程。如果 kworker 占用过多 CPU发疯通常有两类问题某物在快速连续地被调度例如有代码在忙循环投递工作项单个工作项消耗大量 CPU 周期。针对第 1 类用 ftrace 跟踪工作项投递事件。workqueue 子系统提供workqueue_queue_work等 tracepoint定义于 include/trace/events/workqueue.h$ echo workqueue:workqueue_queue_work /sys/kernel/tracing/set_event $ cat /sys/kernel/tracing/trace_pipe out.txt (wait a few secs) ^C如果确实存在忙循环投递输出会被其主导根据 trace 中的工作项函数即可锁定元凶。针对第 2 类直接查看肇事 worker 线程的内核栈工作项函数会清晰地出现在栈回溯中$ cat /proc/THE_OFFENDING_KWORKER/stack非重入保证Non-reentrance Conditionsworkqueue 保证只要工作项在入队之后满足以下条件就不会发生重入工作函数没有被修改没有人把该工作项投递到另一个工作队列该工作项没有被重新初始化reinitiate。换言之满足上述条件时系统范围内任意时刻最多只有一个 worker 在执行该工作项。需要注意在工作函数内部把工作项重新投递回同一个队列并不破坏这些条件因此这种做法是安全的而破坏上述条件时在工作函数内必须格外小心。深入阅读本文档对应的内核源码内联文档kernel-doc是权威的一手参考include/linux/workqueue.h工作项结构、INIT_WORK()系列初始化宏、queue_work()系列投递接口、全部WQ_*标志位定义第 372-421 行与alloc_workqueue()/alloc_ordered_workqueue()的宏展开与注释kernel/workqueue.cstruct worker_pool第 195 行、struct pool_workqueue第 271 行、并发管理的核心计数器nr_running、alloc_workqueue_noprof()第 6041 行、apply_workqueue_attrs()第 5610 行、系统工作队列的创建第 8224-8240 行以及default_affinity_scope/cache_shard_size模块参数的注册。配套的脚本 tools/workqueue/wq_dump.py 与 tools/workqueue/wq_monitor.py 是日常运维、调优与故障排查最直接的上手工具。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考