1. 什么是 kworker?它不是“病毒”,也不是“后台程序”,而是 Linux 内核的“隐形搬运工”
你第一次在top或htop里看到一串kworker/u8:2+events_power_efficient这样的进程名时,大概率会愣一下:这既不像nginx那样有明确服务指向,也不像java那样能一眼看出是哪个应用在跑,更不像chrome那样有图形界面可追溯。它不占 CPU、不写磁盘、不建网络连接,却常年稳坐进程列表前排——甚至比你的 shell 还“长寿”。它到底是什么?为什么系统重启后它还在?为什么有时候它突然飙到 90% CPU?为什么kill -9它根本没用?
kworker 是 Linux 内核工作队列(workqueue)机制的执行实体,本质是内核线程(kernel thread),不是用户空间进程,更不是恶意软件。它没有可执行文件路径(ls -l /proc/PID/exe会显示not a symbolic link),不能被kill终止,也不会出现在/etc/init.d/或systemd的服务单元中。它存在的唯一目的,就是把内核中那些不能在中断上下文(interrupt context)里完成的、需要稍作延时或调度的任务,安全地“搬”到进程上下文(process context)中去执行。
举个生活化类比:想象一个急诊室。当救护车送来病人(硬件中断触发,比如网卡收到一个数据包、硬盘完成一次读写),医生必须立刻响应(这就是中断处理),但不能当场做手术(中断上下文禁止睡眠、禁止调用大部分内存分配函数、不能长时间占用 CPU)。于是医生快速登记、测血压、贴标签,然后把病人交给护士站(workqueue),由护士按优先级、按科室、按空闲资源安排手术室和主刀医生(kworker 线程)——这个“安排手术”的过程,就是 kworker 在干的事。
所以,当你看到kworker/0:1H,其中0表示它绑定在 CPU 0 上运行,1是该 CPU 上第 1 个工作队列实例编号,H表示 high-priority(高优先级队列,用于紧急但非实时任务)。而kworker/u8:2+events_power_efficient中的u8指的是 unbound workqueue(不绑定特定 CPU,可跨 CPU 调度),events_power_efficient则是该工作队列注册时的名字,暗示它可能与电源管理、设备休眠唤醒等节能事件相关。
这解释了为什么ps aux | grep kworker总能看到十几个甚至几十个同类进程:它们不是“多个程序”,而是内核为不同场景、不同优先级、不同 CPU 绑定策略预设的“搬运工班组”。它们共享同一套内核代码逻辑,只是参数和上下文不同。你无法apt install kworker,也无法systemctl restart kworker——它随内核启动而生,随内核卸载模块而动态增减,是操作系统最底层的“呼吸节奏”本身。
对运维、嵌入式开发、性能调优工程师而言,理解 kworker 不是为了“干掉它”,而是为了读懂系统真正的负载来源。当iostat -x 1显示%util接近 100% 但top里找不到高 IO 进程时,很可能是 kworker 正在密集处理存储驱动的 completion 回调;当vmstat 1中si/so(swap in/out)持续为 0,但bi/bo(block in/out)却异常高,那背后驱动块设备队列的,十有八九是某个 kworker 实例。它不喧哗,但系统每一处隐性瓶颈,都可能留下它的指纹。
2. kworker 异常飙升的四大根源与精准定位方法
kworker CPU 使用率突增,从来不是“随机故障”,而是内核某处任务积压的明确信号。它不撒谎,但需要你用对工具、问对问题。我见过太多人第一反应是killall kworker(无效)、systemctl stop systemd-journald(误伤)、甚至重装系统(过度)。真正有效的排查,必须回归到“它在替谁干活?”这个核心。
2.1 根源一:硬件驱动缺陷或固件 Bug(最常见,也最难察觉)
这是生产环境 kworker 飙升的头号原因。尤其在使用较新硬件(如 NVMe SSD、Intel AX2xx WiFi、AMD Renoir/Raphael 核显)或较老驱动(如某些 RAID 卡、USB 3.x 控制器)时,驱动在处理中断或完成回调时陷入死循环、重复提交任务、或未正确清理 pending work,导致工作队列无限堆积。
实操定位法:perf + stack trace
# 1. 先确认是哪个 kworker 在忙(假设 PID 是 1234) sudo perf record -e sched:sched_switch -p 1234 -- sleep 10 sudo perf script | grep "kworker.*switch" | head -20 # 2. 更直接:抓取其调用栈(需 kernel-debuginfo 包) sudo perf record -g -p 1234 -- sleep 5 sudo perf report -g --no-children | head -50重点观察输出中反复出现的函数名:
nvme_irq_handler→ NVMe 驱动问题ahci_port_intr/ata_qc_complete→ SATA/AHCI 驱动异常usb_submit_urb/usb_hcd_giveback_urb→ USB 设备或 hub 故障drm_atomic_helper_commit_hw_done→ GPU 驱动(尤其是 Intel i915 或 AMDGPU)在页面翻转时卡住
提示:若
perf report显示大量__schedule、pick_next_task_fair,说明 kworker 本身没卡,而是被其他高优先级任务频繁抢占,需查sched_latency_ns和sched_min_granularity_ns参数;若显示flush_work、process_one_work循环调用,则基本锁定为驱动层 work 未被正确 consume。
2.2 根源二:文件系统元数据操作风暴(ext4/xfs 常见)
当大量小文件创建、删除、属性修改(如touch、chmod、chown)集中发生时,ext4 的 journal 提交、xfs 的 log buffer 刷盘、以及 inode 分配/回收,都会生成大量 work item。尤其在data=ordered模式下,每次 write() 后需等待 journal commit 完成,若 journal 区域慢(如放在机械盘上),kworker 就会排队等待。
验证手段:iostat + mount 选项分析
# 观察是否伴随高 %util 和高 r/s w/s iostat -x 1 | grep -A1 "sda\|nvme0n1" # 查看挂载选项(重点关注 data=, barrier=, journal=) findmnt -D / # 或 findmnt -D /home # 检查 ext4 journal 状态(需 root) dumpe2fs -h /dev/sda1 | grep -i "journal"典型症状:iostat显示r/s极低(<10),但w/s高达数千,await> 50ms,%util100%,同时vmstat中bi(block in)值巨大。此时kworker/u*:2+events类进程 CPU 占用飙升,正是在刷 journal 或处理 delayed allocation。
2.3 根源三:内核模块热插拔/卸载残留(尤其 USB、PCIe 设备)
当 USB 设备频繁插拔、PCIe 设备(如 GPU、FPGA)热升级、或内核模块(如nvidia,vfio-pci)被强制rmmod时,若驱动未正确清理其注册的 workqueue,会导致已提交但未执行的 work item 永久滞留,或触发cancel_work_sync()的自旋等待,最终表现为 kworker 占满单核。
诊断命令:
# 查看当前所有 workqueue 状态(需 debugfs 挂载) mount | grep debugfs || sudo mount -t debugfs none /sys/kernel/debug cat /sys/kernel/debug/workqueue/*/stats | grep -E "(name|pending|active|delayed)" | head -30 # 检查最近模块加载/卸载日志 dmesg -T | grep -i "usb\|pci\|module\|remove\|insert" | tail -20关键指标:pending数值持续 > 0 且不下降,active长期为 0,delayed非零——说明 work item 已入队但无人消费,极可能是模块卸载不干净。
2.4 根源四:电源管理与设备休眠唤醒失序(嵌入式/笔记本高频)
ACPI、Suspend/Resume、Runtime PM(运行时电源管理)相关 workqueue(如events_power_efficient)在设备状态切换时承担大量协调任务。若 BIOS 固件对 _OSC(Operating System Capabilities)支持不全、或内核acpi_enforce_resources=lax设置不当,会导致设备唤醒后未能正确恢复 DMA、中断路由错误,进而引发 work 重试风暴。
排查要点:
# 检查 ACPI 状态与错误 dmesg | grep -i "acpi\|firmware\|bios" cat /sys/firmware/acpi/interrupts/* | grep -v "edge\|level" | sort -nk2 # 监控 suspend/resume 时间点(对比 kworker 飙升时刻) journalctl -b | grep -i "suspend\|resume\|PM:" | tail -10典型场景:合盖唤醒后,WiFi 断连、触摸板失灵,同时kworker/u*:3+events_power_efficientCPU 占用 80%+,dmesg中伴随ACPI Error: Method parse/execution failed或ACPI: EC: event blocked。
3. 从 perf 到 vmstat:一套组合拳定位 kworker 真凶
单靠top看 CPU 占用,就像只看汽车仪表盘的转速表,却不知道是发动机爆缸还是变速箱打滑。要真正揪出 kworker 背后的“雇主”,必须建立一套跨层级的观测链:从用户态 I/O 请求,到内核块层调度,再到驱动中断处理,最后落到 workqueue 执行。这套流程,我称之为“三层穿透法”。
3.1 第一层:用户态行为锚定(iostat + pidstat)
目标:确认高负载是否源于真实业务压力,而非内核自身异常。
# 同时监控磁盘与进程 I/O(-r/-w 显示每秒读写字节数,-d 显示设备名) iostat -x -d 1 5 | grep -E "(sda|nvme|Device|avg-cpu)" # 找出真实发起 I/O 的进程(注意:-d 选项显示设备,-r/-w 显示字节) pidstat -d 1 5 | sort -k6nr | head -10 # 按 read B/s 排序 pidstat -d 1 5 | sort -k7nr | head -10 # 按 write B/s 排序关键解读:
- 若
iostat显示r/s和w/s极高(>5000),且pidstat明确指向mysqld、postgres、rsync等进程,则 kworker 高 CPU 是正常负载传导,无需干预。 - 若
iostatr/s/w/s< 100,但%util100%、await> 100ms,且pidstat无显著 I/O 进程,则问题必在内核层(驱动或文件系统)。 - 特别注意
svctm(服务时间)与await(平均等待时间)差值:若await - svctm > 50ms,说明请求在队列中等待过久,根源在 block layer 或 driver queue depth 设置不当。
3.2 第二层:内核块层与中断分析(vmstat + /proc/interrupts)
目标:区分是“请求太多”还是“处理太慢”。
# 全局内存与 I/O 压力快照 vmstat 1 5 | grep -v "procs\|---" # 实时查看各 CPU 中断分布(重点关注 IO-APIC、MSI、PCIe) watch -n1 'cat /proc/interrupts | grep -E "(IO-APIC|MSI|PCIe|nvme|ahci|usb)" | head -15' # 检查 page cache 与 buffer 压力(bi/bo 高说明脏页回写压力大) vmstat 1 5 | awk 'NR==3 {print "bi:", $10, "bo:", $11, "in:", $12, "cs:", $13}'现象与归因:
vmstat中bi(blocks in)持续 > 1000,bo(blocks out)接近 0:说明大量脏页待刷,kworker 正在执行writebackwork,根源可能是vm.dirty_ratio设置过高或pdflush(旧内核)/writeback线程调度延迟。/proc/interrupts中某 CPU 的nvme0n1中断数远高于其他 CPU(如 5000 vs 50),且该 CPU 上 kworker 占用 100%:说明中断未均衡,需启用irqbalance或手动绑定echo 1 > /proc/irq/XX/smp_affinity_list。cs(context switch)值异常高(> 100000),in(interrupts)同步升高:表明中断过于频繁,驱动可能未启用 MSI-X 或未正确聚合中断。
3.3 第三层:内核工作队列深度剖析(perf + debugfs)
目标:直达 kworker 执行的具体函数,锁定驱动或子系统。
# 1. 获取 kworker PID(过滤掉 idle 和 migration) pgrep -f "kworker" | xargs -I{} ps -o pid,tid,comm,psr -p {} | grep -v "migration\|ksoftirqd" # 2. 对目标 PID 进行火焰图采样(需安装 perf 和 flamegraph) sudo perf record -g -p $(pgrep -f "kworker/u.*events_power_efficient" | head -1) -- sleep 10 sudo perf script | ~/FlameGraph/stackcollapse-perf.pl | ~/FlameGraph/flamegraph.pl > kworker-flame.svg # 3. 查看其绑定 workqueue 的 pending work 数量 WORKER_PID=$(pgrep -f "kworker/u.*events_power_efficient" | head -1) QUEUE_NAME=$(cat /proc/$WORKER_PID/stack 2>/dev/null | grep "workqueue" | head -1 | sed 's/.*\///; s/\].*//') echo "Pending work for $QUEUE_NAME:" cat /sys/kernel/debug/workqueue/$QUEUE_NAME/stats | grep pending火焰图解读技巧:
- 顶部宽大的函数框(如
nvme_queue_rq→nvme_submit_cmd→nvme_pci_map_data)说明问题在 NVMe 命令提交路径。 - 大量
flush_work→process_one_work→delayed_work_timer_fn循环,表明 work 被反复 delay,驱动可能在queue_delayed_work()中传入了过短的 delay(如msecs_to_jiffies(1))。 - 出现
rcu_read_lock/rcu_read_unlock占比高,说明 RCU callback 积压,需检查rcutree.kthreads参数或是否存在长时 RCU 临界区。
注意:
perf record -g采样需内核开启CONFIG_PERF_EVENTS=y且debuginfo可用,否则函数名将显示为[unknown]。生产环境若无 debuginfo,可用perf record -e irq:irq_handler_entry,irq:irq_handler_exit抓取中断进出点,再结合/proc/interrupts定位源头设备。
4. 实战案例:一块 NVMe SSD 引发的 kworker 飙升与根治方案
去年帮一家做视频转码的客户处理过一个典型 case:一台搭载 Intel Optane 905P 的服务器,在批量转码任务启动后 3 分钟,kworker/0:2HCPU 占用稳定在 95%,iostat显示nvme0n1%util100% 但r/s仅 120,await高达 250ms。pidstat显示ffmpeg进程 I/O 正常,dmesg无报错。表面看是 I/O 瓶颈,但细查发现ffmpeg读取速率仅 80MB/s,远低于 Optane 905P 2.5GB/s 的标称带宽。
4.1 现场诊断步骤还原
Step 1:排除用户态干扰
# 确认无其他进程争抢 pidstat -r -u -d 1 3 | sort -k8nr | head -5 # 按 CPU% 排序 # 输出:只有 kworker/0:2H 和 ffmpeg,且 ffmpeg CPU < 30%Step 2:聚焦 NVMe 设备
# 查看 nvme 设备队列深度与状态 sudo nvme smart-log /dev/nvme0n1 | grep -E "(avail_spare|media_errors|num_err_log_entries)" sudo cat /sys/block/nvme0n1/queue/nr_requests # 默认 128 sudo cat /sys/block/nvme0n1/queue/rq_affinity # 默认 1(绑定 CPU)发现nr_requests=128,但rq_affinity=1,意味着所有 I/O 请求都被强制路由到 CPU 0,而kworker/0:2H正在 CPU 0 上疯狂处理 completion。
Step 3:perf 抓栈确认
sudo perf record -g -p $(pgrep -f "kworker/0:2H") -- sleep 5 sudo perf report -g --no-children | head -20输出关键帧:
nvme_irq_handler nvme_process_cq nvme_complete_rq blk_mq_complete_request __blk_mq_complete_request blk_mq_sched_restart blk_mq_run_hw_queue __blk_mq_delay_run_hw_queue queue_delayed_work_on清晰指向:NVMe 中断处理完成后,调用blk_mq_complete_request,进而触发blk_mq_run_hw_queue,但因rq_affinity=1导致所有run_hw_queue都被调度到 CPU 0,而 CPU 0 上的 kworker 无法及时消费,形成 backlog。
4.2 根治方案与效果验证
方案一:调整队列亲和性(立即生效)
# 允许 I/O 请求在所有 CPU 间均衡(需内核 >= 5.0) echo 2 | sudo tee /sys/block/nvme0n1/queue/rq_affinity # 或指定多 CPU(如 0-3) echo "0-3" | sudo tee /sys/block/nvme0n1/queue/rq_affinityrq_affinity=2表示启用 SMP-aware 调度,内核会根据当前 CPU 负载自动选择最优 CPU 处理 completion。
方案二:增大硬件队列深度(需重启)
# 编辑 /etc/default/grub,添加内核参数 GRUB_CMDLINE_LINUX_DEFAULT="... nvme.default_ps_max_latency_us=0" # 更新 grub 并重启 sudo update-grub && sudo rebootdefault_ps_max_latency_us=0禁用 NVMe 电源状态转换,确保始终运行在高性能模式,避免 PS 状态切换引入延迟。
方案三:升级固件与驱动(长期保障)
- 下载 Intel 官方 Optane 905P 固件(版本 80102001),通过
nvme-cli刷写:sudo nvme fw-download /path/to/fw.bin -f /dev/nvme0n1 sudo nvme fw-commit /dev/nvme0n1 -s 1 - 升级内核至 5.10+,启用
CONFIG_NVME_MULTIPATH=y和CONFIG_NVME_HWMON=y。
效果验证:
# 重启后 iostat -x 1 | grep nvme0n1 # 输出:r/s 1500+, await < 5ms, %util 98%(健康饱和) top -p $(pgrep -f "kworker") | grep "kworker" # 输出:所有 kworker CPU 占用 < 15%,分布均匀转码任务吞吐量从 12 路提升至 38 路,kworker 再未成为瓶颈。
4.3 经验总结:三个必须养成的习惯
- 永远先看
/sys/block/XXX/queue/下的参数:nr_requests、rq_affinity、scheduler(noop/mq-deadline)是 I/O 性能的基石,比调优sysctl更直接有效。 dmesg必须在问题发生后 1 分钟内捕获:很多驱动错误(如nvme 0000:01:00.0: controller is down)只在首次报错时记录,后续重试不再打印。- 不要迷信
top的 CPU%:kworker 的 95% CPU 很可能只是“等待 I/O 完成”的 busy-wait,实际计算量极少。真正要看perf的cycles事件占比,若< 10%,说明它 90% 时间在futex_wait或schedule_timeout,根源在上游阻塞。
5. 常见问题速查表与避坑指南
| 问题现象 | 可能原因 | 快速验证命令 | 解决方案 |
|---|---|---|---|
kworker/u*:0持续 100% CPU,iostat无 I/O | 内核模块卸载残留(如nvidia-uvm) | cat /sys/kernel/debug/workqueue/*/stats | grep pending | sudo modprobe -r nvidia-uvm && sudo modprobe nvidia-uvm(重新加载) |
kworker/0:1H占用高,/proc/interrupts中IO-APIC-fasteoi某 CPU 中断数暴增 | 中断未均衡,网卡 RSS 未启用 | ethtool -l eth0(查看通道数),cat /proc/irq/*/smp_affinity_list | sudo ethtool -L eth0 combined 8+sudo irqbalance --oneshot |
kworker/u*:3+events_power_efficient高 CPU,合盖唤醒后必现 | BIOS ACPI _OSC 支持不全 | dmesg | grep -i "osc" | BIOS 中关闭Fast Boot,或内核启动参数加acpi_enforce_resources=lax |
kworker/1:0高 CPU,vmstatcs> 200000 | RCU callback 积压 | cat /proc/sys/kernel/rcu_normal | echo 1 | sudo tee /proc/sys/kernel/rcu_normal(临时缓解),长期需升级内核 |
kworker/u*:2+events高 CPU,iostatr/s极低但await> 200ms | 文件系统 journal 刷盘慢(ext4 journal 在慢盘) | sudo dumpe2fs -h /dev/sda1 | grep -i journal | 将 journal 移至 SSD:sudo tune2fs -j -J device=/dev/nvme0n1p1 /dev/sda1 |
5.1 三个绝对不能做的“伪操作”
- 不要
kill -9kworker:它不是用户进程,kill信号会被内核忽略,ps中 PID 会立即刷新为新值,徒增混乱。 - 不要盲目
echo 0 > /proc/sys/vm/swappiness:这只会减少 swap 使用,对 kworker 飙升毫无帮助,反而可能加剧内存压力导致 OOM。 - 不要
rmmod正在使用的驱动模块:如rmmod ahci会直接导致系统 panic,rmmod nvme会让所有 NVMe 设备离线,kworker 会因无法完成 pending work 而永久卡死。
5.2 一个被严重低估的调试利器:/proc/PID/stack
每个 kworker 进程的/proc/PID/stack文件,以纯文本形式记录其当前内核栈,无需perf即可快速窥探。例如:
sudo cat /proc/$(pgrep -f "kworker/u.*events_power_efficient")/stack输出类似:
[<ffffffffa1234567>] process_one_work+0x1a7/0x3a0 [<ffffffffa1234567>] worker_thread+0x4a/0x3c0 [<ffffffffa1234567>] kthread+0x113/0x150 [<ffffffffa1234567>] ret_from_fork+0x35/0x40 [<ffffffffa1234567>] nvme_pci_complete_rq+0x45/0x70 [<ffffffffa1234567>] nvme_complete_rq+0x2a/0x50 [<ffffffffa1234567>] nvme_irq_handler+0x1c2/0x2a0这比ps -T -p PID的线程列表直观百倍——它告诉你此刻 kworker 正在执行哪一行内核代码。我习惯把它加入日常巡检脚本:
#!/bin/bash for pid in $(pgrep -f "kworker"); do if [ $(cat /proc/$pid/stat | awk '{print $14}') -gt 1000000 ]; then # utime > 1s echo "=== kworker $pid stack ===" sudo cat /proc/$pid/stack 2>/dev/null | head -5 fi done5.3 最后一个建议:把 kworker 当作系统的“心电图”
它不报警,但每一次异常波动,都是内核某处血管的微小痉挛。与其等到它飙到 100% 再手忙脚乱,不如把它纳入常规监控:
- Zabbix/Prometheus 中采集
/proc/interrupts各 CPU 中断数、/sys/kernel/debug/workqueue/*/stats中pending值、/proc/stat中intr总中断数。 - 设置告警阈值:
pending > 100持续 5 分钟,或单 CPU 中断数 > 其他 CPU 均值 3 倍。 - 每次系统升级、驱动更新、BIOS 刷写后,必做
stress-ng --io 4 --timeout 60s压力测试,并用perf top -p $(pgrep kworker)观察火焰图基线。
我见过太多团队把 kworker 当作“背景噪音”忽略,直到某次磁盘故障导致 journal 积压,才在dmesg里翻出三个月前就存在的nvme 0000:01:00.0: completing cmd timed out报错。其实,它一直都在说话,只是我们没学会听。