检查进程页表¶
pagemap 是内核中一套新(自 2.6.25 版本起)的接口,它允许用户空间程序通过读取 /proc
中的文件来检查页表及相关信息。
pagemap 有四个组成部分
/proc/pid/pagemap
。此文件允许用户空间进程找出每个虚拟页映射到的物理帧。它为每个虚拟页包含一个 64 位值,包含以下数据(来自fs/proc/task_mmu.c
,在 pagemap_read 之上)
位 0-54 页帧号 (PFN)(如果存在)
位 0-4 交换类型(如果已交换)
位 5-54 交换偏移量(如果已交换)
位 55 pte 是软脏的(请参阅 软脏 PTE)
位 56 页被独占映射(自 4.2 版本起)
位 57 pte 受 uffd-wp 写保护(自 5.13 版本起)(请参阅 Userfaultfd)
位 58 pte 是一个保护区域(自 6.15 版本起)(请参阅 madvise (2) 手册页)
位 59-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。这允许精确地确定哪些页已映射(或处于交换状态)并比较进程之间映射的页。
传统上,位 56 表示页只被精确映射一次,当页被多次映射时(即使在同一进程中被多次映射),位 56 会被清除。在某些内核配置中,作为较大分配(例如 THP)一部分的页的语义可能会有所不同:如果相应较大分配的所有页都确定地映射在同一进程中,即使该页在该进程中被多次映射,位 56 也会被设置。当较大分配的任何页可能映射在不同进程中时,位 56 会被清除。在某些情况下,即使不再是这种情况,较大的分配也可能被视为“可能被多个进程映射”。
此接口的高效用户将使用
/proc/pid/maps
来确定内存的哪些区域实际已映射,并使用 llseek 跳过未映射的区域。
/proc/kpagecount
。此文件包含一个 64 位计数,表示每个页被映射的次数,并按 PFN 索引。某些内核配置不跟踪作为较大分配(例如 THP)一部分的页被映射的精确次数。在这些配置中,将返回此较大分配中每个页的平均映射次数。但是,如果较大分配的任何页已映射,则返回的值将至少为 1。
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
。此文件包含一个 64 位 inode 号,表示每个页所属的内存 cgroup,并按 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 页(HugeTLB 页)、SLUB 等内存分配器和各种设备驱动程序。然而,在此接口中,只有巨页/千兆页对最终用户可见。
- 16 - COMPOUND_TAIL(复合页尾)
复合页尾(见上述描述)。
- 17 - HUGE(巨页)
这是 HugeTLB 页的组成部分。
- 19 - HWPOISON(硬件中毒)
硬件检测到此页上的内存损坏:请勿触碰数据!
- 20 - NOPAGE(无页)
请求的地址处不存在页帧。
- 21 - KSM
一个或多个进程之间动态共享的相同内存页。
- 22 - THP
构造任意大小 THP 并以任意粒度映射的连续页。
- 23 - OFFLINE(离线)
该页逻辑上处于离线状态。
- 24 - ZERO_PAGE(零页)
pfn_zero 或 huge_zero 页的零页。
- 25 - IDLE(空闲)
该页自被标记为空闲以来未被访问(请参阅 空闲页跟踪)。请注意,如果该页通过 PTE 访问,则此标志可能已过时。为确保标志最新,必须首先读取
/sys/kernel/mm/page_idle/bitmap
。- 26 - PGTABLE(页表)
该页正在用作页表。
其他注意事项¶
如果您没有在 8 字节边界上开始读取(例如,如果您在文件中查找了奇数个字节),或者如果读取的大小不是 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
- 页具有零 PFNPAGE_IS_HUGE
- 页是 PMD 映射的 THP 或 HugeTLB 支持的PAGE_IS_SOFT_DIRTY
- 页是软脏的PAGE_IS_GUARD
- 页是保护区域的一部分
struct pm_scan_arg
用作 IOCTL 的参数。
struct pm_scan_arg
的大小必须在size
字段中指定。如果以后进行扩展,此字段将有助于识别结构。标志可以在
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
标志可以被认为是 soft-dirty 标志的一个性能更好的替代方案。它不受内核 VMA 合并的影响,因此用户可以在普通页的情况下找到真正的软脏页。(对于 THP 或 Hugetlb 页,仍可能报告额外的脏页。)
“PAGE_IS_WRITTEN” 类别与启用 uffd 写保护的范围一起使用,以在用户空间中实现内存脏页跟踪
userfaultfd 文件描述符通过
userfaultfd
系统调用创建。
UFFD_FEATURE_WP_UNPOPULATED
和UFFD_FEATURE_WP_ASYNC
功能通过UFFDIO_API
IOCTL 设置。内存范围通过
UFFDIO_REGISTER
IOCTL 以UFFDIO_REGISTER_MODE_WP
模式注册。然后,注册内存的任何部分或整个内存区域必须使用带有
PM_SCAN_WP_MATCHING
标志的PAGEMAP_SCAN
IOCTL 或UFFDIO_WRITEPROTECT
IOCTL 进行写保护。两者执行相同的操作。前者在性能方面更好。现在,
PAGEMAP_SCAN
IOCTL 可以用于查找自上次标记以来已被写入的页,和/或可选地写保护这些页。