如何在 Linux 上使用 pmap 命令

已发表: 2022-06-25
显示 bash 提示符的 Linux 笔记本电脑
fatmawati achmad zaenuri/Shutterstock.com

找出一个 Linux 进程使用了​​多少 RAM 并不是一件简单的事情——尤其是在需要考虑共享内存的时候。 值得庆幸的是, pmap命令可以帮助您理解这一切。

内存映射

在现代操作系统上,每个进程都存在于自己分配的内存区域或分配空间中。 分配区域的边界不直接映射到物理硬件地址。 操作系统为每个进程创建一个虚拟内存空间,并充当将虚拟内存映射到物理内存的抽象层。

内核为每个进程维护一个转换表,并由 CPU 访问。 当内核更改在特定 CPU 内核上运行的进程时,它会更新将进程和 CPU 内核联系在一起的转换表。

抽象的好处

这个计划有好处。 对于用户空间中的每个进程,内存的使用在某种程度上被封装和沙盒化。 进程仅根据虚拟内存地址“看到”内存。 这意味着它只能使用操作系统提供的内存。 除非它可以访问某些共享内存,否则它既不知道也无法访问分配给其他进程的内存。

Linux 上的 Swappiness 是什么? (以及如何改变它)
相关什么是 Linux 上的 Swappiness? (以及如何改变它)

将基于硬件的物理内存抽象为虚拟内存地址,让内核可以更改某些虚拟内存映射到的物理地址。 它可以通过更改虚拟内存区域指向的实际地址来将内存交换到磁盘。 它还可以推迟提供物理内存,直到实际需要。

只要读取或写入内存的请求在请求时得到服务,内核就可以自由地在它认为合适的时候调整映射表。

按需内存

映射表和“RAM on demand”的概念开启了共享内存的可能性。 内核将尝试避免多次将相同的东西加载到内存中。 例如,它将一个共享库加载到内存中一次,并将其映射到需要使用它的不同进程。 每个进程都有自己唯一的共享库地址,但它们都指向同一个实际位置。

如果内存的共享区域是可写的,内核使用一种称为写时复制的方案。 如果一个进程写入共享内存,而共享该内存的其他进程不应该看到更改,则在写入请求时创建共享内存的副本。

初学者极客:如何创建和使用虚拟机
相关初学者极客:如何创建和使用虚拟机

2009 年 12 月发布的 Linux 内核 2.6.32 为 Linux 提供了一个名为“Kernel SamePage Merging”的功能。 这意味着 Linux 可以检测不同地址空间中相同的数据区域。 想象一下在一台计算机上运行一系列虚拟机,并且这些虚拟机都运行相同的操作系统。 使用共享内存模型和写时复制,主机上的开销可以大大减少。

所有这些都使得 Linux 中的内存处理变得复杂且尽可能优化。 但是这种复杂性使得很难查看一个进程并知道它的内存使用情况到底是什么。

pmap 实用程序

内核通过“/proc”系统信息伪文件系统中的两个伪文件公开了它对 RAM 所做的很多事情。 每个进程有两个文件,以每个进程的进程 ID 或PID命名:“/proc/maps”和“/proc//smaps”。

pmap工具从这些文件中读取信息并在终端窗口中显示结果。 很明显,每当我们使用pmap时,我们都需要提供我们感兴趣的进程的 PID。

查找进程 ID

有几种方法可以找到进程的 PID。 这是我们将在示例中使用的一个简单程序的源代码。 它是用 C 语言编写的。它所做的只是向终端窗口打印一条消息并等待用户按下“Enter”键。

 #include <stdio.h>

int main(int argc, char *argv[])
{
  printf("How-To Geek 测试程序。");
  获取(标准输入);
} // main 结束

使用gcc编译器将该程序编译为名为pm的可执行文件:

 gcc -o 下午 pm.c 

编译示例程序

因为程序会等待用户点击“Enter”,所以它会一直运行,只要我们愿意。

 。/下午

运行示例程序

程序启动,打印消息,并等待击键。 我们现在可以搜索它的 PID。 ps命令列出正在运行的进程。 -e (显示所有进程)选项使ps列出每个进程。 我们将通过grep管道输出并过滤掉名称中包含“pm”的条目。

 ps -e | grep 下午

使用 grep 查找进程 ID

这列出了名称中任何位置带有“pm”的所有条目。

我们可以使用pidof命令更具体。 我们在命令行中为pidof指定我们感兴趣的进程的名称,它会尝试找到匹配项。 如果找到匹配项, pidof会打印匹配进程的 PID。

 pidof 下午

使用 pidof 查找进程 ID

当您知道进程名称时, pidof方法更简洁,但即使只知道部分进程名称, ps方法也可以工作。

使用 pmap

随着我们的测试程序运行,一旦我们确定了它的 PID,我们就可以像这样使用 pmap:

 地图 40919 

在示例程序上运行 pmap

为我们列出了进程的内存映射。

标准 pmap 输出

这是命令的完整输出:

 40919:./下午
000056059f06c000 4K r---- 下午
000056059f06d000 4K rx--下午
000056059f06e000 4K r---- 下午
000056059f06f000 4K r---- 下午
000056059f070000 4K rw--- 下午
000056059fc39000 132K rw--- [匿名]
00007f97a3edb000 8K rw--- [匿名]
00007f97a3edd000 160K r---- libc.so.6
00007f97a3f05000 1616K rx--libc.so.6
00007f97a4099000 352K r---- libc.so.6
00007f97a40f1000 4K ----- libc.so.6
00007f97a40f2000 16K r---- libc.so.6
00007f97a40f6000 8K rw--- libc.so.6
00007f97a40f8000 60K rw--- [匿名]
00007f97a4116000 4K r---- ld-linux-x86-64.so.2
00007f97a4117000 160K rx--ld-linux-x86-64.so.2
00007f97a413f000 40K r---- ld-linux-x86-64.so.2
00007f97a4149000 8K r---- ld-linux-x86-64.so.2
00007f97a414b000 8K rw--- ld-linux-x86-64.so.2
00007ffca0e7e000 132K rw--- [堆栈]
00007ffca0fe1000 16K r---- [匿名]
00007ffca0fe5000 8K rx-- [匿名]
ffffffffff600000 4K --x-- [匿名]
总计 2756K

第一行是进程名称和它的 PID。 其他每一行显示一个映射的内存地址,以及该地址的内存量,以千字节表示。 每行接下来的五个字符称为虚拟内存权限。 有效权限是:

  • r :映射的内存可以被进程读取。
  • w :映射的内存可以被进程写入。
  • x :进程可以执行映射内存中包含的任何指令。
  • s :映射内存是共享的,对共享内存所做的更改对共享内存的所有进程都是可见的。
  • R :没有为这个映射内存预留交换空间。

每行的最后信息是映射源的名称。 这可以是进程名称、库名称或系统名称,例如堆栈或堆。

扩展显示

-x (扩展)选项提供了两个额外的列。

 pmap -x 40919 

将 -X 扩展选项与 pmap 一起使用

列有标题。 我们已经看到了“地址”、“千字节”、“模式”和“映射”列。 新列称为“RSS”和“Dirty”。

pmap 扩展输出

这是完整的输出:

 40919:./下午
地址千字节 RSS 脏模式映射
000056059f06c000 4 4 0 r---- 下午
000056059f06d000 4 4 0 rx--下午
000056059f06e000 4 4 0 r---- 下午
000056059f06f000 4 4 4 r---- 下午
000056059f070000 4 4 4 rw--- 下午
000056059fc39000 132 4 4 rw--- [匿名]
00007f97a3edb000 8 4 4 rw--- [匿名]
00007f97a3edd000 160 160 0 r---- libc.so.6
00007f97a3f05000 1616 788 0 rx--libc.so.6
00007f97a4099000 352 64 0 r---- libc.so.6
00007f97a40f1000 4 0 0 ----- libc.so.6
00007f97a40f2000 16 16 16 r---- libc.so.6
00007f97a40f6000 8 8 8 rw--- libc.so.6
00007f97a40f8000 60 28 28 rw--- [匿名]
00007f97a4116000 4 4 0 r---- ld-linux-x86-64.so.2
00007f97a4117000 160 160 0 rx-- ld-linux-x86-64.so.2
00007f97a413f000 40 40 0 r---- ld-linux-x86-64.so.2
00007f97a4149000 8 8 8 r---- ld-linux-x86-64.so.2
00007f97a414b000 8 8 8 rw--- ld-linux-x86-64.so.2
00007ffca0e7e000 132 12 12 rw--- [堆栈]
00007ffca0fe1000 16 0 0 r---- [匿名]
00007ffca0fe5000 8 4 0 rx-- [匿名]
ffffffffff600000 4 0 0 --x-- [匿名]
---------------- -------- ------- -------- 
总 kB 2756 1328 96
  • RSS :这是驻留集的大小。 也就是说,当前在 RAM 中且未换出的内存量。
  • Dirty :自从进程和映射开始以来,“Dirty”内存已被更改。

向我展示一切

-X (甚至超过扩展)在输出中添加了额外的列。 注意大写的“X”。 另一个名为-XX的选项(甚至超过-X )向您展示了pmap可以从内核获得的所有内容。 由于-X-XX的子集,我们将描述-XX的输出。

 pmap -XX 40919 

在 pmap 中使用 -XX show me everything 选项

输出在终端窗口中可怕地环绕,几乎无法辨认。 这是完整的输出:

 40919:./下午
         地址 Perm Offset 设备 Inode 大小 KernelPageSize MMUPageSize Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty 引用匿名 LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping
    56059f06c000 r--p 00000000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm
    56059f06d000 r-xp 00001000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd pm
    56059f06e000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm
    56059f06f000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd pm
    56059f070000 rw-p 00003000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd pm
    56059fc39000 rw-p 00000000 00:00 0 132 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd [堆]
    7f97a3edb000 rw-p 00000000 00:00 0 8 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd 
    7f97a3edd000 r--p 00000000 08:03 264328 160 4 4 160 4 160 0 0 0 160 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6
    7f97a3f05000 r-xp 00028000 08:03 264328 1616 4 4 788 32 788 0 0 0 788 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me sd libc.so.6
    7f97a4099000 r--p 001bc000 08:03 264328 352 4 4 64 1 64 0 0 0 64 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6
    7f97a40f1000 ---p 00214000 08:03 264328 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 mr mw me sd libc.so.6
    7f97a40f2000 r--p 00214000 08:03 264328 16 4 4 16 16 0 0 0 16 16 16 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac sd libc.so.6
    7f97a40f6000 rw-p 00218000 08:03 264328 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd libc.so.6
    7f97a40f8000 rw-p 00000000 00:00 0 60 4 4 28 28 0 0 0 28 28 28 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd 
    7f97a4116000 r--p 00000000 08:03 264305 4 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2
    7f97a4117000 r-xp 00001000 08:03 264305 160 4 4 160 11 160 0 0 0 160 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd ld-linux-x86-64.so.2
    7f97a413f000 r--p 00029000 08:03 264305 40 4 4 40 1 40 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2
    7f97a4149000 r--p 00032000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd ld-linux-x86-64.so.2
    7f97a414b000 rw-p 00034000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd ld-linux-x86-64.so.2
    7ffca0e7e000 rw-p 00000000 00:00 0 132 4 4 12 12 0 0 0 12 12 12 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me gd ac [堆栈]
    7ffca0fe1000 r--p 00000000 00:00 0 16 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 rd mr pf io de dd sd [vvar]
    7ffca0fe5000 r-xp 00000000 00:00 0 8 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me de sd [vdso]
ffffffffff600000 --xp 00000000 00:00 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ex [vsyscall]
                                             ==== ============================================== ========== ========================================= ===== ============================================== === ============== ================================== ========== 
                                             2756 92 92 1328 157 1220 0 12 96 1328 96 0 0 0 0 0 0 0 0 0 0 KB 

这里有很多信息。 这是列的内容:

  • 地址:此映射的起始地址。 这使用虚拟内存寻址。
  • Perm :内存的权限。
  • Offset :如果内存是基于文件的,则此映射在文件内的偏移量。
  • Device :Linux 设备号,以主要和次要数字给出。 您可以通过运行lsblk命令查看计算机上的设备号。
  • Inode :与映射关联的文件的 inode。 例如,在我们的示例中,这可能是保存有关 pm 程序信息的 inode。
  • Size :内存映射区域的大小。
  • KernelPageSize :内核使用的页面大小。
  • MMUPageSize :内存管理单元使用的页面大小。
  • Rss :这是驻留集的大小。 也就是说,当前在 RAM 中且未换出的内存量。
  • Pss :这是比例份额大小。 这是添加到的私有共享大小(共享大小除以共享数量。)
  • Shared_Clean :与其他进程共享的内存量,自映射创建以来更改。 请注意,即使内存是可共享的,如果它实际上没有被共享,它仍然被认为是私有内存。
  • Shared_Dirty :与其他进程共享的内存量,自映射创建以来已更改。
  • Private_Clean :自映射创建以来与其他进程共享的私有内存量。
  • Private_Dirty :自映射创建以来已更改的私有内存量。
  • Referenced :当前标记为已引用或已访问的内存量。
  • 匿名:没有设备可换出的内存。 也就是说,它不是文件支持的。
  • LazyFree :被标记为MADV_FREE的页面。 这些页面已被标记为可以释放和回收,即使它们可能有不成文的更改。 但是,如果在内存映射上设置MADV_FREE之后发生后续更改,则MADV_FREE标志将被删除,并且在写入更改之前不会回收页面。
  • AnonHugePages :这些是非文件支持的“巨大”内存页面(大于 4 KB)。
  • ShmemPmdMapped :与大页面关联的共享内存。 它们也可能被完全驻留在内存中的文件系统使用。
  • FilePmdMapped :页面中间目录是内核可用的分页方案之一。 这是 PMD 条目指向的文件支持页面的数量。
  • Shared_Hugetlb :转换后备表或 TLB 是内存缓存,用于优化访问用户空间内存位置所花费的时间。 这个数字是与共享大内存页面相关联的 TLB 中使用的 RAM 量。
  • Private_Hugetlb :这个数字是与私有大内存页面相关联的 TLB 中使用的 RAM 量。
  • 交换:正在使用的交换量。
  • SwapPss交换比例份额大小。 这是由添加到(共享大小除以共享数)的已交换专用内存页组成的交换量。
  • Locked :可以锁定内存映射以防止操作系统调出堆或堆外内存。
  • THPeligible :这是一个标志,指示映射是否有资格分配透明大页面。 1 表示正确,0 表示错误。 透明大页面是一种内存管理系统,可减少在具有大量 RAM 的计算机上查找 TLB 页面的开销。
  • VmFlags :请参阅下面的标志列表。
  • 映射:映射源的名称。 这可以是进程名称、库名称或系统名称,例如堆栈或堆。

VmFlags - 虚拟内存标志 - 将是以下列表的子集。

  • rd :可读。
  • wr :可写。
  • 例如:可执行。
  • sh :共享。
  • 先生:可以阅读。
  • mw : 可以写。
  • :可以执行。
  • 女士:可以分享。
  • gd :堆栈段向下增长。
  • pf :纯页框号范围。 页框号是物理内存页的列表。
  • dw :禁用对映射文件的写入。
  • lo :页面被锁定在内存中。
  • io : 内存映射 I/O 区域。
  • sr :提供顺序读取建议(由madvise()函数提供。)
  • rr :提供随机阅读建议。
  • dc :如果进程被分叉,则不要复制此内存区域。
  • de :不要在重新映射时扩展此内存区域。
  • ac :区域负责。
  • nr :不为该区域保留交换空间。
  • ht :区域使用巨大的 TLB 页面。
  • sf :同步页面错误。
  • ar :特定于架构的标志。
  • wf :如果进程被分叉,则擦除此内存区域。
  • dd :不要在核心转储中包含此内存区域。
  • sd :软脏标志。
  • mm :混合地图区域。
  • hg :巨大的页面建议标志。
  • nh :没有大页面建议标志。
  • mg :可合并的建议标志。
  • bt :ARM64 偏置温度不稳定保护页面。
  • mt :启用 ARM64 内存标记扩展标记。
  • um : Userfaultfd 缺少跟踪。
  • uw : Userfaultfd 写入保护跟踪。

内存管理很复杂

并且从数据表向后工作以了解实际发生的事情是困难的。 但至少pmap为您提供了完整的图片,因此您有最好的机会弄清楚您需要知道什么。

有趣的是,我们的示例程序编译为 16 KB 的二进制可执行文件,但它使用(或共享)了大约 2756 KB 的内存,这几乎完全归功于运行时库。

最后一个巧妙的技巧是,您可以同时使用pmappidof命令,将查找进程的 PID 并将其传递给pmap的操作组合成一个命令:

 pmap $(pidof pm)