空闲页跟踪

动机

空闲页跟踪功能允许跟踪工作负载正在访问哪些内存页以及哪些是空闲的。这些信息可用于估算工作负载的工作集大小,这反过来又可在配置工作负载参数、设置内存 cgroup 限制或决定将工作负载放置在计算集群的何处时予以考虑。

通过 CONFIG_IDLE_PAGE_TRACKING=y 启用此功能。

用户 API

空闲页跟踪 API 位于 /sys/kernel/mm/page_idle。目前,它仅包含一个读写文件:/sys/kernel/mm/page_idle/bitmap

该文件实现了一个位图,其中每个位对应一个内存页。位图由一个 8 字节整数数组表示,PFN #i 处的页映射到数组元素 #i/64 的位 #i%64,字节顺序为本机字节序。如果某个位被设置,则对应的页为空闲状态。

如果一个页自被标记为空闲以来未被访问过(关于“访问”的具体含义,请参阅实现细节部分),则该页被视为空闲。要将一个页标记为空闲,必须通过写入文件来设置对应于该页的位。写入文件的值将与当前位图值进行 OR 运算。

只跟踪用户内存页的访问。这些包括映射到进程地址空间的页、页缓存和缓冲区页、交换缓存页。对于其他页类型(例如 SLAB 页),尝试将页标记为空闲会被静默忽略,因此这些页永远不会被报告为空闲。

对于大页,空闲标志只在头部页上设置,因此必须读取 /proc/kpageflags 才能正确计算空闲大页的数量。

如果读写 /sys/kernel/mm/page_idle/bitmap 不是从 8 字节边界开始,或者读写的大小不是 8 字节的倍数,则会返回 -EINVAL。写入此文件超出最大 PFN 会返回 -ENXIO。

也就是说,为了估算工作负载未使用的页数量,应该:

  1. 通过设置 /sys/kernel/mm/page_idle/bitmap 中对应的位,将所有工作负载的页标记为空闲。如果工作负载由一个进程表示,可以通过读取 /proc/pid/pagemap 找到这些页;如果工作负载放置在内存 cgroup 中,则可以通过 /proc/kpagecgroup 过滤掉无关的页。

  2. 等待工作负载访问其工作集。

  3. 读取 /sys/kernel/mm/page_idle/bitmap 并计算设置的位数。如果想忽略某些类型的页,例如因为它们不可回收而忽略已锁定的页,可以使用 /proc/kpageflags 过滤掉它们。

tools/mm 目录中的 page-types 工具可以协助完成此操作。如果该工具最初运行时带有适当的选项,它将把所有查询的页标记为空闲。后续运行该工具可以显示哪些页的空闲标志在此期间已被清除。

有关 /proc/pid/pagemap/proc/kpageflags/proc/kpagecgroup 的更多信息,请参见检查进程页表

实现细节

内核内部跟踪用户内存页的访问,以便在内存不足时首先回收未引用的页。如果一个页最近通过进程地址空间被访问过,则认为该页已被引用,在这种情况下,它所映射的一个或多个 PTE 将设置 Accessed 位,或者由内核显式标记为已访问(参见 mark_page_accessed())。后者发生在以下情况:

  • 用户空间进程使用系统调用(例如 read(2) 或 write(2))读写一个页

  • 一个用于存储文件系统缓冲区的页被读写,因为进程需要存储在其中的文件系统元数据(例如列出目录树)

  • 一个页被设备驱动程序使用 get_user_pages() 访问

当一个脏页由于内存回收或超出脏内存限制而被写入交换区或磁盘时,它不会被标记为已引用。

空闲内存跟踪功能增加了一个新的页标志,即空闲标志(Idle flag)。此标志通过写入 /sys/kernel/mm/page_idle/bitmap 手动设置(参见用户 API 部分),并在页被如上定义为已引用时自动清除。

当一个页被标记为空闲时,它所映射的所有 PTE 中的 Accessed 位必须被清除,否则我们将无法检测到来自进程地址空间对该页的访问。为了避免与回收器(如上所述,回收器使用 Accessed 位来提升活跃引用的页)发生冲突,引入了另一个页标志:Young 标志。当由于设置或更新页的 Idle 标志而清除 PTE Accessed 位时,该页的 Young 标志被设置。回收器将 Young 标志视为额外的 PTE Accessed 位,因此会将此类页视为已引用。

由于空闲内存跟踪功能基于内存回收器逻辑,它只适用于 LRU 列表上的页,其他页会被静默忽略。这意味着它会忽略一个用户内存页(如果它被隔离),但由于这类页通常不多,因此不会显著影响总体结果。为了避免阻塞空闲页位图的扫描,锁定的页也可能被跳过。