文章目录
前言
Linux下我们可以通过通过命令行输入free
查看系统的整体内存消耗情况,以及通过输入
cat /proc/$PID/status
查看进程实际消耗物理内存VmRss的方式了解到进程所消耗的内存。因此我们可以通过输入命令行的方式进行对比查看了解当内存泄露时,具体是哪个进程泄露的。
但是想要查看线程所消耗的内存则需要借助外部工具了,这里我们介绍下通过strace
工具查看进程内存增长时,具体是哪个线程增长的
1、strace简介?
Strace是个功能强大的Linux调试分析诊断工具,可用于跟踪程序执行时进程系统调用(system call)和所接收的信号,尤其是针对源码不可读或源码无法再编译的程序。
在Linux系统中,用户进程不能直接访问计算机硬件设备。当进程需要访问硬件设备(如读取磁盘文件或接收网络数据等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。
strace可跟踪进程产生的系统调用,包括参数、返回值和执行所消耗的时间。
若strace没有任何输出,并不代表此时进程发生阻塞;也可能程序进程正在执行某些不需要与系统其它部分发生通信的事情。strace从内核接收信息,且无需以任何特殊方式来构建内核。
2、strace下载和编译
嵌入式产品大都是使用arm平台,因此我们想要在开发设备上使用strace就需要通过交叉编译工具链对strace源码进行编译链接生成最终的可执行文件。
2.1 下载源码
下载 strace的源码,一般不建议下载最新版本的,因为可能存在一些问题,这里我下载的是6.2版本的。
下载地址是: 链接: strace
2.2 编译
-
下载完成后,将压缩包放到自己的编译环境下。
-
进行配置,这一步的目的是修改strace的编译工具链为我们正在使用的。
在strace的源码主目录替换自己的交叉编译工具链:
./configure --prefix=//tool/strace-6.2/instal__ --enable-mpers=no --host=arm-linux --target=arm-linux CC=/arm-linux-gcc LD=/arm-linux-ld
-
编译
直接在源码目录下执行,通过上一步配置生成的makefile
执行:Make
成果物:
strace-6.2/instal__ 目录下
3.strace使用介绍
Strace的通用使用方式为:
strace <command>
其中,是要跟踪的命令或程序的命令,运行命令后,strace将输出与命令相关的系统调用和信号信息。
以下是strace命令的各个选项的详细介绍:
-o <file>:将strace输出写入指定的文件中,而不是默认的标准错误输出。例如:strace -o output.txt command
-e <expr>:指定一个表达式,用于过滤所要跟踪的系统调用。只有符合表达式条件的系统调用才会被跟踪。可以使用多个表达式,用逗号分隔。可以使用的表达式类型有:
- trace=<syscall>:跟踪指定的系统调用。例如:-e trace=open,read
- read=<size>:只跟踪读取指定字节数以上的读取操作。
- write=<size>:只跟踪写入指定字节数以上的写入操作。
- signal=<signal>:跟踪指定的信号相关的系统调用。
- fd=<file descriptor>:跟踪与指定文件描述符相关的系统调用。
- path=<path>:跟踪与指定路径相关的系统调用。
-p <pid>:跟踪指定进程ID的系统调用。可以同时跟踪多个进程,多个pid之间用逗号分隔。例如:strace -p 1234。
-s <size>:指定输出的字符串最大长度,超过该长度的字符串将被截断。例如:strace -s 1000 command。
-t:在输出中显示每次系统调用的时间戳。例如:strace -t command。
-c:在程序运行结束后,显示每个系统调用的统计信息,包括调用次数和总共使用的时间。例如:strace -c command。
-f:跟踪由fork创建的子进程。例如:strace -f command。
-ff:在跟踪时为每个进程创建一个独立的输出文件。例如:strace -ff command。
-y:打印路径名时,将路径名转换为人类可读的格式,而不是以原始形式显示。例如:strace -y command。
-h:显示帮助信息,列出所有可用的选项。
这些选项可以根据实际需求进行组合使用。请注意,在使用strace进行系统调用跟踪时,需要小心敏感信息的泄露,并遵守相关的法律法规。
4.strce实战
4.1 背景介绍
在工作开发过程中发现当某个场景触发时,系统整体消耗的内存会多7M,经过对问题复现借助查看各个进程的vmrss信息,初步确定了是应用进程消耗的。
因为linux系统提供的常用命令只能观察到进程消耗了多少内存。但是进程中又包含了很多的线程,其中绝大部分线程是应用申请的,但是还有一部分线程是外部组件申请。如果都是应用申请的,知道是哪个线程消耗的可以帮助我们快速的找到内存消耗点,如果是应用中的组件线程申请的,则我们需要及时和相关组件人员进行沟通,由相关组件人员进行排查。因此我们需要确定各个线程消耗了多少内存。
之后通过一些其它渠道我们了解到可以通过strace工具来检测每个线程的系统调用,通过这种方式可以查看在内存快速增长期间,查看有哪些线程正在申请内存,从而确定内存到底是被谁给使用了。
4.2 检测原理
在使用strace命令时,我们可以通过加入下面的参数,让strace打印出来系统调用的时间和线程以及过滤关键字。
-tt 在每行输出的前面,显示毫秒级别的时间
-f 跟踪目标进程,以及目标进程创建的所有子进程
-e 控制要跟踪的事件和跟踪行为,比如指定要跟踪的系统调用名称。
在-e参数后面可以通过以下关键字来查看内存使用情况:
brk:用于动态分配内存的系统调用。通过跟踪brk调用可以观察到进程动态分配内存的情况。
mmap/mmap2:用于映射文件或者设备到内存中的系统调用。通过跟踪mmap调用可以观察到进程对内存映射的操作,包括动态库加载、文件I/O等。
munmap:用于解除映射的系统调用。通过跟踪munmap调用可以观察到进程释放内存映射的情况。
sbrk:类似于brk,也是用于动态分配内存的系统调用。通过跟踪sbrk调用同样可以观察到进程动态分配内存的情况。
实际使用过程中发现本系统中通过mmap2来统计映射文件或者设备到内存中的系统调用,然后sbrk信息几乎没有,brk信息非常多。
其中brk是系统调用用于扩展或收缩进程的堆空间。当程序需要更多的堆内存时,它会调用 brk 来增加堆的大小。如果 brk 系统调用的地址一直在增加,意味着进程一直在请求更多的堆内存,但没有释放已经不再使用的堆内存。
brk信息一般是如下格式
[pid 110] 16:29:49.119530 brk(0x957000) = 0x957000
[pid 110] 16:29:49.119538 brk(0x958000) = 0x958000
[pid 110] 16:29:49.119539 brk(0x959000) = 0x959000
其中pid: 线程号
Brk(0x95800) 代表着当前堆栈的地址,两条相邻brk之间的地址相差4k, 因此可以通过该(pid 打印的brk信息条数 – 1) 4 K 来大致确定内存增长期间该pid申请了多大的内存*
4.3 排查方式
-
- 通过tftp/ftp将编译好的strace放到设备的文件系统指定目录下(最好和应用可执行文件在同一个目录)
-
-
修改启动脚本,通过如下方式启动自己的app,可以检测app中每时每刻的哪些线程正在申请内存。
/strace -tt -f -e "brk,mmap2,munmap" /app &
-
-
- 通过ps –T 打印出来所有的线程号
-
- 对问题进行复现,挑选出内存增加时的日志
-
- 从日志中找到brk打印条数最多的线程
-
- 按照每个brk差值为4K,进行计算,例如PID:110的线程,在内存增长期间,打印了100个brk,那么这期间他申请的内存应该是>= 4k * 100。
计算方式举例:
起始地址:brk(0x6fb000) = 0x6fb000 最终地址:brk(0xb93000) = 0xb93000 整体消耗 = 最终地址 – 起始地址 = 498 000 ≈ 5M
注意:
有些不适用brk的打印条数*4K的原因是,部分情况下会出现相邻两个brk之间地址跳跃的情况(怀疑这种一般出现在某个不常申请内存的线程,突然申请了大块内存的情况)。
5. 总结
Strace排查内存消耗的方式除了上述的通过brk查看外,还可以通过mmap和munmap来对比申请了多少内存和释放了多少内存。
具体排查方式和问题出现后strace的打印息息相关。
另外strace还有很多强大的功能,例如监视系统行为,理解应用程序依赖关系等等。
详细信息大家可以查看参考链接中的内容。
6. 参考链接
利用Strace工具分析内存泄漏问题_strace 内存泄漏_权艺的博客-CSDN博客
strace 怎么用 - CSDN文库
Linux进程照妖镜strace命令 - 知乎 (zhihu.com)