0. 简介

我们在之前介绍了使用Valgrind、perf、AddressSanitzer等工具来完成内存泄漏的检测,当然内存泄漏以外还有cpu的占用率变高这类问题。作者在这里提供几个方法来对C++程序中CPU程序占用率高问题排查。

1. pstack堆栈查看

pstack 命令可以监听日志,Linux 系统默认没有这个命令。所以我们需要安装pstack

sudo apt install pstack

在ubuntu下使用apt安装时,可能会出现无法使用的问题,解决方法是:直接在/usr/bin下新建一个名为pstack的脚本文件,并给sudo权限

#!/bin/sh

if test $# -ne 1; then
    echo "Usage: `basename $0 .sh` <process-id>" 1>&2
    exit 1
fi

if test ! -r /proc/$1; then
    echo "Process $1 not found." 1>&2
    exit 1
fi

# GDB doesn't allow "thread apply all bt" when the process isn't
# threaded; need to peek at the process to determine if that or the
# simpler "bt" should be used.

backtrace="bt"
if test -d /proc/$1/task ; then
    # Newer kernel; has a task/ directory.
    if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then
    backtrace="thread apply all bt"
    fi
elif test -f /proc/$1/maps ; then
    # Older kernel; go by it loading libpthread.
    if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then
    backtrace="thread apply all bt"
    fi
fi

GDB=${GDB:-gdb}

# Run GDB, strip out unwanted noise.
# --readnever is no longer used since .gdb_index is now in use.
$GDB --quiet -nx $GDBARGS /proc/$1/exe $1 <<EOF 2>&1 |
set width 0
set height 0
set pagination no
$backtrace
EOF
/bin/sed -n \
    -e 's/^\((gdb) \)*//' \
    -e '/^#/p' \
    -e '/^Thread/p'

启动:

 sudo bash /usr/bin/pstack 27596 

然后确保pstack命令被以root权限或者适当的权限运行,并确保目标进程处于可调试状态,可以通过使用gdb或者其他调试工具来设置进程的调试。pstack显示的其实是进程在某一刻的调用栈。

以htop为例,这是我们用pstack调用的结果:

这个是用gdb调用的结果:

按照下面的步骤检查一下线程内部的问题

  1. 命令:top -c。 输入大写P,top的输出会按使用cpu多少排序

PID就是进程号,我程序的进程号是4918。

  1. 查看耗CPU的线程号

    命令:top -Hp 进程号。 同样输入大写Ptop的输出会按使用cpu多少排序。

    输入`top -Hp 4918`,展示内容如图:
    

  可以看出PID是4927的线程占到了100%的cpu,我的业务日志是打印线程号的
  1. 查看耗CPU的任务

    上面找到了耗CPU的线程,那这个线程在做什么呢?

    看线程在干什么,可以看线程的堆栈,命令是pstack 进程号,会输出所有线程的堆栈信息。

    输入pstack 4918,并搜索线程4927的堆栈,展示内容如图:

2. 使用kill功能完成堆栈抓取

除了ptsack以外,还可以使用kill完成高CPU占用时候的数据抓取,相信大家基本使用的是kill -9 + 进程号的方法杀程序。但是我们可以使用kill -s的方法完成更多的功能:

这里是一些常见的kill信号和它们的含义:

  • HUP (1):挂起信号,通常用于通知进程重新加载配置文件。
  • INT (2):中断信号,通常由用户按下Ctrl+C来发送,用于中断进程。
  • QUIT (3):退出信号,通常由用户按下Ctrl+\来发送,用于请求进程退出并生成核心转储文件。
  • ILL (4):非法指令信号,表示进程执行了非法指令。
  • TRAP (5):陷阱信号,通常用于调试目的。
  • ABRT (6):中止信号,通常用于请求进程异常终止并生成核心转储文件。
  • BUS (7):总线错误信号,表示进程执行了非法内存访问。
  • FPE (8):浮点异常信号,表示进程执行了非法的浮点运算。
  • KILL (9):强制终止信号,用于立即终止进程,进程无法捕获或忽略这个信号。
  • USR1 (10):用户自定义信号1,可以由用户自定义处理。
  • USR2 (12):用户自定义信号2,可以由用户自定义处理。
  • PIPE (13):管道破裂信号,表示进程尝试写入已被关闭的管道。
  • ALRM (14):闹钟信号,用于定时器通知。
  • TERM (15):终止信号,通常用于请求进程正常终止。
  • CONT (18):继续信号,用于恢复已停止的进程。
  • STOP (19):停止信号,用于暂停进程的执行。
  • TSTP (20):终端停止信号,通常由用户按下Ctrl+Z来发送,用于请求进程暂停。
  • TTIN (21):后台进程尝试读取终端信号。
  • TTOU (22):后台进程尝试写入终端信号。
  • URG (23):紧急条件信号。
  • XCPU (24):CPU时间限制信号。
  • XFSZ (25):文件大小限制信号。
  • VTALRM (26):虚拟定时器信号。
  • PROF (27):性能计数器定时器信号。
  • WINCH (28):窗口大小改变信号。
  • POLL (29):轮询文件描述符事件信号。
  • PWR (30):电源故障信号。
  • SYS (31):保留供系统使用的信号。
  • RTMIN (34):实时信号的最小值。
  • RTMAX (64):实时信号的最大值。

我们在这里可以使用kill -s ABRT命令,这可以向进程发送ABRT信号,该信号会导致进程异常终止并生成核心转储文件。通常情况下,这个信号是用来告诉进程发生了严重的错误,需要进行核心转储以便进行调试和分析。然后操作就是和我们看coredump操作一致了。

3. htop完成堆栈追踪

htop非linux系统的自带工具,需要用户自行安装

sudo apt install htop

htop的界面与top很类似,但功能更强,一大特色是支持鼠标和滚轮操作:

此外还可以切换到大写,并使用P来完成CPU利用率排序,使用F完成该进程在界面中高亮显示

使用sudo运行htop,还可以输入s完成strace命令追踪进程的系统调用情况

其他常用指令

  • / 或 F3 : 搜索一个进程
  • \ 或 F4 : 模糊搜索,过滤器
  • F5 :树状显示
  • F7 :减小nice,增加优先级
  • F8 :增大nice,减少优先级
  • F9 :杀死进程

4. 使用strace完成堆栈动态追踪

首先也是安装strace

 sudo apt-get install strace 

strace是一个用来动态追踪进程处理的的系统调用和信号的命令。输入下面指令就可以获取信息

sudo strace -p 29844

高级操作可以参考这篇文章:https://cloud.tencent.com/developer/article/1667055

5. 参考连接

https://blog.csdn.net/lmb1612977696/article/details/89404019

https://cloud.tencent.com/developer/article/1667055

http://www.wangkaixuan.tech/?p=943