Contents

了解Linux/proc/id/maps文件

 1. 概述

在本教程中,我们将了解如何通过读取*/proc/id/maps*文件的输出来分析 Linux 进程的内存使用情况。我们将从解释 虚拟内存的概念开始。接下来,我们将描述 一个进程的虚拟地址空间它的结构以及它周围的权限。最后,我们将讨论如何解释*/proc/id/maps*的输出 以查看特定进程的虚拟地址空间。

2. 什么是虚拟内存?

虚拟内存允许每个进程占用机器上所有可用的物理内存。换句话说,每个进程都好像它是操作系统中运行的唯一进程一样。这种方法有很多好处。 例如,它提供了更好的开发者体验,因为它极大地简化了新的内存请求。此外,它提高了安全性,因为它隔离了进程,因此它们不会相互干扰。最后,它提高了性能,因为当发生错误时,它只影响单个进程,而不会增加其余进程的开销。

但是,如果虚拟内存总量超过可用的实际内存(包括交换空间 ),则需要特别小心。为了解决这个问题,Linux 引入了内存不足杀手

3. 进程的虚拟地址空间(VAS)

进程的虚拟地址空间 (VAS) 由该进程可以引用的所有可用内存地址组成。这是虚拟内存,大约等于机器上安装的总物理内存。

VAS 分为两个区域:

3.1. 细分类型

用户**VAS 段,也称为映射,是连续的内存块,**其内容取决于段类型。让我们看看不同的类型:

  • 包含可执行代码的代码段(或文本段)
  • 包含过程数据(例如变量和常量)的数据段。它们进一步细分为初始化数据段、未初始化数据段
  • 堆栈段可以动态扩展,包含函数参数和局部函数变量
  • 共享库段,其中包含进程使用的链接(共享)库

3.2. 段权限

这些段映射有一组权限,这些权限控制允许对它们执行哪些操作。这些权限通常称为模式,如下所示:

  • 只读 (r) 表示该段是可读的,因此所有段通常都具有该模式
  • 读写(w)表示该段是可读可写的,以允许数据修改
  • execute(x) 表示该段包含可执行代码
  • private (p),这意味着该段是私有的,因此只能从该进程中看到
  • shared(s),这意味着多个(至少 2 个)进程共享该段

4. 使用*/proc/id/maps*检查进程的 VAS

现在让我们应用我们在前面几节中学到的知识来理解流程的 VAS。假设我们知道进程的PID ,我们可以利用procfs 来查看 VAS 的样子。为此,我们可以从/proc/pid/maps*文件*中读取。这是使用cat命令的示例输出:

$ cat /proc/self/maps
559b8c418000-559b8c41a000 r--p 00000000 08:30 1708                       /usr/bin/cat
559b8c41a000-559b8c41f000 r-xp 00002000 08:30 1708                       /usr/bin/cat
559b8c41f000-559b8c422000 r--p 00007000 08:30 1708                       /usr/bin/cat
559b8c422000-559b8c423000 r--p 00009000 08:30 1708                       /usr/bin/cat
559b8c423000-559b8c424000 rw-p 0000a000 08:30 1708                       /usr/bin/cat
559b8c5d1000-559b8c5f2000 rw-p 00000000 00:00 0                          [heap]
7faa72001000-7faa72023000 rw-p 00000000 00:00 0
7faa72023000-7faa72055000 r--p 00000000 08:30 3023                       /usr/lib/locale/C.UTF-8/LC_CTYPE
7faa72055000-7faa72056000 r--p 00000000 08:30 3030                       /usr/lib/locale/C.UTF-8/LC_NUMERIC
7faa72056000-7faa72057000 r--p 00000000 08:30 3033                       /usr/lib/locale/C.UTF-8/LC_TIME
7faa72057000-7faa721ca000 r--p 00000000 08:30 3022                       /usr/lib/locale/C.UTF-8/LC_COLLATE
7faa721ca000-7faa721cb000 r--p 00000000 08:30 3028                       /usr/lib/locale/C.UTF-8/LC_MONETARY
7faa721cb000-7faa721cc000 r--p 00000000 08:30 3027                       /usr/lib/locale/C.UTF-8/LC_MESSAGES/SYS_LC_MESSAGES
7faa721cc000-7faa721cd000 r--p 00000000 08:30 3031                       /usr/lib/locale/C.UTF-8/LC_PAPER
7faa721cd000-7faa721ce000 r--p 00000000 08:30 3029                       /usr/lib/locale/C.UTF-8/LC_NAME
7faa721ce000-7faa721cf000 r--p 00000000 08:30 3021                       /usr/lib/locale/C.UTF-8/LC_ADDRESS
7faa721cf000-7faa724b5000 r--p 00000000 08:30 3034                       /usr/lib/locale/locale-archive
7faa724b5000-7faa724da000 r--p 00000000 08:30 11971                      /usr/lib/x86_64-linux-gnu/libc-2.31.so
7faa724da000-7faa72652000 r-xp 00025000 08:30 11971                      /usr/lib/x86_64-linux-gnu/libc-2.31.so
7faa72652000-7faa7269c000 r--p 0019d000 08:30 11971                      /usr/lib/x86_64-linux-gnu/libc-2.31.so
7faa7269c000-7faa7269d000 ---p 001e7000 08:30 11971                      /usr/lib/x86_64-linux-gnu/libc-2.31.so
7faa7269d000-7faa726a0000 r--p 001e7000 08:30 11971                      /usr/lib/x86_64-linux-gnu/libc-2.31.so
7faa726a0000-7faa726a3000 rw-p 001ea000 08:30 11971                      /usr/lib/x86_64-linux-gnu/libc-2.31.so
7faa726a3000-7faa726a9000 rw-p 00000000 00:00 0
7faa726a9000-7faa726aa000 r--p 00000000 08:30 3032                       /usr/lib/locale/C.UTF-8/LC_TELEPHONE
7faa726aa000-7faa726ab000 r--p 00000000 08:30 3025                       /usr/lib/locale/C.UTF-8/LC_MEASUREMENT
7faa726ab000-7faa726b2000 r--s 00000000 08:30 11818                      /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
7faa726b2000-7faa726b3000 r--p 00000000 08:30 11854                      /usr/lib/x86_64-linux-gnu/ld-2.31.so
7faa726b3000-7faa726d6000 r-xp 00001000 08:30 11854                      /usr/lib/x86_64-linux-gnu/ld-2.31.so
7faa726d6000-7faa726de000 r--p 00024000 08:30 11854                      /usr/lib/x86_64-linux-gnu/ld-2.31.so
7faa726de000-7faa726df000 r--p 00000000 08:30 3024                       /usr/lib/locale/C.UTF-8/LC_IDENTIFICATION
7faa726df000-7faa726e0000 r--p 0002c000 08:30 11854                      /usr/lib/x86_64-linux-gnu/ld-2.31.so
7faa726e0000-7faa726e1000 rw-p 0002d000 08:30 11854                      /usr/lib/x86_64-linux-gnu/ld-2.31.so
7faa726e1000-7faa726e2000 rw-p 00000000 00:00 0
7ffeb2f53000-7ffeb2f74000 rw-p 00000000 00:00 0                          [stack]
7ffeb2f99000-7ffeb2f9d000 r--p 00000000 00:00 0                          [vvar]
7ffeb2f9d000-7ffeb2f9e000 r-xp 00000000 00:00 0                          [vdso]

4.1.了解输出

在上面的输出中,我们可以看到命令行实用程序cat的用户 VAS 。我们可以看到总共有六列。让我们看看第一行并描述每一列的内容:

<address start>-<address end>  <mode>  <offset>   <major id:minor id>   <inode id>   <file path>  
 559b8c418000-559b8c41a000      r--p    00000000          08:30               1708     /usr/bin/cat
  • 地址开始 - 地址结束是该映射的开始和结束地址。请注意,整个 输出是根据这些地址从低到高排序的。
  • 模式(权限)指定此映射上可用的操作以及它是私有的还是共享的。
  • offset 是映射文件中的起始偏移量(以字节为单位)。这仅对文件映射有意义。例如,堆栈或堆映射是不是文件的映射示例,在这些情况下,偏移量为 0。在上面的示例中,映射是*/usr/bin/cat*文件,偏移量为 0 .
  • 主要:次要 ids以主要和次要 id 的形式**表示映射文件所在的设备。**在上面的示例中,08:30 表示具有根文件系统的硬盘驱动器的主要和次要 ID。对于非文件映射,此列显示 00:00
  • 映射文件的 inode id(同样,这仅对文件映射有效)。索引节点 是包含核心文件系统相关元数据的数据结构。当涉及到非文件映射时,此字段设置为 0。在我们的示例中,此 id 为 1708。
  • 映射文件的文件路径。如果这不是文件映射,则该字段为空。

4.2. 逐行检查输出

我们现在准备好逐行检查输出。为了简短起见,我们将只为我们在输出中看到的每种不同类型的段描述一个代表性示例。让我们直接进入:

首先,我们看到***/usr/bin/cat*文件的只读私有映射**:

559b8c418000-559b8c41a000 r--p 00000000 08:30 1708 /usr/bin/cat

这是一个私有数据段,很可能包含全局变量或常量,因此是只读的。 其次,我们看到了一个可读和可执行的映射,同样是*/usr/bin/cat:*

559b8c41a000-559b8c41f000 r-xp 00002000 08:30 1708 /usr/bin/cat

鉴于它是可执行的,这个段就是代码段。 第三,我们看到一个可读可写的映射

559b8c423000-559b8c424000 rw-p 0000a000 08:30 1708 /usr/bin/cat

同样,这是一个数据段,很可能用于存储和更新变量的进程。 此外,我们可以看到 对应于堆段的映射

559b8c5d1000-559b8c5f2000 rw-p 00000000 00:00 0 [heap]

请注意,这不是文件映射,因此主要、次要和 inode id 均为 0。 接下来,我们看到一个 只读的私有映射

7faa72023000-7faa72055000 r--p 00000000 08:30 3023 /usr/lib/locale/C.UTF-8/LC_CTYPE

这是一个(只读)数据段,包含与语言环境相关的 常量。 最后,我们可以看到 libc 的只读数据段和代码段

7faa724b5000-7faa724da000 r--p 00000000 08:30 11971 /usr/lib/x86_64-linux-gnu/libc-2.31.so 
7faa724da000-7faa72652000 r-xp 00025000 08:30 11971 /usr/lib/x86_64-linux-gnu/libc-2.31.so 
7faa72652000-7faa7269c000 r--p 0019d000 08:30 11971 /usr/lib/x86_64-linux-gnu/libc-2.31.so 
7faa7269c000-7faa7269d000 ---p 001e7000 08:30 11971 /usr/lib/x86_64-linux-gnu/libc-2.31.so 
7faa7269d000-7faa726a0000 r--p 001e7000 08:30 11971 /usr/lib/x86_64-linux-gnu/libc-2.31.so 
7faa726a0000-7faa726a3000 rw-p 001ea000 08:30 11971 /usr/lib/x86_64-linux-gnu/libc-2.31.so

这是 cat 进程使用的共享库。