调试定位CPU占用率高的问题
今天面试嵌入式Linux C++开发工程师的时候,面试官问了我一个这样的问题:
如何定位 Linux CPU 占用率高的问题?
我想了很久,想不出来。只能支支吾吾的从嘴里出来几句:一般通过 Git 提交记录进行代码回溯,看看最近有哪些改动导致的CPU升高。然后适时加入一些日志看打印是否频繁。
嗯,我也感觉这样回答很不好,后续也没戏了,是我太菜了吧。这里借此在网上查一下资料,记录一下。
首先,确认CPU占用高的现象。
-
全局定位进程:使用
top
、htop
查看系统整体CPU使用情况,快速定位到占用率高的进程(观察%CPU
列)。 -
区分用户态与内核态:通过
top
中的us
(用户态)和sy
(内核态)占比,初步判断是程序逻辑问题还是系统调用频繁。%Cpu(s): 30.3 us, 34.5 sy, 0.0 ni, 33.5 id, 0.5 wa, 0.0 hi, 1.2 si, 0.0 st
我们能在
top
输出看到如上信息。us
(user)- 用户态 CPU 时间:CPU 执行用户空间进程(应用程序代码)的时间占比。
- 示例分析:如果
us
过高,说明用户程序(如业务逻辑、计算密集型任务)占用了大量 CPU。
sy
(system)- 内核态 CPU 时间:CPU 执行内核空间代码(系统调用、中断处理、内核线程)的时间占比。
- 示例分析:
sy
高可能是频繁的系统调用(如文件 I/O、网络操作)或内核任务(如调度、内存管理)导致的。
然后定位到具体线程:在 top
中按 H
切换线程视图,或使用 ps -T -p <PID>
查看进程内各线程的CPU占用。记录高CPU占用的线程ID(TID)。
再分析线程调用栈。
gdb
附加进程:通过gdb -p <PID>
附加到进程,使用thread apply all bt
查看所有线程的调用栈,或切换到高负载线程后执行bt
查看堆栈。- 使用
perf
生成火焰图,看哪些函数被高频调用。
进行代码级定位。
结合 gdb
和 perf
的结果,定位到函数具体调用的地方。然后检查是否有常见问题:
- 死循环或忙等待:如未正确退出的循环、未使用条件变量或超时的忙等待。
- 算法复杂度:是否存在时间复杂度高的操作(如未优化的嵌套循环)。
- 锁竞争:通过
valgrind --tool=helgrind
检测竞争条件,或 perf lock 分析锁争用。 - I/O 或第三方库:频繁的同步I/O、低效的库函数(如正则表达式匹配、内存拷贝)。
复现与优化:想办法复现。然后结合性能分析结果,审查热点函数逻辑,优化算法或调整实现(如改用异步I/O、减少锁粒度)。
示例回答:当遇到CPU占用率高的问题时,我通常会分步骤定位。首先用 top
找到高CPU的进程,再通过 top -H
或 ps -T
定位到具体线程。接着用 perf record
采样生成火焰图,或者用 gdb
附加进程查看线程堆栈,找到热点函数。结合代码审查,检查是否有死循环、算法复杂度问题或锁竞争。例如,之前我遇到一个因循环中未正确等待条件变量导致的CPU空转,通过 perf
定位到忙等待的线程,最终通过添加条件变量超时修复。