页迁移最初是为了解决NUMA中一个节点的CPU上运行的进程访问另一个节点中 的内存而提出的,目前内存紧缩(compaction)创建大页时,也需要用到页 迁移。
应用程序可以使用mbind系统调用的MF_MOVE和MF_MOVE_ALL将该程序的内存都 迁移到指定的内存节点中,或者使用migrate_pages函数将其他程序的内存迁 移到某个内存节点之中,这样做的目的是为了降低内存访问的开销,使得每个 CPU都能访问到离它最近的内存。目前大多数内存迁移都需要人为的去指定, 操作系统不能自动进行,有些时候NUMA系统会根据一些内存访问的统计信息 做出一些迁移。
cgroup中的cpuset子系统是用来管理NUMA系统中的节点,当属于一个cpuset 的进程被迁移到另一个cpuset中时,该进程使用的内存也会自动的被迁移到 另一个cpuset中。
- 使用isolate_lru_page函数将待迁移的页从LRU链表上删除;
- 准备分配新内存的函数,这个函数负责分配新的内存——即将迁移到的内存;
- 调用migrate_pages函数开始内存迁移;
- 锁定即将迁移的页;
- 保证该页已经完成了回写(writeback);
- 锁定即将迁移到的页;
- 将所有页表对旧页的访问都替换为迁移项(migration entries),这将会 减少旧页中的mapcount字段,如果旧页中的mapcount字段不为0,那么将不能进 行迁移。所有用户空间需要访问旧页的进程都会等待在page lock上;
- 获取基树锁(radix tree lock),这将会导致所有通过映射访问待迁移内存 的进程在基树自旋锁上等待;
- 检查旧页的refcount,如果refcount为1,说明此页可以进行迁移,因为我们 是唯一的使用者了;
- 检查基树中是否包含旧页,如果不包含,不能迁移,说明其他部分修改了基树;
- 根据旧页的设置来设置新页;
- 基树指向新页;
- 从基树中删除旧页,并将旧页的refcount减1;将新页的refcount加1;
- 释放基树锁,现在程序可以通过获取基树锁来查找映射,程序从基树锁上的 等待转移到page lock上的等待;
- 旧页中的数据被拷贝到新页中;
- 旧页中剩余的页标记被拷贝到新页中;
- 旧页中的标记被清除,表明这个页中不再包含有用的数据;
- 触发新页的writeback;
- 如果迁移项(migration entries)是页,就把他们替换为真正的pte;这样 就可以让那些还没有等待在page lock上的用户空间进程访问内存了;
- 释放旧页和新页的锁,等待在page lock上的进程将会重新执行page fault, 这样就可以到达新页;
- 新页被加入到LRU,因此可以被swapper等进行扫描;
http://lxr.free-electrons.com/source/Documentation/vm/page_migration