检查进程页表¶
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-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 位 inode 号,按 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 用于 “page-shift”(在大多数架构上始终为 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
- 页面是软脏的
struct pm_scan_arg
用作 IOCTL 的参数。
struct pm_scan_arg
的大小必须在size
字段中指定。如果以后进行扩展,此字段将有助于识别该结构。可以在
flags
字段中指定标志。此时,PM_SCAN_WP_MATCHING
和PM_SCAN_CHECK_WPASYNC
是唯一添加的标志。根据是否提供输出缓冲区,可以选择性地执行 get 操作。范围通过
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 写保护的范围一起使用,以在用户空间中实现内存脏跟踪
用户faultfd 文件描述符通过
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 可用于查找自上次标记以来已被写入的页面,和/或选择性地写保护页面。