页面迁移
页面迁移允许在 NUMA 系统中的节点之间移动页面的物理位置,同时进程正在运行。这意味着进程看到的虚拟地址不会改变。但是,系统重新排列这些页面的物理位置。
另请参阅异构内存管理(HMM),用于将页面迁移到或从设备私有内存中。
页面迁移的主要目的是通过将页面移动到访问该内存的进程所在的处理器附近,以减少内存访问的延迟。
页面迁移允许进程通过使用 mbind() 设置新的内存策略,通过 MF_MOVE 和 MF_MOVE_ALL 选项手动重新定位其页面所在的节点。进程的页面也可以通过使用 sys_migrate_pages() 函数调用从另一个进程重新定位。migrate_pages() 函数调用接受两组节点,并将进程的页面从源节点移动到目标节点。页面迁移功能由 Andi Kleen 的 numactl 软件包提供(需要版本大于 0.9.3。可从 https://github.com/numactl/numactl.git 获取)。numactl 提供了 libnuma,它提供了类似于其他 NUMA 功能的页面迁移接口。cat /proc/<pid>/numa_maps
允许轻松查看进程页面的位置。还可以参考 proc(5) 手册中的 numa_maps 文档。
手动迁移在例如调度程序已将进程迁移到远程节点上的处理器时非常有用。批处理调度程序或管理员可以检测到这种情况,并将进程的页面移动到新处理器附近。内核本身仅提供手动页面迁移支持。可以通过用户空间进程实现自动页面迁移。特殊的函数调用 "move_pages" 允许在进程内移动单个页面。例如,NUMA 分析器可以获取显示频繁跨节点访问的日志,并使用结果将页面移动到更有利的位置。
较大的安装通常使用 cpusets 将系统分区为节点的部分。Paul Jackson 已经为 cpusets 增加了在任务移动到另一个 cpuset 时移动页面的功能(参见 CPUSETS)。Cpusets 允许自动化进程的局部性。如果任务移动到新的 cpuset,则其所有页面也将随之移动,以便进程的性能不会急剧下降。如果更改了 cpuset 的允许内存节点,则 cpuset 中进程的页面也会被移动。
页面迁移允许保留节点组内页面的相对位置,对于所有迁移技术,即使在迁移进程后也会保留特定的内存分配模式。这是为了保持内存延迟。迁移后,进程将以类似的性能运行。
页面迁移分为几个步骤。首先是针对尝试从内核使用 migrate_pages() 的人员的高级描述(有关用户空间使用,请参见上述 Andi Kleen 的 numactl 软件包),然后是关于低级细节工作的低级描述。
在 migrate_pages() 的内核使用中
- 从 LRU 中移除页面。
- 通过扫描页面并将其移动到列表中生成要迁移的页面的列表。这是通过调用 isolate_lru_page() 完成的。调用 isolate_lru_page() 增加了页面的引用,以便在页面迁移发生时它不会消失。它还防止交换程序或其他扫描遇到页面。
- 我们需要有一个 new_folio_t 类型的函数,可以传递给 migrate_pages()。此函数应该根据旧 folio 如何分配正确的新 folio。
- 调用 migrate_pages() 函数,它尝试执行迁移。它将为考虑移动的每个 folio 调用函数以分配新 folio。
migrate_pages() 的工作原理
migrate_pages() 对其页面列表进行了几次遍历。如果页面的所有引用在此时可移除,则移动页面。页面已通过 isolate_lru_page() 从 LRU 中移除,并且引用计数已增加,因此在页面迁移发生时页面不会被释放。
步骤:
- 锁定要迁移的页面。
- 确保写回完成。
- 锁定我们要移动到的新页面。它被锁定,以便对这个(尚未更新的)页面的访问立即在移动进行时阻塞。
- 将页面的所有页表引用转换为迁移条目。这会减少页面的映射计数。如果结果的映射计数不为零,则我们不迁移页面。所有尝试访问页面的用户空间进程现在将等待页面锁或等待迁移页表条目被移除。
- 获取 i_pages 锁。这将导致所有尝试通过映射访问页面的进程在自旋锁上阻塞。
- 检查页面的引用计数,如果仍然存在引用,则撤销。否则,我们知道我们是唯一引用此页面的人。
- 检查 radix 树,如果它不包含指向此页面的指针,则撤销,因为其他人修改了 radix 树。
- 从旧页面准备一些设置到新页面,以便对新页面的访问将发现具有正确设置的页面。
- 更改 radix 树以指向新页面。
- 降低旧页面的引用计数,因为地址空间引用已经消失。建立对新页面的引用,因为地址空间引用新页面。
- 释放 i_pages 锁。这样一来,映射中的查找就变得可能了。进程将从在锁上旋转转为在锁定的新页面上睡眠。
- 将页面内容复制到新页面。
- 将剩余的页面标志复制到新页面。
- 清除旧页面标志,表示该页面不再提供任何信息。
- 触发新页面上排队的写回。
- 如果迁移条目已插入到页表中,则用真实的 pte 替换它们。这样将使用户空间进程可以访问尚未等待页面锁的页面。
- 从旧页面和新页面中释放页面锁。等待页面锁的进程将重新执行其页面故障,并将到达新页面。
- 将新页面移动到 LRU 中,并可以再次被交换程序等扫描。
非 LRU 页面迁移
尽管迁移最初旨在减少 NUMA 的内存访问延迟,但压缩也使用迁移来创建高阶页面。对于压缩目的,能够移动非 LRU 页面(例如 zsmalloc 和 virtio-balloon 页面)也是有用的。
如果驱动程序希望使其页面可移动,它应该定义一个 struct movable_operations。然后需要在可能移动的每个页面上调用 __SetPageMovable()
。这使用了 page->mapping 字段,因此该字段不可供驱动程序用于其他目的。
监控迁移
以下事件(计数器)可用于监视页面迁移。
- PGMIGRATE_SUCCESS:正常页面迁移成功。每次计数表示迁移了一个页面。如果页面不是 THP 和非巨大页,则此计数增加一。如果页面是 THP 或巨大页,则此计数增加 THP 或巨大页的子页面数。例如,迁移一个单个 2MB 的 THP,其具有 4KB 大小的基本页面(子页面),将导致此计数增加 512。
- PGMIGRATE_FAIL:正常页面迁移失败。与 PGMIGRATE_SUCCESS 相同的计数规则:如果是 THP 或巨大页,则将增加子页面的数量。
- THP_MIGRATION_SUCCESS:成功迁移了一个 THP 而没有拆分。
- THP_MIGRATION_FAIL:无法迁移 THP 也无法拆分。
- THP_MIGRATION_SPLIT:迁移了一个 THP,但不是作为 THP:首先,必须拆分 THP。拆分后,使用迁移重试其子页面。
THP_MIGRATION_* 事件还会更新相应的 PGMIGRATE_SUCCESS 或 PGMIGRATE_FAIL 事件。例如,THP 迁移失败将导致 THP_MIGRATION_FAIL 和 PGMIGRATE_FAIL 都增加。
Christoph Lameter,2006 年 5 月 8 日。Minchan Kim,2016 年 3 月 28 日。
struct movable_operations
https://www.kernel.org/doc/html/v6.6/mm/page_migration.html#c.movable_operations