检查进程页表 (翻译 by chatgpt)
原文:https://www.kernel.org/doc/html/v6.6/admin-guide/mm/pagemap.html
检查进程页表
pagemap是内核中的一组接口,允许用户空间程序通过读取/proc中的文件来检查页面表和相关信息。
pagemap包括以下四个组件:
-
/proc/pid/pagemap:该文件允许用户空间进程查找每个虚拟页面映射到的物理帧。它包含每个虚拟页面的一个64位值,其中包含以下数据:
-
位0-54:页面帧号(PFN)(如果存在)
-
位0-4:交换类型(如果已交换)
-
位5-54:交换偏移(如果已交换)
-
位55:pte为软脏(参见软脏PTEs)
-
位56:页面独占映射(自4.2版起)
-
位57:pte为uffd-wp写保护(自5.13版起)(参见Userfaultfd)
-
位58-60:零
-
位61:页面是文件页或共享匿名页(自3.5版起)
-
位62:页面已交换
-
位63:页面存在
自Linux 4.0起,只有具有CAP_SYS_ADMIN权限的用户才能获取PFN。在4.0和4.1中,非特权用户的打开操作会失败并返回-EPERM。从4.2开始,如果用户没有CAP_SYS_ADMIN权限,则PFN字段将被清零。原因是:关于PFN的信息有助于利用Rowhammer漏洞。
如果页面不在内存中但在交换空间中,则PFN包含交换文件号和页面在交换空间中的偏移的编码。未映射的页面返回空的PFN。这允许精确确定哪些页面被映射(或在交换中),并比较不同进程之间的映射页面。
使用这个接口的高效用户将使用/proc/pid/maps来确定哪些内存区域实际上被映射,并使用llseek来跳过未映射的区域。
-
-
/proc/kpagecount:该文件包含每个页面被映射的次数的64位计数,以PFN为索引。
在tools/mm目录中的page-types工具可以用于查询页面被映射的次数。
-
/proc/kpageflags:该文件包含每个页面的64位标志集,以PFN为索引。
这些标志包括(来自fs/proc/page.c,上面的kpageflags_read):
- LOCKED
- ERROR
- REFERENCED
- UPTODATE
- DIRTY
- LRU
- ACTIVE
- SLAB
- WRITEBACK
- RECLAIM
- BUDDY
- MMAP
- ANON
- SWAPCACHE
- SWAPBACKED
- COMPOUND_HEAD
- COMPOUND_TAIL
- HUGE
- UNEVICTABLE
- HWPOISON
- NOPAGE
- KSM
- THP
- OFFLINE
- ZERO_PAGE
- IDLE
- PGTABLE
-
/proc/kpagecgroup:该文件包含每个页面所属的内存cgroup的64位索引节点号,以PFN为索引。仅在设置了CONFIG_MEMCG时可用。
页面标志的简要描述
-
0 - LOCKED
该页面被锁定,用于独占访问,例如正在进行读/写IO操作。 -
7 - SLAB
该页面由SLAB/SLUB内核内存分配器管理。当使用复合页面时,只有头页面会设置此标志。 -
10 - BUDDY
由伙伴系统分配器管理的空闲内存块。伙伴系统以各种顺序组织空闲内存块。一个N阶块具有2^N个物理连续页面,对于第一个页面,只有设置了BUDDY标志。 -
15 - COMPOUND_HEAD
由N阶复合页面由2^N个物理连续页面组成。具有2阶的复合页面采用“HTTT”形式,其中H代表头页面,T代表尾页面。复合页面的主要使用者包括大页(HugeTLB Pages)、SLUB等内存分配器和各种设备驱动程序。然而,在这个接口中,只有大/巨大页面对最终用户可见。 -
16 - COMPOUND_TAIL
复合页面的尾部(见上述描述)。 -
17 - HUGE
这是大页的一个组成部分。 -
19 - HWPOISON
硬件检测到该页面上的内存损坏:不要触摸数据! -
20 - NOPAGE
请求的地址上不存在页面帧。 -
21 - KSM
动态共享的相同内存页面,由一个或多个进程共享。 -
22 - THP
构成透明大页面的连续页面。 -
23 - OFFLINE
该页面在逻辑上处于离线状态。 -
24 - ZERO_PAGE
用于pfn_zero或huge_zero页面的零页面。 -
25 - IDLE
该页面自从被标记为空闲以来尚未被访问(请参阅空闲页面跟踪)。请注意,如果页面通过PTE被访问,此标志可能已过时。要确保该标志是最新的,必须首先读取/sys/kernel/mm/page_idle/bitmap。 -
26 - PGTABLE
该页面正在用作页表。
与IO相关的页面标志
-
1 - ERROR
发生了IO错误。 -
3 - UPTODATE
该页面具有最新的数据。即对于文件支持的页面:(内存中的数据修订版 >= 磁盘上的数据修订版)。 -
4 - DIRTY
该页面已被写入,因此包含新数据。即对于文件支持的页面:(内存中的数据修订版 > 磁盘上的数据修订版)。 -
8 - WRITEBACK
该页面正在同步到磁盘。
LRU相关的页面标志
-
5 - LRU
该页面位于LRU列表之一中。 -
6 - ACTIVE
该页面位于活动的LRU列表中。 -
18 - UNEVICTABLE
该页面位于不可驱逐(非)LRU列表中。它被某种方式固定,并且不是LRU页面回收的候选项,例如ramfs页面、shmctl(SHM_LOCK)和mlock()内存段。 -
2 - REFERENCED
该页面自上次LRU列表入队/重新入队以来已被引用。 -
9 - RECLAIM
该页面在其页面输出IO完成后将很快被回收。 -
11 - MMAP
内存映射页面。 -
12 - ANON
不属于文件的内存映射页面。 -
13 - SWAPCACHE
该页面映射到交换空间,即具有关联的交换条目。 -
14 - SWAPBACKED
该页面由交换/内存支持。
可以使用tools/mm目录中的page-types工具来查询上述标志。
利用pagemap做一些有用的事情
使用pagemap进行有用操作的一般步骤如下:
-
阅读/proc/pid/maps以确定内存空间的哪些部分映射到了什么。
-
选择您感兴趣的映射 - 可以是所有映射,也可以是特定的库,或者堆栈或堆等。
-
打开/proc/pid/pagemap并定位到您想要检查的页面。
-
从pagemap中读取每个页面的u64。
-
打开/proc/kpagecount和/或/proc/kpageflags。对于刚刚读取的每个PFN,定位到文件中的相应条目,并读取您想要的数据。
例如,要找到"唯一集大小"(USS),即进程使用的与任何其他进程不共享的内存量,您可以遍历进程中的每个映射,找到PFN,在kpagecount中查找它们,并统计仅被引用一次的页面数量。
共享内存的例外情况
共享页面的页表条目在页面被清除或交换出时会被清除。这使得交换出的页面与从未分配的页面无法区分。
在内核空间,交换位置仍然可以从页面缓存中检索到。然而,仅存储在普通PTE上的值在页面被交换出时会被永久丢失(即SOFT_DIRTY)。
在用户空间,可以通过lseek()和/或mincore()系统调用来推断页面是否存在、已交换或不存在。
lseek()可以通过在支持页面的文件上指定SEEK_DATA标志来区分已访问的页面(存在或已交换出)和空洞(不存在/未分配)。对于匿名共享页面,文件可以在/proc/pid/map_files/中找到。
mincore()可以区分内存中的页面(存在,包括交换缓存)和内存外的页面(已交换出或不存在/未分配)。
其他注意事项
如果您在文件中的任何位置开始读取(例如,如果您在文件中寻找了奇数字节),或者读取的大小不是8字节的倍数,那么从任何文件中读取都会返回-EINVAL。
在Linux 3.11之前,pagemap的55-60位用于“页面位移”(在大多数架构上始终为12)。自Linux 3.11以来,它们的含义在第一次清除软脏位之后发生了变化。自Linux 4.2以来,它们无条件地用于标志。
Pagemap扫描IOCTL
在pagemap文件上的PAGEMAP_SCAN IOCTL可用于获取或可选地清除有关页表条目的信息。此IOCTL支持以下操作:
-
扫描地址范围并获取与提供的条件匹配的内存范围。当指定输出缓冲区时执行此操作。
-
写保护页面。PM_SCAN_WP_MATCHING用于写保护感兴趣的页面。PM_SCAN_CHECK_WPASYNC如果发现非异步写保护页面,则中止操作。PM_SCAN_WP_MATCHING可以与或不与PM_SCAN_CHECK_WPASYNC一起使用。
-
这两种操作可以合并为一个原子操作,其中我们可以获取并写保护页面。
当前支持有关页面的以下标志:
-
PAGE_IS_WPALLOWED - 页面启用了异步写保护
-
PAGE_IS_WRITTEN - 从写保护以来已对页面进行了写入
-
PAGE_IS_FILE - 页面由文件支持
-
PAGE_IS_PRESENT - 页面存在于内存中
-
PAGE_IS_SWAPPED - 页面已被交换
-
PAGE_IS_PFNZERO - 页面具有零PFN
-
PAGE_IS_HUGE - 页面是THP或HugeTLB支持的
struct pm_scan_arg用作IOCTL的参数。
-
必须在size字段中指定struct pm_scan_arg的大小。如果以后进行扩展,此字段将有助于识别结构。
-
可以在flags字段中指定标志。目前,PM_SCAN_WP_MATCHING和PM_SCAN_CHECK_WPASYNC是唯一添加的标志。根据是否提供了输出缓冲区,执行获取操作是可选的。
-
通过start和end指定范围。
-
遍历可能在访问完整范围之前中止,例如用户缓冲区可能已满等。遍历结束地址在end_walk中指定。
-
struct page_region数组的输出缓冲区和大小在vec和vec_len中指定。
-
可选的最大请求页面在max_pages中指定。
-
掩码在category_mask、category_anyof_mask、category_inverted和return_mask中指定。
查找已写入的页面并对其进行写保护:
struct pm_scan_arg arg = {
.size = sizeof(arg),
.flags = PM_SCAN_CHECK_WPASYNC | PM_SCAN_CHECK_WPASYNC,
..
.category_mask = PAGE_IS_WRITTEN,
.return_mask = PAGE_IS_WRITTEN,
};
查找已写入、由文件支持、未交换且存在或巨大的页面:
struct pm_scan_arg arg = {
.size = sizeof(arg),
.flags = 0,
..
.category_mask = PAGE_IS_WRITTEN | PAGE_IS_SWAPPED,
.category_inverted = PAGE_IS_SWAPPED,
.category_anyof_mask = PAGE_IS_PRESENT | PAGE_IS_HUGE,
.return_mask = PAGE_IS_WRITTEN | PAGE_IS_SWAPPED |
PAGE_IS_PRESENT | PAGE_IS_HUGE,
};
PAGE_IS_WRITTEN标志可以被视为软脏标志的性能更佳的替代品。它不受内核VMA合并的影响,因此用户可以在普通页面的情况下找到真正的软脏页面。(对于THP或HugeTLB页面,仍可能报告额外的脏页面。)
"PAGE_IS_WRITTEN"类别与启用了uffd写保护的范围一起使用,以在用户空间实现内存脏页跟踪:
-
使用userfaultfd系统调用创建userfaultfd文件描述符。
-
通过UFFDIO_API IOCTL设置UFFD_FEATURE_WP_UNPOPULATED和UFFD_FEATURE_WP_ASYNC功能。
-
使用UFFDIO_REGISTER IOCTL通过UFFDIO_REGISTER_MODE_WP模式注册内存范围。
-
然后可以使用PAGEMAP_SCAN IOCTL并使用标志PM_SCAN_WP_MATCHING或UFFDIO_WRITEPROTECT IOCTL来写保护注册的内存的任何部分或整个内存区域。这两者执行相同的操作。就性能而言,前者更好。
-
现在可以使用PAGEMAP_SCAN IOCTL仅查找自上次标记以来已写入的页面和/或可选地对页面进行写保护。
本文来自博客园,作者:dolinux,未经同意,禁止转载