50、深入理解Linux内存管理与调试工具

深入理解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
  1. 设置断点 :断点是程序执行过程中的暂停点,方便我们观察程序的状态。可以使用 break 命令设置断点。例如,在 main 函数的第10行设置断点:
(gdb) break main:10
  1. 运行程序 :使用 run 命令开始运行程序,程序会在遇到断点时暂停。
(gdb) run
  1. 单步执行 :在程序暂停后,可以使用 next 命令单步执行下一行代码, step 命令会进入函数内部执行。例如:
(gdb) next
(gdb) step
  1. 查看变量值 :使用 print 命令可以查看变量的值。例如,查看变量 x 的值:
(gdb) print x
  1. 继续执行 :使用 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
  1. 使用 bt (backtrace)命令查看程序崩溃时的调用栈,了解程序是在哪个函数中崩溃的。
(gdb) bt
  1. 根据调用栈信息,使用 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
  1. 在内核中设置断点并进行调试:使用 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 通用的调试工具,支持多种调试场景 调试应用程序、内核代码等
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值