进程内存检查

1.进程内存映射文件smaps

在内核的数据结构中,进程、进程使用内存、虚拟内存块和一个二进制程序文件的对应关系图如下。

vm_area

查看/proc/${PID}/smaps,可以得到每一个vm_area_node的详细信息。

下图是一个具体的vm_area_node信息。

smaps

1.1 两种映射

下面两种映射的介绍,是为了下一节解释各字段含义做准备。

  • 文件映射

就是存储介质(比如:磁盘)中的数据通过文件系统映射到内存再通过文件映射映射到虚拟空间,这样,用户就可以在用户空间通过 open, read, write 等函数区操作文件内容。代码中函数open(), read(), write(), close(), mmap(fd,…)… 操作的虚拟地址都属于文件映射。

  • 匿名映射

就是用户空间要求内核分配一定的物理内存来存储数据,这部分内存不属于任何文件。内核就使用匿名映射将内存中的某段物理地址与用户空间一一映射,这样用户就可用直接操作虚拟地址来范围这段物理内存。比如使用malloc(), mmap(NULL,…)申请内存。

1.2 各字段含义

  • 第一行

smaps_head

  1. 08048000-080bc000: 该虚拟内存段的开始和结束位置
  2. r-xp:内存段的权限,分别是可读、可写、可运行、私有或共享,最后一位p代表私有,s代表共享(如共享的内存, shm). 如果有”w”,表示是库的数据区.
  3. 00000000: 虚拟内存段起始地址在对应的映射文件中以页为单位的偏移量, 对匿名映射,它等于0或者vm_start/PAGE_SIZE
  4. 03:02: 文件的主设备号和次设备号。 对有名映射来说,是映射的文件所在设备的设备号 对匿名映射来说,因为没有文件在磁盘上,所以没有设备号,始终为00:00。
  5. 13130: 被映射到虚拟内存的文件的索引节点号,通过该节点可以找到对应的文件, 对匿名映射来说,因为没有文件在磁盘上,所以为0
  6. /bin/bash: 被映射到虚拟内存的文件名称。后面带(deleted)的是内存数据,可以被销毁。 对有名映射来说,是映射的文件名。 对匿名映射来说,是此段虚拟内存在进程中的角色。[stack]表示在进程中作为栈使用,[heap]表示堆。其余情况比如mmap(NULL, ….)则无显示。
  • Size

    虚拟内存空间大小。但是这个内存值不一定是物理内存实际分配的大小,因为在用户态上,虚拟内存总是延迟分配的。这个值计算也非常简单,就是该VMA的开 始位置减结束位置。

    延迟分配:就是当进程申请内存的时候,Linux会给他先分配页,但是并不会区建立页与页框的映射关系,也就是并不会分配物理内存,而当真正使用的时候,就会产生一个缺页异常,硬件跳转page fault处理程序执行,在其中分配物理内存,然后修改页表(创建页表项)。异常处理完毕,返回程序用户态,继续执行。

  • Rss resident set size

    实际分配的内存,这部分物理内存已经分配,不需要缺页中断就可以使用的。但可能是和其他进程共享的

    这里有一个公式计算Rss:

    Rss=Shared_Clean+Shared_Dirty+Private_Clean+Private_Dirty
    
  • Shared_Clean Shared_Dirty Private_Clean Private_Dirty

    share/private:表示该页面是共享还是私有。

    dirty/clean: 表示该页面是否被修改过,如果修改过(dirty),在页面被淘汰的时候,就会把该脏页面回写到交换分区(换出,swap out)。有 一个标志位用于表示页面是否dirty。

    share/private_dirty/clean 计算逻辑:

    查看该page的引用数,如果引用>1,则归为shared,如果是1,则归为private,再查看该page的flag,是否标记为_PAGE_DIRTY,如果不是,则认为干净的

  • Pss proportional set size

    平摊计算后的实际物理使用内存(有些内存会和其他进程共享,例如mmap进来的)。实际上包含上面private_clean+private_dirty,和按比例均分的shared_clean、shared_dirty。

    举个计算Pss的例子:

    如果进程A有x个private_clean页面,有y个private_dirty页面,有z个shared_clean仅和进程B共享,有h个shared_dirty页面和进程B、C共享。那么进程A的Pss为:x + y + z/2 + h/3

  • Referenced

    当前页面被标记为已引用或者包含匿名映射(The amount of memory currently marked as referenced or a mapping associated with a file may contain anonymous pages)。在Linux内存管理的页面替换算法中,当某个页面被访问后,Referenced标志被设置,如果该标志设置了,就不能将该页移出。

  • Anonymous

    匿名映射的物理内存,这部分内存不是来自于文件。

  • VmFlags

    vm_area的各种属性,具体如下:

    vmflags

1.3 不同变量的位置

一个库映射到内存, 一般分为代码段、数据段和只读数据段

  • r- --p: so中的字符串常数
  • rw--p: so中的全局变量,静态变量
  • r- -xp: so的代码段,常量
  • - ---p: 表示该 VMA 是私有的,不可执行,且不可读写. 这通常用于保护敏感数据或代码,防止其被修改或执行

下面这段代码展示了不同变量的存储位置:

variable

2.free 命令

free 命令用于显示系统的内存状态,包括物理内存、交换内存(swap)和内核缓冲区内存。详细输出如下:

variable

  • Mem 行(第二行)显示了内存的使用情况。
  • Swap行(第三行)显示了交换空间的使用情况。

  • total: 表示系统总的可用物理内存交换空间大小。
  • used : 表示已经被使用的物理内存交换空间
  • free : 表示还有多少物理内存交换空间可用使用。
  • shared: 显示被共享使用的物理内存大小。
  • buff/cache: 显示被 buffer 和 cache 使用的物理内存大小。
  • available: 显示还可以被应用程序使用的物理内存大小。

2.1 buffer与cache

  • buffer: 缓

    CPU 在进行一系列操作时,先在内存的一块区域进行,一系列操作完成后,再一次性把该内存区域提交给外部设备,来对这个区域操作。

    比如写一堆数据给硬盘,就先写到内存的一块区域,写好后一次写回到硬盘。又比如读数据,先在内存划出一块区域,让硬盘控制器写数据到这块区域,写好后,CPU 直接访问该区域得到数据。这个内存区域就叫buffer

    缓冲区是内存或存储的一部分,用于在等待从输入设备传输到输出设备时存放项目。

    操作系统通常在打印文档时使用缓冲区。这个过程称为排队(spooling),它将要打印的文档发送到缓冲区,而不是立即发送到打印机。如果打印机没有自己的内部存储器,或者内存已满,操作系统的缓冲区会保存等待打印的信息,同时打印机以自己的速度从缓冲区打印。

    通过将文档排队到缓冲区,处理器可以继续解释和执行指令,同时打印机进行打印。这使用户可以在打印机打印时继续在计算机上进行其他任务。多个打印作业在缓冲区中排队(发音为“Q”)。一个名为打印排队程序(print spooler)的程序拦截操作系统中要打印的文档,并将其放入队列中

  • cache:缓

    CPU 要访问一块数据时,首先访问内存的某个区域,看是否有该数据的缓存,有则直接访问,没有则访问它的来源地。 CPU 利用内存或高速缓存对数据的再备份,为以后的再次访问提供方便

    缓存如今的大多数计算机通过缓存(发音为“cash”)来提高处理速度。

    缓存有两种主要类型:内存缓存和磁盘缓存。让我们详细了解一下内存缓存。

    L1 缓存:

    L1 缓存直接内置在处理器芯片中。
    它通常容量很小,范围从 8 KB 到 128 KB。 L1 缓存存储经常使用的指令和数据,以便快速访问。

    L2 缓存:

    L2 缓存比 L1 缓存稍慢,但容量更大。 它的大小范围从 64 KB 到 16 MB。 一些现代处理器包括高级传输缓存,这是一种直接内置在处理器芯片上的 L2 缓存类型。 使用高级传输缓存的处理器的性能比不使用它的处理器要快得多。 现今的个人计算机通常具有 512 KB 到 12 MB 的高级传输缓存。

    缓存通过存储经常使用的指令和数据来显著加快处理时间。

    当处理器需要一条指令或数据时,它按照以下顺序搜索内存:L1 缓存,然后是 L2 缓存,然后是 RAM。

    如果所需信息在内存中找不到,处理器必须搜索速度较慢的存储介质,例如硬盘或光盘。

2.2. 手动释放缓存

  1. 首先,使用sync命令将未写入磁盘的数据同步到磁盘,以确保文件系统的完整性。
  2. 然后,通过设置/proc/sys/vm/drop_caches来释放内存缓存:
    • echo 1 > /proc/sys/vm/drop_caches:释放页缓存。
    • echo 2 > /proc/sys/vm/drop_caches:释放 dentries 和 inodes。
    • echo 3 > /proc/sys/vm/drop_caches:释放所有缓存。

3.mtrace

mtrace 是 Linux 系统内核自带的一个内存追踪函数。它会在每个内存申请函数(malloc、realloc、calloc)的位置记录下信息,并在每个内存释放的位置记录下 free 的内存信息。其中包含有内存申请的地址、内存申请的大小、释放内存的地址、释放内存的大小。

具体来说,mtrace 函数的作用如下:

  • 安装钩子函数,用于跟踪内存分配和释放。
  • 记录有关内存分配和释放的跟踪信息。
  • 可以用于发现程序中的内存泄漏和试图释放未分配内存的情况。

使用方式:

  1. 在代码中包含 <mcheck.h> 头文件。
  2. 在程序启动时调用 mtrace() 函数,开启内存分配和释放跟踪。
  3. 程序结束时,可以调用 muntrace() 函数关闭内存分配和释放跟踪。
  4. 运行mtrace脚本,分析跟踪日志,生成报告。

请注意,mtrace 的跟踪输出通常是文本形式,不一定易于人类阅读。GNU C 库提供了一个 Perl 脚本 mtrace,用于解析跟踪日志并生成人类可读的输出。为了获得最佳效果,建议编译时启用调试,以便在可执行文件中记录行号信息。不过,mtrace 的跟踪会带来性能损耗.如果 MALLOC_TRACE 没有指向有效且可写的路径, 则mtrace不会记录信息。

mtrace mtrace

4.strace与ltrace

ltrace 用于跟踪程序的库函数调用,而 strace 则用于跟踪系统调用。 它们都基于 ptrace 系统调用,但跟踪库函数和跟踪系统调用之间存在差异。 通过它们,我们也可以对应用的内存申请、释放进行跟踪。

ltrace 的工作原理:

  • ptrace 附加到正在运行的程序。
  • 定位程序的 PLT。
  • 使用 PTRACE_POKETEXT 设置软件断点(int $3 指令)覆盖库函数的 PLT 中的汇编 trampoline。
  • 恢复程序执行。

strace 应该也是相类似的工作原理

4.1 strace

strace 是一个强大的 Linux 命令,用于诊断、调试和统计。它允许您跟踪正在运行的程序的系统调用和接收的信号。下面是一些关于 strace 的参数使用方法。

  • -c:统计每个系统调用的执行时间、次数和错误次数。 示例:打印执行 uptime 时系统调用的时间、次数和错误次数:
    strace -c uptime
    
  • -f:跟踪子进程,这些子进程是由当前跟踪的进程创建的。
  • -i:在系统调用时打印指令指针。
  • -t:跟踪的每一行都以时间为前缀。
  • -tt:如果给出两次,则打印时间将包括微秒。
  • -ttt:如果给定三次,则打印时间将包括微秒,并且前导部分将打印为自启动以来的秒数。
  • -T:显示花费在系统调用上的时间。

限定表达式:

  • -e trace=set:仅跟踪指定的系统调用集。例如,trace=open,close,read,write 表示仅跟踪这四个系统调用。
  • -e trace=file:跟踪所有以文件名作为参数的系统调用。示例:打印执行 ls 时与文件有关的系统调用:
    strace -e trace=file ls
    
  • -e trace=process:跟踪涉及进程管理的所有系统调用。
  • -e trace=network:跟踪所有与网络相关的系统调用。
  • -e trace=signal:跟踪所有与信号相关的系统调用。
  • -e trace=ipc:跟踪所有与 IPC 相关的系统调用。
  • -e trace=memory:跟踪所有与 momory 相关的系统调用。

其他参数:

  • -o 文件名:将跟踪输出写入文件而不是 stderr。
  • -p pid:使用进程 ID pid 附加到该进程并开始跟踪

下面是运行 strace ls 的输出 strace

4.2 ltrace

ltrace 是一个用于跟踪程序库调用的 Linux 工具。它可以拦截并记录被执行进程调用的动态库函数,以及该进程接收到的信号。此外,ltrace 还可以拦截并打印程序执行的系统调用。

常用参数和示例: -c:统计每个系统调用的执行时间、次数和错误次数。 示例:打印执行 uptime 时系统调用的时间、次数和错误次数:

ltrace -c uptime
  • -f:跟踪子进程,这些子进程是由当前跟踪的进程创建的。
  • -i:在系统调用时打印指令指针。
  • -t:跟踪的每一行都以时间为前缀。
  • -tt:如果给出两次,则打印时间将包括微秒。
  • -ttt:如果给定三次,则打印时间将包括微秒,并且前导部分将打印为自启动以来的秒数。
  • -T:显示花费在系统调用上的时间。

限定表达式:

  • -e trace=set:仅跟踪指定的系统调用集。例如,trace=open,close,read,write 表示仅跟踪这四个系统调用。
  • -e trace=file:跟踪所有以文件名作为参数的系统调用。示例:打印执行 ls 时与文件有关的系统调用:
    ltrace -e trace=file ls
    
  • -e trace=process:跟踪涉及进程管理的所有系统调用。
  • -e trace=network:跟踪所有与网络相关的系统调用。
  • -e trace=signal:跟踪所有与信号相关的系统调用。
  • -e trace=ipc:跟踪所有与 IPC 相关的系统调用。

其他参数:

  • -o 文件名:将跟踪输出写入文件而不是 stderr。
  • -p pid:使用进程 ID pid 附加到该进程并开始跟踪。

下面是运行 ltrace ls 的输出

ltrace

results matching ""

    No results matching ""