页面迁移

451 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情

Linux内核为页面迁移提供了一个系统调用migrate_pages,它最早是在Linux 2.6.16内核中加入的,它可以迁移一个进程的所有页面到指定内存节点上。该系统调用的用户空间的接口函数如下。


#include <numaif.h>

long migrate_pages(int pid, unsigned long maxnode,
                         const unsigned long *old_nodes,
                         const unsigned long *new_nodes);

该系统调用最早是为了在NUMA系统中提供一种迁移进程到任意内存节点的能力。现在内核除了为NUMA系统提供页面迁移能力外,其他的一些模块也可以利用页面迁移功能做一些事情,如内存规整和内存热插拔(memory hotplug)等。

哪些页面可以迁移

在阅读页面迁移代码之前,我们首先了解哪些页面是可以迁移的。页面迁移的设计初衷是在NUMA系统中提高内存访问性能,把一些页面从一个内存节点迁移到另外一个内存节点。它还有一个应用场景——内存规整。这些迁移的页面都是LRU链表上的页面。LRU链表上的页面通常是用户进程地址空间映射的页面,如匿名页面和文件映射的页面。但是,最近几年Linux内核引入了一些新的特性,如zsmalloc和virtio-balloon页面。以virtio-balloon页面为例,它也有页面迁移的需求,之前的做法是在virtio-balloon驱动中进行迁移操作和相应的逻辑。如果其他的驱动也想做类似的页面迁移,那么它们就不能复用与virtio-balloon驱动相关的代码,必须重新写一套代码,这样会造成很多代码的重复和冗余。为了解决这个问题,内存管理的页面迁移机制提供相应的接口来支持这些非LRU页面的迁移。 因此,页面迁移机制支持两大类内存页面。

  • 传统LRU页面,如匿名页面和文件映射页面。
  • 非LRU页面。如zsmalloc或者virtio-balloon页面。

页面迁移主函数

页面迁移(page migration)在Linux内核的主函数是migrate_pages()函数,它实现在mm/migrate.c文件中。migrate_pages()函数内部调用unmap_and_move()函数来实现。


<mm/migrate.c>

int migrate_pages(struct list_head *from, new_page_t get_new_page,
        free_page_t put_new_page, unsigned long private,
        enum migrate_mode mode, int reason)

static ICE_noinline int unmap_and_move(new_page_t get_new_page,
                   free_page_t put_new_page,
                   unsigned long private, struct page *page,
                   int force, enum migrate_mode mode,
                   enum migrate_reason reason)

migrate_pages()函数和unmap_and_move()函数的参数是一样的,一共有6个参数。

  • from:将要迁移页面的链表。
  • get_new_page:申请新内存的页面的函数指针。
  • put_new_page:迁移失败时释放目标页面的函数指针。
  • private:传递给get_new_page的参数。
  • mode:迁移模式。
  • reason:迁移的原因。

unmap_and_move()函数中的主要操作如下。 在第1175行中,调用get_new_page()分配一个新的页面。 在第1179~1194行中,刚分配的页面需要调用put_new_page()回调函数,如内存规整机制中的compaction_free()回调函数,把空闲页面添加到cc->freepages链表中。 在第1196行中,调用__unmap_and_move()尝试迁移页面到新分配的页面中。我们稍后会详细分析这个函数。 在第1201行中,若返回值不等于-EAGAIN,说明可能迁移没成功。 在第1225行中,若返回值为MIGRATEPAGE_SUCCESS,说明迁移成功,释放页面。 在第1237行中,处理迁移没成功的情况,把页面重新添加到可移动的页面里。释放刚才新分配的页面。 下面重点分析__unmap_and_move()函数。

<mm/migrate.c>

static int __unmap_and_move(struct page *page, struct page *newpage,
                int force, enum migrate_mode mode)