深入理解Linux内存管理与调试工具
在Linux系统中,有效的内存管理和调试对于系统的稳定运行至关重要。本文将详细介绍内存管理的相关概念、常用的内存使用查看工具,以及如何识别和处理内存泄漏问题。
1. 内存使用指标:RSS
RSS(Resident Set Size)在
ps
命令中显示为
RSS
,在
top
命令中显示为
RES
,它表示映射到物理内存页面的内存总和。虽然RSS更接近进程实际使用的内存量,但由于存在共享页面,将所有进程的RSS相加会高估实际使用的内存。
2. 使用
top
和
ps
命令查看内存使用
BusyBox版本的
top
和
ps
命令提供的信息有限,建议使用
procps
包中的完整版本。
-
ps
命令
:使用
-Aly
选项或自定义格式(包含
vsz
和
rss
)可以显示VSS(VSZ)和RSS。示例命令如下:
ps -eo pid,tid,class,rtprio,stat,vsz,rss,comm
输出示例:
PID TID CLS RTPRIO STAT VSZ RSS COMMAND
1 1 TS -Ss 4496 2652 systemd
205 205 TS -Ss 4076 1296 systemd-journal
228 228 TS -Ss 2524 1396 udevd
-
top命令 :显示系统的整体内存使用情况和每个进程的内存使用摘要。示例输出如下:
top - 21:17:52 up 10:04, 1 user, load average: 0.00, 0.01, 0.05
Tasks: 96 total, 1 running, 95 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.7 us, 2.2 sy, 0.0 ni, 95.9 id, 0.0 wa, 0.0 hi
KiB Mem: 509016 total, 278524 used, 230492 free, 25572 buffers
KiB Swap: 0 total, 0 used, 0 free, 170920 cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
595 root 20 0 64920 9.8m 4048 S 0.0 2.0 0:01.09 node
866 root 20 0 28892 9152 3660 S 0.2 1.8 0:36.38 Xorg
这些简单的命令可以让我们大致了解内存使用情况,当某个进程的RSS持续增加时,可能意味着存在内存泄漏。但它们在精确测量内存使用方面并不准确。
3. 使用
smem
工具
2009年,Matt Mackall引入了两个新的内存使用指标:唯一集大小(USS)和比例集大小(PSS)。
-
USS(Unique Set Size)
:指进程独有的、已提交到物理内存的内存量,即进程终止时会释放的内存。
-
PSS(Proportional Set Size)
:将共享页面的内存按比例分配给所有映射了这些页面的进程。例如,一个12页的库代码被6个进程共享,每个进程的PSS为2页。将所有进程的PSS相加,可得到这些进程实际使用的内存量。
PSS信息可以在
/proc/<PID>/smaps
文件中找到,该文件包含了
/proc/<PID>/maps
中每个映射的详细信息。示例如下:
b6e6d000-b6f45000 r-xp 00000000 b3:02 2444 /lib/libc-2.13.so
Size: 864 kB
Rss: 264 kB
Pss: 6 kB
Shared_Clean: 264 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 264 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
VmFlags: rd ex mr mw me
可以看到,虽然RSS为264 KiB,但由于该页面被多个进程共享,PSS仅为6 KiB。
smem
工具可以从
smaps
文件中收集信息,并以多种方式呈现,如饼图或柱状图。其项目页面为https://www.selenic.com/smem/ ,大多数桌面发行版都提供该工具的软件包。由于
smem
是用Python编写的,在嵌入式目标系统上安装需要Python环境。为了解决这个问题,可以使用
smemcap
工具,它可以捕获目标系统
/proc
目录的状态并保存为TAR文件,然后在主机上进行分析。
运行
smem
的示例命令如下:
smem -t
输出示例:
PID User Command Swap USS PSS RSS
610 0 /sbin/agetty -s ttyO0 11 0 128 149 720
1236 0 /sbin/agetty -s ttyGS0 1 0 128 149 720
609 0 /sbin/agetty tty1 38400 0 144 163 724
578 0 /usr/sbin/acpid 0 140 173 680
819 0 /usr/sbin/cron 0 188 201 704
634 103 avahi-daemon: chroot hel 0 112 205 500
980 0 /usr/sbin/udhcpd -S /etc 0 196 205 568
...
836 0 /usr/bin/X :0 -auth /var 0 7172 7746 9212
583 0 /usr/bin/node autorun.js 0 8772 9043 10076
1089 1000 /usr/bin/python -O /usr/ 0 9600 11264 16388
--------------------------------------------------------------
53 6 0 65820 78251 146544
从输出的最后一行可以看出,在这个例子中,总PSS约为RSS的一半。
如果目标系统上没有或不想安装Python,可以使用
smemcap
捕获状态:
smemcap > smem-bbb-cap.tar
然后将TAR文件复制到主机上,使用
smem -S
命令进行分析:
smem -t -S smem-bbb-cap.tar
输出与直接在目标系统上运行
smem
相同。
4. 其他相关工具
-
ps_mem:可以显示PSS信息,格式更简单,同样用Python编写,代码地址为https://github.com/pixelb/ps_mem 。 -
procrank:Android系统中的工具,可显示每个进程的USS和PSS摘要,稍作修改后可用于嵌入式Linux,代码地址为https://github.com/csimmonds/procrank_linux 。
5. 识别内存泄漏
内存泄漏是指内存被分配后,在不再需要时没有被释放。在嵌入式系统中,由于内存有限且系统通常长时间运行,内存泄漏问题更为突出。
当运行
free
或
top
命令,发现即使清空缓存后,可用内存仍持续减少时,就可能存在内存泄漏。通过查看每个进程的USS和RSS,可以找出可能的罪魁祸首。
以下介绍两种常用的内存泄漏检测工具:
-
mtrace
:
mtrace
是
glibc
的一个组件,用于跟踪
malloc
、
free
等相关函数的调用,并在程序退出时识别未释放的内存区域。使用步骤如下:
1. 在程序中调用
mtrace()
函数开始跟踪。
2. 在运行时,将跟踪信息的保存路径写入
MALLOC_TRACE
环境变量。
3. 使用
mtrace
命令查看跟踪信息。
示例代码如下:
#include <mcheck.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int j;
mtrace();
for (j = 0; j < 2; j++)
malloc(100); /* Never freed:a memory leak */
calloc(16, 16); /* Never freed:a memory leak */
exit(EXIT_SUCCESS);
}
运行程序并查看跟踪信息的命令如下:
export MALLOC_TRACE=mtrace.log
./mtrace-example
mtrace mtrace-example mtrace.log
输出示例:
Memory not freed:
-----------------
Address Size Caller
0x0000000001479460 0x64 at /home/chris/mtrace-example.c:11
0x00000000014794d0 0x64 at /home/chris/mtrace-example.c:11
0x0000000001479540 0x100 at /home/chris/mtrace-example.c:15
需要注意的是,
mtrace
只能在程序终止后才能提供内存泄漏信息。
-
Valgrind:Valgrind是一个强大的内存调试工具,可以发现内存泄漏和其他内存问题。其优点是无需重新编译要检查的程序和库,但如果程序在编译时使用了-g选项,包含调试符号表,效果会更好。Valgrind通过在模拟环境中运行程序并在不同点捕获执行来工作,但这也导致程序运行速度大幅降低,不太适用于有实时约束的测试。
Valgrind
包含多个诊断工具:
-
memcheck
:默认工具,用于检测内存泄漏和内存的一般误用。
-
cachegrind
:计算处理器缓存命中率。
-
callgrind
:计算每个函数调用的成本。
-
helgrind
:检测
Pthread
API的误用,包括潜在的死锁和竞态条件。
-
DRD
:另一个
Pthread
分析工具。
-
massif
:分析堆和栈的使用情况。
使用
Valgrind
的
memcheck
工具检测内存泄漏的示例命令如下:
valgrind --leak-check=full ./mtrace-example
输出示例:
==17235== Memcheck, a memory error detector
==17235== Copyright (C) 2002-2013, and GNU GPL'd, by Julian
Seward et al.==17235==Using Valgrind-3.10.0.SVN and LibVEX;
rerun with -h for copyright info
==17235== Command: ./mtrace-example
==17235==
==17235==
==17235== HEAP SUMMARY:
==17235== in use at exit: 456 bytes in 3 blocks
==17235== total heap usage: 3 allocs, 0 frees, 456 bytes
allocated
==17235==
==17235== 200 bytes in 2 blocks are definitely lost in loss
record
1 of 2==17235== at 0x4C2AB80: malloc (in /usr/lib/valgrind/
vgpreload_memcheck-linux.so)
==17235== by 0x4005FA: main (mtrace-example.c:12)
==17235==
==17235== 256 bytes in 1 blocks are definitely lost in loss
record
2 of 2==17235== at 0x4C2CC70: calloc (in /usr/lib/valgrind/
vgpreload memcheck-linux so)
==17235== by 0x400613: main (mtrace-example.c:14)
==17235==
==17235== LEAK SUMMARY:
==17235== definitely lost: 456 bytes in 3 blocks
==17235== indirectly lost: 0 bytes in 0 blocks
==17235== possibly lost: 0 bytes in 0 blocks
==17235== still reachable: 0 bytes in 0 blocks
==17235== suppressed: 0 bytes in 0 blocks
==17235==
==17235== For counts of detected and suppressed errors, rerun
with: -v==17235== ERROR SUMMARY: 2 errors from 2 contexts
(suppressed: 0 from 0)
从输出可以看出,在
mtrace-example.c
文件的第12行和第14行分别发现了一个
malloc
和
calloc
的内存泄漏。
6. 内存不足情况处理
Linux系统的标准内存分配策略是过度提交(over-commit),即内核允许应用程序分配的内存超过物理内存总量。大多数情况下,这种策略可以正常工作,因为应用程序通常会请求比实际需要更多的内存。但在某些特定工作负载下,可能会出现多个进程同时尝试使用已分配的内存,导致内存不足(Out of Memory,OOM)的情况。
在
/proc/sys/vm/overcommit_memory
文件中可以设置内核的内存分配策略:
-
0
:启发式过度提交,默认选项,适用于大多数情况。
-
1
:总是过度提交,不进行检查,仅适用于处理大型稀疏数组的程序,在嵌入式系统中很少使用。
-
2
:总是检查,从不过度提交,适用于对内存不足有担忧的关键应用。当分配的内存超过提交限制(交换空间大小加上总内存乘以过度提交比例)时,分配将失败。过度提交比例由
/proc/sys/vm/overcommit_ratio
文件控制,默认值为50%。
示例:假设系统有512 MB的RAM,将过度提交比例设置为25%:
echo 25 > /proc/sys/vm/overcommit_ratio
grep -e MemTotal -e CommitLimit /proc/meminfo
输出示例:
MemTotal: 509016 kB
CommitLimit: 127252 kB
由于没有交换空间,提交限制为MemTotal的25%。
/proc/meminfo
文件中的
Committed_AS
变量表示到目前为止所有已分配内存所需的总内存量。例如:
grep -e MemTotal -e Committed_AS /proc/meminfo
输出示例:
MemTotal: 509016 kB
Committed_AS: 741364 kB
这意味着内核已经承诺的内存超过了可用内存。在这种情况下,将
overcommit_memory
设置为2会导致所有分配失败,需要增加内存或减少运行的进程数量。
当出现OOM情况时,OOM杀手(oom-killer)会使用启发式方法为每个进程计算一个0到1000的坏分数(badness score),并终止分数最高的进程,直到问题解决。可以通过
echo f > /proc/sysrq-trigger
命令强制触发OOM事件。
还可以通过修改
/proc/<PID>/oom_score_adj
文件中的值来影响进程的坏分数:
-
-1000
:表示该进程的坏分数永远不会大于0,不会被OOM杀手终止。
-
+1000
:表示该进程的坏分数总是大于1000,会被OOM杀手优先终止。
7. 总结
在虚拟内存系统中,精确计算每个字节的内存使用是不可能的。但可以使用
free
命令来大致了解可用内存的总量(不包括缓冲区和缓存占用的内存)。通过长时间监控不同工作负载下的内存使用情况,可以确保系统内存使用在合理范围内。
当需要调整内存使用或识别意外的内存分配源时,可以参考
/proc
目录下的
meminfo
、
slabinfo
和
vmallocinfo
等文件。对于用户空间的内存测量,PSS是最准确的指标,可以使用
smem
等工具查看。在内存调试方面,可以使用
mtrace
等简单的跟踪工具,或者使用强大的
Valgrind
的
memcheck
工具。
如果担心OOM情况的影响,可以通过
/proc/sys/vm/overcommit_memory
文件调整内存分配策略,并通过
oom_score_adj
参数控制进程被终止的可能性。
8. 调试工具:GDB简介
除了内存管理,调试也是软件开发过程中不可或缺的环节。GNU Project Debugger(GDB)是一个强大而灵活的调试工具,可以用于调试应用程序、分析程序崩溃后的核心文件(core files),甚至可以单步执行内核代码。
GDB可以帮助开发者完成以下任务:
- 调试应用程序,观察程序的执行过程。
- 分析核心文件,找出程序崩溃的原因。
- 单步执行内核代码,深入了解内核的运行机制。
在后续的文章中,我们将详细介绍GDB的使用方法,包括如何准备调试环境、调试应用程序、处理核心文件等内容。
通过合理使用这些内存管理和调试工具,可以提高Linux系统的稳定性和性能,确保软件的质量。希望本文能对您有所帮助。
深入理解Linux内存管理与调试工具
9. 调试前的准备工作
在使用GDB进行调试之前,需要做一些准备工作,以确保调试过程顺利进行。
-
编译时添加调试信息
:在编译程序时,使用
-g
选项可以将调试符号信息嵌入到可执行文件中。例如:
gcc -g -o my_program my_program.c
这样,GDB就可以根据这些调试符号找到源代码和变量的位置。
-
生成核心文件
:当程序崩溃时,系统可以生成核心文件(core file),记录程序崩溃时的内存状态。可以通过修改
ulimit
命令来设置核心文件的大小限制,允许生成核心文件。例如:
ulimit -c unlimited
设置完成后,当程序崩溃时,会在当前目录下生成一个名为
core
或
core.<PID>
的文件。
10. 调试应用程序
使用GDB调试应用程序是其最常见的用途。下面是一个简单的调试流程:
1.
启动GDB
:在终端中输入
gdb <可执行文件>
来启动GDB并加载要调试的程序。例如:
gdb my_program
-
设置断点
:断点是程序执行过程中的暂停点,方便我们观察程序的状态。可以使用
break命令设置断点。例如,在main函数的第10行设置断点:
(gdb) break main:10
-
运行程序
:使用
run命令开始运行程序,程序会在遇到断点时暂停。
(gdb) run
-
单步执行
:在程序暂停后,可以使用
next命令单步执行下一行代码,step命令会进入函数内部执行。例如:
(gdb) next
(gdb) step
-
查看变量值
:使用
print命令可以查看变量的值。例如,查看变量x的值:
(gdb) print x
-
继续执行
:使用
continue命令可以让程序继续执行,直到下一个断点或程序结束。
(gdb) continue
以下是一个简单的示例代码和调试过程:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int x = 5;
int y = 3;
int result = add(x, y);
printf("The result is: %d\n", result);
return 0;
}
调试过程如下:
(gdb) break add
(gdb) run
(gdb) step
(gdb) print a
(gdb) print b
(gdb) continue
11. 即时调试(Just-in-time Debugging)
即时调试允许在程序运行过程中动态地附加GDB进行调试。当程序出现问题时,可以使用
gdb -p <PID>
命令将GDB附加到正在运行的进程上。例如:
gdb -p 1234
这样就可以在不停止程序的情况下对其进行调试,观察程序的状态和变量的值。
12. 调试多进程和多线程程序
在Linux系统中,很多程序会使用多进程或多线程来提高性能。GDB也支持对多进程和多线程程序的调试。
-
调试多进程程序
:当程序使用
fork()
创建子进程时,GDB默认只调试父进程。可以使用
set follow-fork-mode
命令来设置调试模式。例如,调试子进程:
(gdb) set follow-fork-mode child
-
调试多线程程序
:使用
info threads命令可以查看当前程序中的所有线程。使用thread <线程编号>命令可以切换到指定的线程进行调试。例如:
(gdb) info threads
(gdb) thread 2
13. 核心文件分析
核心文件是程序崩溃时的内存快照,可以帮助我们找出程序崩溃的原因。使用GDB分析核心文件的步骤如下:
1. 启动GDB并加载可执行文件和核心文件:
gdb my_program core
-
使用
bt(backtrace)命令查看程序崩溃时的调用栈,了解程序是在哪个函数中崩溃的。
(gdb) bt
-
根据调用栈信息,使用
frame <帧编号>命令切换到指定的调用帧,查看该帧中的变量值和代码。
(gdb) frame 2
(gdb) print x
14. GDB用户界面
GDB提供了多种用户界面,方便不同用户的使用习惯。
-
命令行界面
:默认的界面,通过输入命令来控制调试过程。适合熟悉命令行操作的用户。
-
TUI(Text User Interface)
:使用
gdb -tui
命令可以启动TUI界面,它将源代码、汇编代码和调试信息以分屏的方式显示,方便查看。
-
图形界面
:如DDD(Data Display Debugger),提供了图形化的界面,通过鼠标操作来控制调试过程,适合初学者。
15. 调试内核代码
GDB还可以用于调试Linux内核代码,这对于深入了解内核的运行机制非常有帮助。调试内核代码的步骤如下:
1.
编译内核时添加调试信息
:在编译内核时,使用
CONFIG_DEBUG_INFO
选项将调试符号信息嵌入到内核镜像中。
2.
启动内核并挂载调试文件系统
:使用
kgdboc
或
kdb
等工具启动内核,并挂载
debugfs
文件系统。
3.
启动GDB并连接到内核
:在主机上启动GDB,加载内核镜像和调试符号文件,然后通过串口或网络连接到正在运行的内核。例如:
gdb vmlinux
(gdb) target remote /dev/ttyS0
-
在内核中设置断点并进行调试:使用
break命令在内核代码中设置断点,然后使用continue命令让内核继续运行,遇到断点时进行调试。
16. 总结与展望
通过本文的介绍,我们了解了Linux系统中内存管理的相关概念和常用工具,以及GDB调试工具的使用方法。合理使用这些工具可以帮助我们更好地管理内存、识别和处理内存泄漏问题,以及调试程序中的错误。
在未来的开发过程中,我们可以根据具体的需求选择合适的工具和方法,不断优化系统的性能和稳定性。同时,随着技术的不断发展,调试工具也会不断更新和完善,为开发者提供更强大的功能和更便捷的使用体验。
希望本文能为您在Linux系统的内存管理和调试方面提供一些帮助,让您在开发过程中更加得心应手。
以下是一个简单的流程图,展示了使用GDB调试应用程序的基本流程:
graph TD;
A[启动GDB并加载程序] --> B[设置断点];
B --> C[运行程序];
C --> D{是否遇到断点};
D -- 是 --> E[单步执行或查看变量];
E --> F[继续执行];
F --> D;
D -- 否 --> G[程序结束];
| 工具名称 | 功能描述 | 使用场景 |
|---|---|---|
top
| 实时显示系统中各个进程的资源占用情况 | 监控系统整体性能 |
ps
| 显示当前系统中的进程信息 | 查看特定进程的详细信息 |
smem
| 提供更准确的内存使用指标(USS、PSS) | 精确分析进程内存使用 |
mtrace
| 跟踪内存分配和释放,检测内存泄漏 | 调试内存泄漏问题 |
Valgrind
| 强大的内存调试工具,可检测多种内存问题 | 全面调试内存相关问题 |
GDB
| 通用的调试工具,支持多种调试场景 | 调试应用程序、内核代码等 |
超级会员免费看
2万+

被折叠的 条评论
为什么被折叠?



