大疆嵌入式面试:Linux内存泄漏与高占用排查方法

在 Linux 系统中,内存泄漏就像是一个悄无声息的杀手,慢慢侵蚀着系统的资源。简单来说,内存泄漏是指程序在申请内存后,当该内存不再被使用时,却没有将其释放回系统 ,导致这部分内存一直被占用,无法被其他程序使用。
Linux 内存泄漏与高占用问题是大疆嵌入式面试中的重点内容,掌握有效的排查方法和工具至关重要。Valgrind 和 AddressSanitizer 是排查内存泄漏的利器,top、htop 以及 /proc 文件系统则是监控和分析内存占用的得力助手 。在实际面试中,不仅要熟悉这些工具和方法的理论知识,更要能够结合具体的场景和问题,灵活运用,展示出自己解决问题的能力。
对于准备大疆嵌入式面试的同学,建议大家在复习时,多进行实际操作和案例分析,加深对这些排查方法的理解和掌握。可以自己编写一些包含内存问题的测试程序,然后使用各种工具进行排查和解决,积累实践经验。同时,要关注内存管理在嵌入式系统中的重要性,理解内存泄漏和高占用对系统性能的影响机制,这样在面试中才能更加深入地回答问题,展现出自己的专业素养和技术能力 。
Part1.内存泄漏:悄无声息的系统杀手
在 Linux 系统中,内存泄漏就像是一个悄无声息的杀手,慢慢侵蚀着系统的资源。简单来说,内存泄漏是指程序在申请内存后,当该内存不再被使用时,却没有将其释放回系统 ,导致这部分内存一直被占用,无法被其他程序使用。就好比你向图书馆借了一本书,看完后却不归还,随着时间推移,越来越多的人借书不还,图书馆的书就会越来越少,可供其他人借阅的资源也就越来越稀缺。
在嵌入式系统里,内存资源本就十分有限,内存泄漏带来的后果往往更加严重。每一次内存泄漏,都像是从系统的 “内存储备库” 中偷走了一部分资源,随着泄漏的不断积累,系统可用内存越来越少。这会导致系统频繁进行内存交换操作,从磁盘的虚拟内存中读写数据,而磁盘的读写速度远远慢于内存,从而使得系统性能急剧下降,响应变得迟缓,原本流畅运行的程序可能变得卡顿甚至无响应。当内存泄漏严重到一定程度,系统再也无法分配到足够的内存来满足正常的运行需求,就如同水库干涸,无法为下游提供足够的水源,系统便会陷入崩溃,造成无人机飞行异常、工业控制设备故障等严重问题。
(1)内存占用过大为什么?
内存占用过大的原因可能有很多,以下是一些常见的情况:
内存泄漏:当程序在运行时动态分配了内存但未正确释放时,会导致内存泄漏。这意味着那部分内存将无法再被其他代码使用,最终导致内存占用增加。频繁的动态内存分配和释放:如果程序中频繁进行大量的动态内存分配和释放操作,可能会导致内存碎片化问题。这样系统将难以有效地管理可用的物理内存空间。数据结构和算法选择不当:某些数据结构或算法可能对特定场景具有较高的空间复杂度,从而导致内存占用过大。在设计和选择数据结构和算法时应综合考虑时间效率和空间效率。缓存未及时清理:如果程序中使用了缓存机制,并且没有及时清理或管理缓存大小,就会导致缓存占用过多的内存空间。高并发环境下资源竞争:在高并发环境下,多个线程同时访问共享资源(包括对内存的申请和释放)可能引发资源竞争问题。若没有适当的同步机制或锁策略,可能导致内存占用过大。第三方库或框架问题:使用的第三方库或框架可能存在内存管理不当、内存泄漏等问题,从而导致整体程序的内存占用过大。(2)内存泄露和内存占用过大区别?
内存泄漏指的是在程序运行过程中,动态分配的内存空间没有被正确释放,导致这些内存无法再被其他代码使用。每次发生内存泄漏时,系统可用的物理内存空间就会减少一部分,最终导致整体的内存占用量增加。
而内存占用过大则是指程序在运行时所消耗的物理内存超出了合理范围或预期值。除了因为内存泄漏导致的额外占用外,其他原因如频繁的动态内存分配和释放、数据结构和算法选择不当、缓存管理问题等都可能导致程序的内存占用过大。
可以说,内存在被正确管理和使用时,即使有一定程度的动态分配和释放操作,也不会造成明显的长期累积效应,即不会出现持续性的内存占用过大情况。而如果存在未及时释放或回收的资源(即发生了内存泄漏),随着时间推移会逐渐积累并导致整体的内存占用越来越高。
因此,在排查和解决内存占用过大问题时,需要注意是否存在内存泄漏,并且还需综合考虑其他可能导致内存占用过大的因素。
(3)产生的原因
我们在进行程序开发的过程使用动态存储变量时,不可避免地面对内存管理的问题。程序中动态分配的存储空间,在程序执行完毕后需要进行释放。没有释放动态分配的存储空间而造成内存泄漏,是使用动态存储变量的主要问题。
一般情况下,作为开发人员会经常使用系统提供的内存管理基本函数,如malloc、realloc、calloc、free等,完成动态存储变量存储空间的分配和释放。但是,当开发程序中使用动态存储变量较多和频繁使用函数调用时,就会经常发生内存管理错误。
Part2.虚拟内存泄露
一般来说,我们观察系统的内存占用喜欢用top命令,然后输入m,对系统中整体的内存占用情况做个排序,然后在重点观察,内存占用排在前几位的进程,再逐步的分析。
[root@VM-0-2-centos ~]# top -p 5576top - 18:21:46 up 198 days, 20:07, 2 users, load average: 0.10, 0.04, 0.05Tasks: 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 stKiB Mem : 1882008 total, 78532 free, 116516 used, 1686960 buff/cacheKiB 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 5Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-2-centos) 07/24/2024 _x86_64_ (1 CPU)06:25:55 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command06:26:00 PM 0 5981 0.20 0.00 4416 352 0.02 a.out06:26:05 PM 0 5981 0.00 0.00 4416 352 0.02 a.out06:26:10 PM 0 5981 0.20 0.00 4456 352 0.02 a.out06:26:15 PM 0 5981 0.00 0.00 4456 352 0.02 a.out06:26:20 PM 0 5981 0.00 0.00 4456 352 0.02 a.out06:26:25 PM 0 5981 0.20 0.00 4496 352 0.02 a.out06:26:30 PM 0 5981 0.00 0.00 4496 352 0.02 a.out06:26:35 PM 0 5981 0.20 0.00 4536 352 0.02 a.out06:26:40 PM 0 5981 0.00 0.00 4536 352 0.02 a.out06:26:45 PM 0 5981 0.20 0.00 4576 352 0.02 a.out06:26:50 PM 0 5981 0.00 0.00 4576 352 0.02 a.out06:26:55 PM 0 5981 0.20 0.00 4616 352 0.02 a.out
我们注意下,VSZ即虚拟内存的占用每10s增加40,单位为k,即10s增加40k的虚拟内存,下面来具体分析下这个程序的内存泄露情况。
Part3.分析泄露原因
我们来分析这个进程的内存分布情况,来分析这泄露的内存有什么特点:
[root@VM-0-2-centos ~]# pmap -x 59815981: ./a.outAddress Kbytes RSS Dirty Mode Mapping0000000000400000 4 4 0 r-x-- a.out0000000000600000 4 4 4 r---- a.out0000000000601000 4 4 4 rw--- a.out00007faab436e000 2720 272 272 rw--- [ anon ]00007faab4616000 1804 260 0 r-x-- libc-2.17.so00007faab47d9000 2048 0 0 ----- libc-2.17.so00007faab49d9000 16 16 16 r---- libc-2.17.so00007faab49dd000 8 8 8 rw--- libc-2.17.so00007faab49df000 20 12 12 rw--- [ anon ]00007faab49e4000 136 108 0 r-x-- ld-2.17.so00007faab4a06000 2012 212 212 rw--- [ anon ]00007faab4c03000 8 8 8 rw--- [ anon ]00007faab4c05000 4 4 4 r---- ld-2.17.so00007faab4c06000 4 4 4 rw--- ld-2.17.so00007faab4c07000 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.stracestrace: Process 5981 attachedstrace: Process 8519 attachedstrace: Process 8533 attachedstrace: Process 8547 attachedstrace: Process 8557 attachedstrace: 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) = 0x7faab403a0005981 19:01:55 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab40300005981 19:02:06 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab40260005981 19:02:17 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab401c0005981 19:02:28 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4012000
至此我们找到了具体的泄露代码位置。看下这个测试代码:
#include
这个测试程序利用mmap申请一块匿名私有的内存,clone为系统函数,pthread_create 和fork底层都是调用它,用来创建进程/线程,将func的地址指针存放在子进程堆栈的某个位置处,该位置就是该封装函数本身返回地址存放的位置,最后一个参数为func的执行参数。clone可以更灵活控制共享,比如可以控制是否共享内存空间,是否共享打开文件,是否共享相同的信号处理函数等。
我们可以看到,mmap申请内存后,需要通过munmap来释放,这里面没有释放,所以导致了虚拟内存泄露,这里面申请的内存只实际使用了4个字节,即复制了func的指针,其他的内存均没有使用,其实仔细观察会发现还有部分的物理内存泄露,每次4个字节,可以通过pmap -x 查到。
在 waitpid后面添加:munmap(addr,STACK_SIZE); 即可以实现内存的释放。
如何排查内存泄漏
memwatchmtracedmallocccmallocvalgrinddebug_new我们平时开发过程中不可避免的会遇到内存泄漏问题,这是常见的问题。既然发生了内存泄漏,我们就要排查内存泄漏的问题。想必大家也经常会用到以下排查内存问题的工具,如下:
Part4.Valgrind 排查工具
在众多排查 Linux 内存泄漏与高占用的工具中,Valgrind 堪称一把锋利的 “宝剑”,在嵌入式开发中被广泛使用。Valgrind 是一套 Linux 下开放源代码(GPL V2)的仿真调试工具的集合 ,它由内核(core)以及基于内核的其他调试工具组成。其内核就像一个精心搭建的舞台框架,模拟出一个 CPU 环境,为其他工具提供表演的场地和基础服务;而其他工具则如同一个个身怀绝技的演员,在这个舞台上利用内核提供的服务,完成各种特定的内存调试任务。
(1)安装与准备
在使用 Valgrind 之前,我们首先得把它安装到系统中。安装方法很简单,对于基于 Debian 或 Ubuntu 的系统,只需在终端中输入 “sudo apt-get install valgrind” ,系统就会自动从软件源中下载并安装 Valgrind 及其依赖项,就像从云端仓库中取来一件趁手的工具。而对于 CentOS 等基于 Red Hat 的系统,安装命令则变为 “sudo yum install valgrind” ,同样能快速将这个强大的工具收入囊中。
(2)基本使用命令
安装完成后,就可以使用 Valgrind 来检测程序的内存问题了。它的基本使用命令格式为:“valgrind [valgrind-options] your-prog [your-prog-options]” 。这里的 “valgrind-options” 是 Valgrind 自身的一些选项,用于控制它的行为和输出;“your-prog” 是你要检测的目标程序;“your-prog-options” 则是目标程序运行时需要的参数。
其中,最常用的选项是 “--tool=memcheck” ,它指定使用 Memcheck 工具,这是 Valgrind 应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况。比如使用未初始化的内存,使用已经释放了的内存,内存访问越界等。若想让 Valgrind 在程序结束时详细叙述每一个内存泄漏情况,就可以加上 “--leak-check=full” 选项;如果希望在错误报告中显示可到达的内存块(即虽然没有释放,但仍有指针指向的内存块),则可以使用 “--show-reachable=yes” 选项。
(3)工作原理
Valgrind 中的 Memcheck 工具能够检测出内存问题,关键在于其建立了两个全局表。第一个是 Valid-Value 表,对于进程的整个地址空间中的每一个字节 (byte),都有与之对应的 8 个 bits;对于 CPU 的每个寄存器,也有一个与之对应的 bit 向量 。这些 bits 就像一个个小卫士,负责记录该字节或者寄存器值是否具有有效的、已初始化的值。第二个是 Valid-Address 表,对于进程整个地址空间中的每一个字节 (byte),还有与之对应的 1 个 bit,它就像一把地址锁,负责记录该地址是否能够被读写。
当程序要读写内存中某个字节时,Memcheck 首先会检查这个字节对应的 A bit。如果该 A bit 显示该位置是无效位置,就如同试图打开一把被锁住的门,Memcheck 则会报告读写错误。内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit 也会被加载到虚拟的 CPU 环境中。一旦寄存器中的值被用来产生内存地址,或者该值能够影响程序输出,Memcheck 就会像一个警惕的监察员,检查对应的 V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。
(4)实际案例分析
为了更直观地感受 Valgrind 的强大功能,我们来看一个实际案例。假设有如下一段简单的 C 语言代码:
#include
这段代码中存在内存泄漏、使用未初始化变量以及数组越界访问的问题。我们使用 Valgrind 来检测它。首先,使用 “gcc -g -O0 -Wall test.c -o test” 命令进行编译,其中 “-g” 选项用于生成调试信息,这对于 Valgrind 准确报告错误位置至关重要;“-O0” 选项表示不进行优化,防止优化影响调试结果;“-Wall” 选项用于开启所有常见的警告信息。
编译完成后,执行 “valgrind --tool=memcheck --leak-check=full --show-reachable=yes./test” 命令。Valgrind 运行后,会输出详细的错误报告:
==2596== Memcheck, a memory error detector==2596== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.==2596== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info==2596== Command:./test==2596== ==2596== Invalid write of size 4==2596== at 0x4005C3: main (test.c:16)==2596== Address 0x51f3050 is 0 bytes after a block of size 80 alloc'd==2596== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)==2596== at 0x4005A9: main (test.c:14)==2596== ==2596== Conditional jump or move depends on uninitialised value(s)==2596== at 0x4005F5: uninit (test.c:9)==2596== by 0x40060A: main (test.c:18)==2596== ==2596== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1==2596== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)==2596== by 0x4005D8: leak (test.c:5)==2596== by 0x400605: main (test.c:17)==2596== ==2596== LEAK SUMMARY:==2596== definitely lost: 100 bytes in 1 blocks==2596== indirectly lost: 0 bytes in 0 blocks==2596== possibly lost: 0 bytes in 0 blocks==2596== still reachable: 0 bytes in 0 blocks==2596== suppressed: 0 bytes in 0 blocks==2596== ==2596== For counts of detected and suppressed errors, rerun with: -v==2596== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
从报告中可以清晰地看到,Valgrind 准确地指出了代码中的数组越界访问错误(“Invalid write of size 4”),发生在 “test.c” 文件的 16 行;使用未初始化变量错误(“Conditional jump or move depends on uninitialised value (s)”),发生在 “test.c” 文件的 9 行;以及内存泄漏错误(“100 bytes in 1 blocks are definitely lost”),发生在 “test.c” 文件的 5 行,泄漏的内存大小为 100 字节(因为分配了 10 个 int 类型的内存空间,每个 int 通常为 4 字节,共 40 字节,但这里报告 100 字节可能包含了一些内部管理信息)。
根据这些详细的错误信息,我们就可以轻松地定位并修复代码中的问题,让程序更加健壮和稳定 。通过这个案例,我们可以看到 Valgrind 在排查内存问题时的高效和精准,它就像一位经验丰富的侦探,能够在复杂的代码迷宫中,准确地找出内存相关的 “罪犯”,为我们解决内存泄漏和高占用问题提供了有力的支持。
Part5.AddressSanitizer(ASan)排查工具
AddressSanitizer(ASAN)是一款由 Google 开发的内存错误检测器 ,在检测内存泄漏方面展现出独特的优势。它主要通过编译器插桩的方式,在程序编译阶段将内存访问检查代码巧妙地插入到目标程序中,就像在程序的关键位置安插了许多 “小哨兵”,时刻监视着内存的使用情况。
(1)特点与优势
与传统的内存检测工具相比,AddressSanitizer 最大的特点就是快。它对程序性能的影响相对较小,在运行时的开销仅会使程序速度减慢约 2 倍 ,而 Valgrind 则可能使程序减慢 10 倍之多。这使得开发者在使用 ASAN 进行内存检测时,不必花费过多时间等待程序运行结果,大大提高了调试效率。
ASAN 能够检测到多种类型的内存错误,除了内存泄漏,还包括释放后使用(悬空指针)、堆缓冲区溢出、堆栈缓冲区溢出、全局缓冲区溢出、在作用域之后使用、初始化顺序错误等 。它就像一个全能的内存问题探测器,能够全面地扫描程序中的内存隐患,为开发者提供更全面的内存错误信息。
(2)使用步骤
使用 AddressSanitizer 进行内存检测,步骤也相对简洁明了。首先是编译阶段,需要在编译命令中添加 “-fsanitize=address” 选项。例如,使用 GCC 编译 C++ 程序时,可以这样操作:
gcc -fsanitize=address -g -O0 -Wall test.c -o test
这里的 “-g” 选项用于生成调试信息,“-O0” 表示不进行优化,“-Wall” 用于开启所有常见的警告信息,这些选项与 Valgrind 编译时类似,都是为了确保在调试过程中能够获取更准确的信息。
编译完成后,直接运行生成的可执行文件即可。当程序运行过程中出现内存错误时,AddressSanitizer 会立即捕获并在控制台上输出详细的错误报告。例如,对于下面这段存在内存泄漏的代码:
#include
运行编译后的程序,ASAN 会输出类似如下的错误报告:
===================================================================1234==ERROR: LeakSanitizer: detected memory leaksDirect leak of 40 byte(s) in 1 object(s) allocated from: #0 0x7f919d0c8b97 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10eb97) #1 0x40059d in main /home/user/test.c:5SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).
从报告中可以清晰地看到,ASAN 准确地指出了内存泄漏的位置在 “test.c” 文件的第 5 行,泄漏的内存大小为 40 字节(因为分配了 10 个 int 类型的内存空间,每个 int 通常为 4 字节,共 40 字节),并且还给出了内存分配的调用栈信息,方便开发者快速定位问题根源。
Part6.其他内存泄露分析
其实上面的内存泄露是我们知道了具体的泄露的进程,然后再做详细分析。那么如果不知道哪里内存泄露了,有什么办法,可以通过分析meminfo文件,来观察泄露的类型。
[root@VM-0-2-centos test]# cat /proc/meminfoMemTotal: 1882008 kBMemFree: 752948 kBMemAvailable: 1610108 kBBuffers: 564900 kBCached: 399584 kBSwapCached: 0 kBActive: 808140 kBInactive: 220812 kBActive(anon): 64548 kBInactive(anon): 488 kBActive(file): 743592 kBInactive(file): 220324 kBUnevictable: 0 kBMlocked: 0 kBSwapTotal: 0 kBSwapFree: 0 kB....
meminfo文件是Linux系统中用于显示内存使用情况的详细信息文件,它位于/proc
目录下,提供了关于系统内存使用的全面信息。通过查看和分析meminfo文件的内容,可以了解系统的内存使用状况,包括总内存、空闲内存、缓存、交换分区等信息,这对于排查内存相关的问题非常有帮助。
meminfo文件包含的主要信息及其含义如下:
MemTotal:系统总内存大小。MemFree:系统空闲内存大小。MemAvailable:可用内存大小,包括空闲内存和缓存。Buffers:用于缓存数据的内存大小。Cached:用于缓存文件系统的内存大小。SwapCached:用于缓存交换分区的内存大小。Active 和 Inactive:分别表示活动和非活动内存大小,即正在使用或最近使用的内存和最近没有使用的内存。SwapTotal 和 SwapFree:交换分区总大小和空闲大小。Dirty 和 Writeback:等待写回到磁盘的内存大小和正在写回到磁盘的内存大小。AnonPages、Mapped、Shmem 等:分别表示用于匿名映射、已映射到文件的内存、共享内存大小等。Slab、SReclaimable、SUnreclaim 等:内核数据结构缓存的内存大小以及可回收和不可回收的Slab内存大小。KernelStack、PageTables 等:内核栈的内存大小和页面表的内存大小。CommitLimit 和 Committed_AS:可用内存可支持的最大内存大小和已分配的内存大小,包括内存和交换分区。VmallocTotal、VmallocUsed 等:虚拟内存总大小和已使用的虚拟内存大小。排查内存问题时,可以通过以下步骤进行:
首先,使用cat /proc/meminfo
命令查看meminfo文件的内容,了解系统的整体内存使用情况。分析MemTotal和MemFree的值,了解系统的总内存和可用空闲内存。注意MemAvailable的值,它表示应用程序可用的内存,与MemFree的区别在于MemAvailable考虑了Buffers和Cached的大小,这些通常在系统需要时可以被回收。检查SwapUsage(虽然meminfo文件中没有直接显示SwapUsage,但可以通过SwapTotal和SwapFree计算得出),如果Swap空间被大量使用,可能意味着物理内存不足。注意Active、Inactive、Dirty和Writeback等值,这些指标可以帮助你了解系统当前的内存使用模式和可能的性能瓶颈。如果发现某些特定类型的内存使用异常高(如AnonPages、Shmem等),可能需要进一步调查这些类型的内存使用情况,以确定是否存在内存泄漏或其他问题。使用其他工具如free
、vmstat
、top
或htop
等命令提供的信息与meminfo文件的内容进行对比,以获得更全面的系统内存使用情况视图。通过综合分析meminfo文件的内容和其他相关工具的输出,可以有效地排查和解决Linux系统中的内存相关问题
Part7.高内存占用:系统性能的隐形障碍
高内存占用就像是系统性能的隐形障碍,虽然不像内存泄漏那样直观,但同样会给系统带来严重的影响。当系统内存被大量占用时,就好比一个原本宽敞的房间堆满了杂物,活动空间变得狭小,行动也变得困难。
在嵌入式系统中,高内存占用可能会导致系统响应速度明显变慢。当系统需要处理新的任务或响应用户的操作时,由于内存中已经充满了各种数据和程序,没有足够的空闲内存来快速加载和运行新的任务,就像仓库里没有足够的空间存放新的货物,只能花费时间去整理和腾出空间,这就使得系统的响应变得迟缓。原本能够迅速执行的指令,现在可能需要等待较长时间才能完成,用户会明显感觉到设备的操作变得卡顿,例如无人机在执行复杂飞行指令时,反应变得不及时,工业控制设备对传感器数据的处理出现延迟等。
高内存占用还会极大地降低系统的多任务处理能力。在现代嵌入式应用中,很多设备需要同时运行多个任务,如无人机既要实时处理飞行姿态数据,又要传输高清图像、接收远程控制指令等。如果内存被某个或某些任务大量占用,其他任务就无法获得足够的内存资源来正常运行,就像多个工人在一个狭小的工作空间里工作,互相干扰,效率低下。这可能导致一些任务被迫暂停或中断,影响整个系统的功能完整性和稳定性,甚至可能引发任务之间的冲突,导致系统出现错误或异常行为。
高内存占用与内存泄漏之间也存在着紧密的关联。内存泄漏是导致高内存占用的一个重要原因,随着内存泄漏的不断发生,系统中被占用却无法释放的内存越来越多,直接导致了系统内存占用率的持续上升 。就像一个不断漏水的水桶,水不断地流进桶里却无法流出,桶里的水就会越来越满。而高内存占用又会加剧内存泄漏的影响,因为当系统内存紧张时,程序在分配和释放内存时更容易出现错误,进一步增加了内存泄漏的风险,形成一个恶性循环。例如,在内存紧张的情况下,程序可能会为了获取内存而采取一些不安全的内存分配方式,或者在释放内存时出现误判,导致本应释放的内存没有被释放,从而加重内存泄漏问题。
★排查手段一:top 与 htop 实时监控
在排查 Linux 内存泄漏与高占用问题时,top 和 htop 命令是我们最先会用到的 “侦察兵”,它们就像系统的实时监控仪表盘,能够让我们快速了解系统内存的使用状况,直观地发现那些占用内存过高的 “嫌疑进程”。
①top 命令:系统资源的实时监视器
top 命令是 Linux 系统中常用的性能分析工具,它能够实时显示系统中各个进程的资源占用状况 ,如同一个忙碌的指挥中心,时刻汇报着系统的 “健康状态”。在终端中输入 “top” 命令,即可打开这个实时监控界面,系统默认每 3 秒刷新一次信息 ,让你能够及时捕捉到系统状态的变化。
在 top 命令的输出界面中,头部区域是系统整体概览的展示区,这里包含了当前时间、系统已经运行的时长、登录用户数,以及系统负载等关键信息 ,就像汽车仪表盘上的时间、里程和负载指示灯,让你对系统的整体运行环境有一个初步了解。同时,这里还会显示 CPU 和内存的使用情况,包括总内存、已用内存、空闲内存等,让你一眼就能看出系统内存的 “库存” 状态。
主体部分则是以列表形式展示系统中的各个进程,每一行都代表着一个进程,详细列出了进程的各种信息,如进程 ID(PID),就像是进程的身份证号码,用于唯一标识每个进程;用户(USER),表明该进程是由哪个用户启动的;优先级(PR),反映了进程在系统资源分配中的优先程度;虚拟内存使用量(VIRT),表示进程占用的虚拟内存大小,包括进程使用的代码、数据和共享库等;
物理内存使用量(RES),即进程实际占用的物理内存大小;共享内存(SHR),是进程与其他进程共享的内存部分;状态(S),显示进程当前是正在运行(R)、休眠(S)、停止(T)还是处于僵尸状态(Z);CPU 使用率(% CPU),直观地展示了该进程占用 CPU 资源的百分比;内存使用率(% MEM),表示进程占用物理内存和总内存的比例;累计 CPU 时间(TIME+),记录了该进程从启动到当前累计使用的 CPU 时间;以及进程名称(COMMAND),让你清楚知道这个进程对应的是哪个程序或服务。
在实际使用 top 命令时,我们还可以通过一些选项和交互命令来更灵活地获取所需信息。比如,使用 “-d” 选项可以设置更新间隔时间,例如 “top -d 2” 表示每 2 秒刷新一次信息,就像你可以调整汽车仪表盘上某些数据的更新频率,以适应不同的观察需求。如果只想监控特定的进程 ID,可以使用 “-p” 选项,如 “top -p 1234”,只关注 PID 为 1234 的进程,这对于排查某个关键服务的内存使用情况非常有用。“-u” 选项则用于指定要监控的用户所属的进程,如 “top -u username”,方便查看特定用户相关进程的内存占用。
而在 top 命令的交互界面中,按下 “M” 键,就可以按内存使用率对进程进行排序,让占用内存较多的进程排在前面,快速定位到内存占用大户,就像在一个货物清单中,按照货物重量进行排序,找出最重的那些货物。按下 “P” 键则可以按 CPU 使用率排序,帮助我们发现占用 CPU 资源较多的进程。如果想要杀死某个进程,可以按下 “k” 键,然后输入要杀死的进程 ID;若要重新调整进程的优先级,按下 “r” 键,再输入进程 ID 和新的优先级值即可。
②htop 命令:更直观强大的监控利器
htop 命令是 top 命令的增强版本,它在功能和用户体验上都有了显著的提升,就像是在普通相机的基础上,增加了更多高级功能和更清晰的显示效果,让我们对系统内存状况的监控更加得心应手。
与 top 命令类似,在终端输入 “htop” 即可启动这个强大的监控工具。htop 的界面布局与 top 有相似之处,但更加美观和直观 。头部同样展示了系统整体概览信息,包括 CPU 使用率、内存使用率、交换空间使用率等,不过它是以彩色条形图的形式呈现,让你能更直观地感受到系统资源的使用比例,就像一个更生动的仪表盘,不同颜色的进度条代表着不同资源的使用情况。同时,这里也显示了系统时间、运行时间、登录用户数和系统负载等信息。
主体部分的进程列表也列出了与 top 命令类似的进程信息,但 htop 的显示更加清晰,并且支持鼠标操作,操作起来更加便捷 。例如,你可以直接用鼠标点击某一列的标题,对进程按照该列信息进行排序,而无需像 top 命令那样记住特定的按键操作。
htop 命令还提供了许多丰富的功能和交互命令。按下 “F2” 键可以进入设置菜单,在这里你可以根据自己的需求和喜好,自定义显示的列、颜色主题、排序方式等,就像给你的监控工具进行个性化定制,让它更符合你的使用习惯。按下 “F3” 键可以搜索进程,当系统中进程众多时,通过输入进程名称或关键字,就能快速定位到相关进程,节省查找时间。如果想要杀死某个进程,在 htop 中操作更加简单,直接选择要杀死的进程,然后按下 “F9” 键即可,无需手动输入进程 ID,这对于需要快速处理问题的场景非常实用。
使用 “+” 和 “-” 键可以轻松调整进程的优先级,选择要调整优先级的进程后,按 “+” 键提高优先级,按 “-” 键降低优先级,方便你根据实际情况优化系统资源分配。按下 “F5” 键可以切换到树状图模式,显示进程之间的父子关系,这对于分析复杂的进程结构和依赖关系非常有帮助,就像查看一个家族树,清晰地了解各个进程之间的传承和关联。按下 “F6” 键可以选择不同的排序方式,除了按 CPU 使用率和内存使用率排序外,还可以根据其他指标进行排序,满足不同的监控需求。
通过 top 和 htop 命令的实时监控,我们能够快速定位到高内存占用的进程,为进一步深入排查内存问题提供了重要线索。它们就像我们在排查内存泄漏与高占用问题道路上的得力助手,让我们在面对复杂的系统内存状况时,能够做到心中有数,有的放矢地进行后续的分析和处理。
★排查手段二:/proc 文件系统深度剖析
在 Linux 系统中,/proc 文件系统是一个非常强大且特殊的工具,它就像一个系统信息的宝藏库,为我们提供了丰富的系统运行时信息,其中与内存相关的文件,更是排查内存泄漏与高占用问题的关键线索来源。
①/proc/[PID]/status:进程状态与内存概况
在 /proc 文件系统中,每个正在运行的进程都有一个以其进程 ID(PID)命名的目录 ,如 /proc/1234,其中的 status 文件就像是这个进程的 “健康报告”,详细记录了进程的各种状态信息以及内存使用的概况。
打开 /proc/[PID]/status 文件,我们可以看到一系列关键信息。其中,“VmPeak” 表示进程虚拟内存使用量的峰值 ,它记录了进程在运行过程中曾经达到的最高虚拟内存占用情况,就像一个运动员在比赛中跳出的最高纪录,反映了进程在内存需求上的最大值。“VmSize” 代表当前进程虚拟内存的实际使用量 ,这是进程当前占用的虚拟内存大小,包括代码段、数据段、堆、栈以及共享库等所占用的虚拟内存空间,是我们了解进程当前内存 “胃口” 大小的重要指标。“VmLck” 表示被锁定的内存大小 ,当进程使用了 mlock () 函数等机制将部分内存锁定,使其不能被交换到磁盘时,这部分内存的大小就会记录在这里,被锁定的内存就像是被特殊标记的 “保险箱”,始终保留在物理内存中。
“VmHWM” 即 High Water Mark,是进程实际使用物理内存的峰值 ,它记录了进程在运行过程中曾经占用物理内存的最大值,反映了进程在物理内存使用上的最高水平。“VmRSS” 则是进程当前实际占用的物理内存大小 ,这是我们最关注的指标之一,它直接反映了进程当前实实在在占用的物理内存资源,就像一个房间里实际摆放的物品所占据的空间大小。
“VmData” 表示进程数据段的大小 ,包括已初始化的数据和未初始化的数据,这部分内存主要用于存储进程运行时的数据变量等信息。“VmStk” 代表进程用户态栈的大小 ,栈是一种后进先出的数据结构,用于函数调用、局部变量存储等,VmStk 记录了进程栈所占用的内存空间。“VmExe” 是进程代码段的大小 ,也就是进程可执行文件所占用的内存空间,这里存放着进程运行的指令代码。“VmLib” 表示进程使用的库映射到虚拟内存空间的大小 ,当进程依赖各种动态链接库时,这些库会被映射到进程的虚拟内存空间中,VmLib 记录了这部分库所占用的内存大小。
通过分析这些内存相关的字段,我们可以对进程的内存使用情况有一个全面的了解。例如,当我们发现 “VmRSS” 不断增长,而 “VmSize” 也同步增加,且没有明显的释放迹象时,就可能存在内存泄漏的隐患。这就好比一个人不断地往房间里搬东西,却从不清理,房间里的物品(内存占用)就会越来越多。而如果 “VmPeak” 与当前的 “VmSize” 相差很大,说明进程在运行过程中曾经有过较大的内存需求波动,这也可能是我们需要进一步关注的点,也许在某个特定的操作或时间段内,进程的内存使用出现了异常增长。
②/proc/[PID]/smaps:内存映射区域的详细剖析
/proc/[PID]/smaps 文件则像是一个放大镜,为我们提供了比 /proc/[PID]/status 文件更加详细的内存映射区域信息 ,让我们能够深入了解进程内存使用的每一个细节。
smaps 文件中,每一个内存映射区域都有详细的记录,包括该区域的起始地址和结束地址,以及一系列描述内存使用情况的字段。“Size” 表示该虚拟内存区域的大小 ,它明确了这个内存块在虚拟地址空间中的范围,就像划定了一块土地的边界。“Rss” 是实际分配给该段落物理页框的数量(单位 KB) ,也就是该内存区域当前实际占用的物理内存大小,它反映了进程在物理内存中实实在在占据的 “地盘”。“Pss(Proportional Set Size)” 表示共享页面按比例分摊后的大小 ,对于共享内存区域,Pss 能够更准确地反映该进程实际使用的内存份额。
当多个进程共享同一个动态链接库时,库所占用的内存会根据每个进程的使用情况按比例分摊,Pss 就是这个分摊后的大小,避免了在共享内存情况下单纯以 Rss 计算可能导致的内存使用统计偏差。“Shared_Clean” 和 “Shared_Dirty” 分别表示和其它进程共享的未修改的 page 的大小以及共享的被改写的 page 的大小 ,这两个字段帮助我们了解共享内存中数据的状态。如果 “Shared_Dirty” 不断增加,可能意味着多个进程对共享内存的频繁修改,这可能会带来数据一致性等问题,也可能影响内存的使用效率。
“Private_Clean” 和 “Private_Dirty” 则是未被改写的私有页面的大小和已被改写的私有页面的大小 ,用于描述进程私有的内存页面的状态。“Referenced” 表示已经被引用过的页面数目 ,它反映了内存页面的使用活跃度,被引用次数多的页面说明是进程运行过程中经常访问的数据或代码所在的页面。“Anonymous” 是不关联任何磁盘文件的匿名映射空间大小 ,例如通过 malloc () 函数分配的堆内存,通常就是匿名映射的,这个字段可以帮助我们了解进程中匿名内存的使用情况。“Swap” 表示当前已交换出去至 swap 分区中的数据量 ,当物理内存不足时,系统会将一些不常用的内存页面交换到磁盘的 swap 分区中,Swap 字段记录了这部分被交换出去的数据量。如果 Swap 的值持续增加,说明系统内存紧张,频繁进行内存交换,这会严重影响系统性能,因为磁盘的读写速度远远慢于内存。
在实际排查内存问题时,我们可以通过监控 smaps 文件中相关字段的变化来发现异常。比如,当某个进程的 “Rss” 持续上升,而 “Pss” 也同步增加,且 “Anonymous” 区域不断扩大,同时 “Swap” 也开始出现增长趋势,这很可能是该进程存在内存泄漏或者内存使用不合理的情况。我们可以进一步分析具体是哪些内存映射区域出现了异常,结合进程的功能和代码逻辑,定位到问题的根源。例如,如果发现某个动态库的内存映射区域中 “Shared_Dirty” 异常高,可能是该库在使用共享内存时存在频繁的写入操作,导致内存使用效率低下,甚至可能存在数据竞争等问题,需要进一步检查库的使用方式和相关代码。
通过深入分析 /proc/[PID]/status 和 /proc/[PID]/smaps 文件,我们能够获取到进程内存使用的详细信息,为排查 Linux 内存泄漏与高占用问题提供有力的支持,就像一位经验丰富的医生通过详细的检查报告,准确地诊断出病人身体的问题所在。
Part8.面试真题实战演练
为了让大家更清楚地了解在实际面试中如何运用这些排查方法,我们来模拟一个大疆面试中可能出现的场景。假设你遇到了一个多线程程序,在运行一段时间后,系统出现了内存泄漏和高内存占用的问题,导致系统性能严重下降。现在,面试官要求你找出问题所在并提出解决方案 。
(1)场景描述
这是一个基于 Linux 系统的多线程数据处理程序,用于实时处理传感器采集的数据。程序中有多个线程,分别负责数据采集、数据处理和结果存储。在程序运行初期,一切正常,但随着时间的推移,系统内存占用不断上升,最终导致系统响应迟缓,甚至出现死机现象。
(2)排查步骤
初步观察与进程定位:首先,使用 top 或 htop 命令,查看系统中各个进程的内存占用情况。通过这一步,我们发现数据处理线程所在的进程内存占用增长异常明显,确定了问题所在的进程,为后续的排查工作明确了方向。这就像是在一片森林中找到了冒烟的那片区域,知道了问题的大致范围。深入分析进程内存使用:进入 /proc/[PID]/status 文件,查看该进程的内存使用概况,发现 VmRSS 和 VmSize 持续增长,这进一步验证了内存泄漏的可能性。接着,查看 /proc/[PID]/smaps 文件,详细分析内存映射区域,发现某个动态分配的内存区域 Rss 不断增加,且没有释放的迹象,初步确定了内存泄漏的位置就在这个动态内存分配的部分。这就像在锁定的问题区域内,通过更细致的搜索,找到了具体的 “问题点”。利用工具精确检测:为了更准确地定位内存泄漏的具体代码行,使用 Valgrind 工具。在编译程序时,加上 “-g” 选项生成调试信息,然后执行 “valgrind --tool=memcheck --leak-check=full --show-reachable=yes./your-program” 命令。Valgrind 运行后,输出了详细的错误报告,指出了在数据处理线程中的某个函数里,存在内存分配后未释放的问题,就像用高精度的探测器,精准地找到了内存泄漏的 “漏洞”。分析多线程环境下的内存问题:由于这是一个多线程程序,线程之间的同步和资源竞争也可能导致内存问题。检查线程同步机制,发现线程之间在访问共享内存时,没有正确地使用互斥锁,导致内存访问混乱,进一步加剧了内存问题。这就像是在一个多人合作的项目中,发现了人员之间的协作流程出现了混乱,影响了整体的工作效果。解决问题与验证:根据排查结果,在数据处理线程的对应函数中,添加正确的内存释放代码,并确保线程同步机制的正确性,使用互斥锁来保护共享内存的访问。修改代码后,重新编译并运行程序,再次使用 top、htop 以及 Valgrind 等工具进行检查,发现内存占用稳定,没有再出现泄漏的情况,系统性能也恢复正常,这表明问题已得到有效解决 。这就像是给出现故障的机器换上了新的零件,并进行了全面的测试,确保机器能够正常运转。通过这个实战演练,我们可以看到,在面对 Linux 内存泄漏与高占用问题时,只要掌握了正确的排查方法和工具,按照合理的步骤进行分析,就能够准确地找到问题的根源,并提出有效的解决方案,这也是在大疆嵌入式面试中,面试官希望看到的解决问题的能力和思路。
免责声明
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
最新文章
CDimension横空出世:立志从底层重建芯片技术栈
随着人工智能、机器人、量子计算与边缘计算等新兴应用对算力提出更高要求,传统硅基架构在能效、封装碎片化及带宽瓶颈等方面的物理极限日益显现。CDimension 正以一种根本性不同的技术路径,力图突破这
小米发布REDMI 15C:百元神机来袭,配置亮眼性价比高
小米近日在多个海外市场推出旗下最新入门级智能手机REDMI 15C,起售价为119美元,折合人民币约849元。作为小米旗下价格最为亲民的手机系列,该产品线历代机型均以高性价比著称,被许多用户称为百元
安富利:30载深耕中国市场,长期主义构筑可持续发展护城河
在安富利,我们始终坚信,ESG(环境、社会、公司治理)是驱动企业实现长期可持续发展的核心竞争力。 管理大师德鲁克曾说:“企业是社会的器官,任何企业得以生存,都是因为它满足了社会某一方面的需要,实现了
务必自查:Linux 爆出本地双杀提权漏洞,从 SSH 到 Root 只需一步?
这两个漏洞组合形成了从普通账号到 root 的完整提权链条,运维工程师们不能掉以轻心,引起足够重视,记得做好提前备份。 今天分享两个6月17号Qualys研究团队披露了公布的Linux漏洞。1 漏
国产动作游戏跻身日本销量榜前十,多款新作表现亮眼
跻身日本销量榜前十,多款新作表现亮眼 " >上周日本地区游戏销量排行榜正式公布,其中一款国产动作游戏失落之魂成功进入前十,引发广泛关注。在本期榜单前十名中,共有七款新作首次进入排行榜,若按不同平台合并
热门推荐
热门教程
更多- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程



















