如何在 Linux 上使用 pmap 命令

x33g5p2x  于2022-04-27 转载在 Linux  
字(8.0k)|赞(0)|评价(0)|浏览(437)

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

内存映射

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

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

抽象的好处

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

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

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

按需内存

映射表和“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.

查找进程 ID

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

#include <stdio.h>

int main(int argc, char *argv[])
{
  printf("How-To Geek test program.");
  getc(stdin);
} // end of main

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

gcc -o pm pm.c

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

./pm

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

ps -e | grep pm

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

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

pidof pm

当你知道进程的名称时,该pidof方法更简洁,但ps即使只知道部分进程名称,该方法也将起作用。

使用 pmap

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

pmap 40919

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

这是命令的完整输出:

40919: ./pm
000056059f06c000      4K r---- pm
000056059f06d000      4K r-x-- pm
000056059f06e000      4K r---- pm
000056059f06f000      4K r---- pm
000056059f070000      4K rw--- pm
000056059fc39000    132K rw--- [ anon ]
00007f97a3edb000      8K rw--- [ anon ]
00007f97a3edd000    160K r---- libc.so.6
00007f97a3f05000   1616K r-x-- 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--- [ anon ]
00007f97a4116000      4K r---- ld-linux-x86-64.so.2
00007f97a4117000    160K r-x-- 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--- [ stack ]
00007ffca0fe1000     16K r---- [ anon ]
00007ffca0fe5000      8K r-x-- [ anon ]
ffffffffff600000      4K --x-- [ anon ]
total              2756K

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

  • r:映射的内存可以被进程读取。
  • w:映射的内存可以被进程写入。
  • x:进程可以执行映射内存中包含的任何指令。
  • s:映射内存是共享的,对共享内存所做的更改对共享内存的所有进程都是可见的。
  • R:没有为这个映射内存预留交换空间。
    每行的最后信息是映射源的名称。这可以是进程名称、库名称或系统名称,例如堆栈或堆。

扩展显示

( -xextended) 选项提供了两个额外的列。

pmap -x 40919

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

这是完整的输出:

40919: ./pm
Address          Kbytes   RSS Dirty Mode  Mapping
000056059f06c000      4     4     0 r---- pm
000056059f06d000      4     4     0 r-x-- pm
000056059f06e000      4     4     0 r---- pm
000056059f06f000      4     4     4 r---- pm
000056059f070000      4     4     4 rw--- pm
000056059fc39000    132     4     4 rw--- [ anon ]
00007f97a3edb000      8     4     4 rw--- [ anon ]
00007f97a3edd000    160   160     0 r---- libc.so.6
00007f97a3f05000   1616   788     0 r-x-- 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--- [ anon ]
00007f97a4116000      4     4     0 r---- ld-linux-x86-64.so.2
00007f97a4117000    160   160     0 r-x-- 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--- [ stack ]
00007ffca0fe1000     16     0     0 r---- [ anon ]
00007ffca0fe5000      8     4     0 r-x-- [ anon ]
ffffffffff600000      4     0     0 --x-- [ anon ]
---------------- ------- ------- ------- 
total kB           2756  1328    96
---------------- ------- ------- ------- 
总 kB 2756 1328 96
  • RSS:这是 驻留集的大小。也就是说,当前在 RAM 中且未换出的内存量。
  • Dirty:自从进程和映射开始以来,“Dirty”内存已被更改。

向我展示一切

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

pmap -XX 40919

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

40919:./下午
         地址 Perm Offset Device Inode Size 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]
                                             ==== ============== =========== ==== === ============ ============ ============= ============= ========== ========= ======== ============= ============== ============= ============== =============== ==== ======= ====== ===========

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

地址:此映射的起始地址。这使用虚拟内存寻址。
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 的内存,这几乎完全归功于运行时库。

最后一个巧妙的技巧是,您可以将命令pmap和pidof命令一起使用,将查找进程的 PID 并将其传递给pmap一个命令的操作结合起来:

pmap $(pidof pm)

相关文章