作为c的程序员,最常见的就是排查内存泄漏,不过我们一般的内存泄漏是针对特定的程序去排查,相对来说比较容易,但是如果是维护人员,不知道哪个程序有内存泄漏,甚至是应用程序的内存泄漏,还是内核的内存泄漏都不明确,所以一定要有一定的查内存泄漏的章法.

一 虚拟内存泄露

一般来说,我们观察系统的内存占用喜欢用top命令,然后输入m,对系统中整体的内存占用情况做个排序,然后在重点观察,内存占用排在前几位的进程,再逐步的分析,

[root@VM-0-2-centos ~]# top -p 5576 top - 18:21:46 up 198 days, 20:07, 2 users, load average: 0.10, 0.04, 0.05 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 1882008 total, 78532 free, 116516 used, 1686960 buff/cache KiB Swap: 0 total, 0 free, 0 used. 1606660 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 5576 root 20 0 184064 11248 1124 S 0.0 0.6 10:34.98 nginx

虽然top 也可以观察到单独的进程的内存变化,不过一般不太好比较内存变化的规律, 推荐使用pidstat工具,此工具需要先安装,通过命令:

yum install sysstat

pidstat 基本说明如下: -u:默认的参数,显示各个进程的cpu使用统计 -r:显示各个进程的内存使用统计 -d:显示各个进程的IO使用情况 -p:指定进程号 -w:显示每个进程的上下文切换情况 -t:显示选择任务的线程的统计信息外的额外信息 -T TASK CHILD | ALL

假如我们观察到如下的内存占用情况:pidstat -r -p pid 5

[root@VM-0-2-centos ~]# pidstat -r -p 5981 5 Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-2-centos) 07/24/2021 _x86_64_ (1 CPU) 06:25:55 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command 06:26:00 PM 0 5981 0.20 0.00 4416 352 0.02 a.out 06:26:05 PM 0 5981 0.00 0.00 4416 352 0.02 a.out 06:26:10 PM 0 5981 0.20 0.00 4456 352 0.02 a.out 06:26:15 PM 0 5981 0.00 0.00 4456 352 0.02 a.out 06:26:20 PM 0 5981 0.00 0.00 4456 352 0.02 a.out 06:26:25 PM 0 5981 0.20 0.00 4496 352 0.02 a.out 06:26:30 PM 0 5981 0.00 0.00 4496 352 0.02 a.out 06:26:35 PM 0 5981 0.20 0.00 4536 352 0.02 a.out 06:26:40 PM 0 5981 0.00 0.00 4536 352 0.02 a.out 06:26:45 PM 0 5981 0.20 0.00 4576 352 0.02 a.out 06:26:50 PM 0 5981 0.00 0.00 4576 352 0.02 a.out 06:26:55 PM 0 5981 0.20 0.00 4616 352 0.02 a.out

我们注意下,VSZ即虚拟内存的占用每10s增加40,单位为k,即10s增加40k的虚拟内存。 下面来具体分析下这个程序的内存泄露情况。

二 分析泄露原因

我们来分析这个进程的内存分布情况,来分析这泄露的内存有什么特点:

[root@VM-0-2-centos ~]# pmap -x 5981 5981: ./a.out Address Kbytes RSS Dirty Mode Mapping 0000000000400000 4 4 0 r-x-- a.out 0000000000600000 4 4 4 r---- a.out 0000000000601000 4 4 4 rw--- a.out 00007faab436e000 2720 272 272 rw--- [ anon ] 00007faab4616000 1804 260 0 r-x-- libc-2.17.so 00007faab47d9000 2048 0 0 ----- libc-2.17.so 00007faab49d9000 16 16 16 r---- libc-2.17.so 00007faab49dd000 8 8 8 rw--- libc-2.17.so 00007faab49df000 20 12 12 rw--- [ anon ] 00007faab49e4000 136 108 0 r-x-- ld-2.17.so 00007faab4a06000 2012 212 212 rw--- [ anon ] 00007faab4c03000 8 8 8 rw--- [ anon ] 00007faab4c05000 4 4 4 r---- ld-2.17.so 00007faab4c06000 4 4 4 rw--- ld-2.17.so 00007faab4c07000 4 4 4 rw--- [ anon ] 00007ffe0f3f5000 132 16 16 rw--- [ stack ] 00007ffe0f47c000 8 4 0 r-x-- [ anon ] ffffffffff600000 4 0 0 r-x-- [ anon ] ---------------- ------- ------- ------- total kB 8940 940 564

其中Address为开始的地址,Kbytes是虚拟内存的大小,RSS为真实内存的大小,Dirty为未同步到磁盘上的脏页,Mode为内存的权限,rw为可写可读,rx为可读和可执行。 通过几次观察,我们发现:

00007faab436e000 2720 272 272 rw--- [ anon ]

为泄露部分,此为匿名内存区,也就是没有映射文件,为malloc或mmap分配的内存。 同样是每次增加40K。

此时还是只能大概知道内存泄露的位置,我们还先找到具体的代码位置,这个该怎么分析? 代码的申请,无非是通过malloc和brk这些库函数进行内存调用,我们可以用strace跟踪下。

[root@VM-0-2-centos ~]# strace -f -t -p 5981 -o trace.strace strace: Process 5981 attached strace: Process 8519 attached strace: Process 8533 attached strace: Process 8547 attached strace: Process 8557 attached strace: Process 8575 attached ^Cstrace: Process 5981 detached

我们通过-t选项来显示时间,-f来跟踪子进程。直接用cat命令查看跟踪的文件内容,会发现内容相当多,只要是系统调用都打印了出来,可以通过每次增加40k这个有用的信息搜索下:

[root@VM-0-2-centos ~]# grep 40960 trace.strace 5981 19:01:44 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab403a000 5981 19:01:55 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4030000 5981 19:02:06 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4026000 5981 19:02:17 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab401c000 5981 19:02:28 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4012000

至此我们找到了具体的泄露代码位置。

看下这个测试代码:

#include <stdio.h> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/wait.h> #define _SCHED_H #define __USE_GNU #include <bits/sched.h> #define STACK_SIZE 40960 int func(void *arg) { printf("thread enter.\n"); sleep(1); printf("thread exit.\n"); return 0; } int main() { int thread_pid; int status; int w; while (1) { void *addr = mmap(NULL, STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0); if (addr == NULL) { perror("mmap"); goto error; } printf("creat new thread...\n"); thread_pid = clone(&func, addr STACK_SIZE, CLONE_SIGHAND|CLONE_FS|CLONE_VM|CLONE_FILES, NULL); printf("Done! Thread pid: %d\n", thread_pid); if (thread_pid != -1) { do { w = waitpid(-1, NULL, __WCLONE | __WALL); if (w == -1) { perror("waitpid"); goto error; } } while (!WIFEXITED(status) && !WIFSIGNALED(status)); } sleep(10); } error: return 0; }

这个测试程序利用mmap申请一块匿名私有的内存,clone为系统函数,pthread_create 和fork底层都是调用它,用来创建进程/线程,将func的地址指针存放在子进程堆栈的某个位置处,该位置就是该封装函数本身返回地址存放的位置,最后一个参数为func的执行参数。clone可以更灵活控制共享,比如可以控制是否共享内存空间,是否共享打开文件,是否共享相同的信号处理函数等。

我们可以看到,mmap申请内存后,需要通过munmap来释放,这里面没有释放,所以导致了虚拟内存泄露,这里面申请的内存只实际使用了4个字节,即复制了func的指针,其他的内存均没有使用,其实仔细观察会发现还有部分的物理内存泄露,每次4个字节,可以通过pmap -x 查到。

在 waitpid后面添加:munmap(addr,STACK_SIZE); 即可以实现内存的释放。

三 valgrind 分析程序内存泄露

这个是比较常见的方法,一般通过下面命令来查看内存泄露:

valgrind --tool=memcheck --leak-check=full ./b [root@VM-0-2-centos test]# valgrind --tool=memcheck --leak-check=full ./b ==14374== Memcheck, a memory error detector ==14374== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==14374== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==14374== Command: ./b ==14374== ==14374== Warning: set address range perms: large range [0x5205040, 0x24605040) (undefined) address:0x5205040 ==14374== Warning: set address range perms: large range [0x5205040, 0x24605040) (defined) 524288000 ==14374== ==14374== HEAP SUMMARY: ==14374== in use at exit: 524,288,000 bytes in 1 blocks ==14374== total heap usage: 1 allocs, 0 frees, 524,288,000 bytes allocated ==14374== ==14374== 524,288,000 bytes in 1 blocks are possibly lost in loss record 1 of 1 ==14374== at 0x4C29F73: malloc (vg_replace_malloc.c:309) ==14374== by 0x400675: main (test.c:17) ==14374== ==14374== LEAK SUMMARY: ==14374== definitely lost: 0 bytes in 0 blocks ==14374== indirectly lost: 0 bytes in 0 blocks ==14374== possibly lost: 524,288,000 bytes in 1 blocks ==14374== still reachable: 0 bytes in 0 blocks ==14374== suppressed: 0 bytes in 0 blocks

明确说明在test.c的17行有可能是内存泄露:==14374== by 0x400675: main (test.c:17) 其他用法看说明吧。

四 其他内存泄露分析

其实上面的内存泄露是我们知道了具体的泄露的进程,然后再做详细分析。那么如果不知道哪里内存泄露了,有什么办法,可以通过分析meminfo文件,来观察泄露的类型。

[root@VM-0-2-centos test]# cat /proc/meminfo MemTotal: 1882008 kB MemFree: 752948 kB MemAvailable: 1610108 kB Buffers: 564900 kB Cached: 399584 kB SwapCached: 0 kB Active: 808140 kB Inactive: 220812 kB Active(anon): 64548 kB Inactive(anon): 488 kB Active(file): 743592 kB Inactive(file): 220324 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 0 kB SwapFree: 0 kB ....

linux程序内存泄露检查(linux下内存泄漏排查)(1)

这里面说明的挺详细的了,如果遇到内存问题,观察这个肯定会发现猫腻的。

五 诗词欣赏 蝶恋花.春景苏轼花褪残红青杏小。燕子飞时,绿水人家绕。枝上柳绵吹又少。天涯何处无芳草。墙里秋千墙外道。墙外行人,墙里佳人笑。笑渐不闻声渐悄。多情却被无情恼。

,