kworker CPU占用高通常源于workqueue任务堆积或阻塞,需用perf record抓栈定位具体回调函数,检查驱动是否耗时过长、自旋等待或错误串行化。
![perf top 显示 [kworker] 高占比的 workqueue 长任务排查](https://img.php.cn/upload/article/001/242/473/176959855118527.png)
perf top 里看到 [kworker] 占比高,先确认是不是 workqueue 延迟问题
perf top 显示 [kworker/u16:3] 或类似名字的符号持续占 CPU 高比例(比如 >20%),不代表内核真在“忙计算”,更可能是某个 workqueue 任务长期得不到调度完成,或反复重入、堆积。这时候 perf top 只是反映了当前 CPU 正在执行 workqueue 的回调函数(如 process_one_work、worker_thread),但根源往往在上层驱动或子系统提交了耗时过长、阻塞、或自旋等待的 work。
用 perf record + stack trace 定位具体 work 回调函数
单纯 perf top 看不到调用链上下文,必须抓栈。运行以下命令捕获 10 秒内所有 kworker 相关的调用栈:
perf record -e 'sched:sched_switch' -g --call-graph dwarf -a sleep 10
然后过滤出 kworker 切换频繁、且栈顶含 work 相关函数的样本:
perf script | awk '/kworker.*process_one_work/ && /function_name|driver_name/ {print}' | head -20
重点关注栈中是否出现:
-
nvme_queue_rq(NVMe 驱动中 work 提交后卡住) -
usb_submit_urb或usb_anchor_urb(USB 子系统 work 阻塞在 URB 提交) -
drm_atomic_helper_commit_work(GPU DRM 提交原子状态时 work 挂起) - 你自己模块里的
queue_work后紧跟着的回调函数名(如mydrv_handle_event)
检查 workqueue 是否被意外串行化或绑核导致堆积
Linux 中如果某个 workqueue 被创建为 WQ_UNBOUND | WQ_HIGHPRI,但实际回调里调用了 mutex_lock 或 wait_event,就可能造成 worker 线程休眠、无法及时处理队列。更隐蔽的是:该 workqueue 被显式绑定到单个 CPU(alloc_workqueue("xxx", WQ_CPU_INTENSIVE, 1)),而该 CPU 正在被其他高优先级中断或 RT 任务长期霸占——此时 work 积压,perf top 就会持续显示那个 kworker。
查证方法:
- 看
/proc/sys/kernel/workqueue/下是否有异常队列(部分内核版本不暴露) - 用
cat /proc/kmsg | grep -i "workqueue"搜近期 warning,例如workqueue: WQ_MEM_RECLAIM is deprecated - 检查模块代码里是否对同一 work 反复调用
schedule_work()而未加锁或去重
验证是否由 softirq 或 RCU callback 溢出间接引发
某些场景下,[kworker] 高占比其实是假象:真正瓶颈是 softirq 处理太久(如网卡 NAPI poll 卡住),导致系统不断唤醒 kworker 来辅助清理(例如 net_rx_action 触发 __napi_schedule → queue_work_on)。这时 perf 栈里能看到 __do_softirq → napi_poll → queue_work → process_one_work 这条链。
排查建议:
- 运行
cat /proc/softirqs,对比NET_RX和NET_TX的计数增长速率 - 用
perf record -e 'irq:softirq_entry' -g --call-graph dwarf -a sleep 5看软中断热点 - 检查是否启用了
net.core.netdev_budget过小,导致 NAPI poll 循环次数不足、任务被迫下沉到 workqueue
workqueue 本身不慢,但它成了上游压力的“显示器”。真正要调的,往往是那个忘了 cond_resched() 的驱动回调,或者在 atomic 上下文中偷偷调了 msleep() 的人。








