跳到主要内容

调试定位CPU占用率高的问题

· 阅读需 5 分钟
阅读量: 101阅读人次: 102

今天面试嵌入式Linux C++开发工程师的时候,面试官问了我一个这样的问题:

如何定位 Linux CPU 占用率高的问题?

我想了很久,想不出来。只能支支吾吾的从嘴里出来几句:一般通过 Git 提交记录进行代码回溯,看看最近有哪些改动导致的CPU升高。然后适时加入一些日志看打印是否频繁。

嗯,我也感觉这样回答很不好,后续也没戏了,是我太菜了吧。这里借此在网上查一下资料,记录一下。

首先,确认CPU占用高的现象。

  • 全局定位进程:使用 tophtop 查看系统整体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 输出看到如上信息。

    1. us(user)
      • 用户态 CPU 时间:CPU 执行用户空间进程(应用程序代码)的时间占比。
      • 示例分析:如果 us 过高,说明用户程序(如业务逻辑、计算密集型任务)占用了大量 CPU。
    2. 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 生成火焰图,看哪些函数被高频调用。

进行代码级定位。

结合 gdbperf 的结果,定位到函数具体调用的地方。然后检查是否有常见问题:

  • 死循环或忙等待:如未正确退出的循环、未使用条件变量或超时的忙等待。
  • 算法复杂度:是否存在时间复杂度高的操作(如未优化的嵌套循环)。
  • 锁竞争:通过 valgrind --tool=helgrind 检测竞争条件,或 perf lock 分析锁争用。
  • I/O 或第三方库:频繁的同步I/O、低效的库函数(如正则表达式匹配、内存拷贝)。

复现与优化:想办法复现。然后结合性能分析结果,审查热点函数逻辑,优化算法或调整实现(如改用异步I/O、减少锁粒度)。

示例回答:当遇到CPU占用率高的问题时,我通常会分步骤定位。首先用 top 找到高CPU的进程,再通过 top -Hps -T 定位到具体线程。接着用 perf record 采样生成火焰图,或者用 gdb 附加进程查看线程堆栈,找到热点函数。结合代码审查,检查是否有死循环、算法复杂度问题或锁竞争。例如,之前我遇到一个因循环中未正确等待条件变量导致的CPU空转,通过 perf 定位到忙等待的线程,最终通过添加条件变量超时修复。