页面迁移系统调用migrate_pages
,可以迁移一个进程所有页面到指定的内存节点上。该系统调用在用户空间libnuma 共享库
的接口如下:
#include <numaif.h>
long migrate_pages(int pid, unsigned long maxnode,
const unsigned long *old_nodes,
const unsigned long *new_nodes);
该系统调用除了用在NUMA系统中,也可以用在页面规整和内存热插拔。
页面迁移机制支持两大类内存页面:
- 传统LRU页面,如匿名页面和文件映射页面。
- 非LRU页面,如zsmalloc或者virtio-balloon页面。
一、页面迁移主函数
页面迁移主函数是migrate_pages
函数,函数内部调用unmap_and_move
函数来实现。
1.migrate_pages
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, unsigned int *ret_succeeded)
static 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,
struct list_head *ret)
from
:将要迁移页面的链表get_new_page
:申请新内存的页面的函数指针put_new_page
:迁移失败时释放目标页面的函数指针private
:传递给get_new_page
的参数mode
:迁移模式reason
:迁移的原因
2.unmap_and_move
unmap_and_move
函数主要操作如下:
- 调用
get_new_page
分配新的页面 - 刚分配的页面调用
put_new_page
回调函数 - 调用
__unmap_and_move
迁移页面到新分配的页面中 - 返回值为
MIGRATEPAGE_SUCCESS
说明迁移成功,释放页面 - 迁移未成功,把页面重新添加到可移动页面里,释放刚才新分配的页面
3.__unmap_and_move
static int __unmap_and_move(struct page *page, struct page *newpage,
int force, enum migrate_mode mode)
page
:被迁移的页面newpage
:迁移页面的目的地force
:表示是否强制迁移,当尝试次数大于2,会设置force = 1mode
:迁移模式
__unmap_and_move
函数主要操作如下:
-
__PageMovable
函数判断页面是否属于非LRU页面,通过page
数据结构的mapping
成员是否是设置了PAGE_MAPPING_MOVABLE
来判断。is_lru
表示页面属于传统LRU页面 -
trylock_page
函数尝试加锁,如果返回false
,表示其他进程已持有这个锁- 如果申请锁不成功,当前不是强制迁移
force = 0
或迁移模式是MIGRATE_ASYNC
,则直接忽略这个页面 - 如果进程设置了
PF_MEMALLOC
,说明进程在直接内存压缩的内核路径上,直接忽略掉该页面。
除了上面两种情况,其余情况调用
lock_page
函数来等待页锁被释放 - 如果申请锁不成功,当前不是强制迁移
-
处理正在回写的页面,即设置了
PG_writeback
标志位的页面。当设置了MIGRATE_ASYNC
或MIGRATE_SYNC_LIGHT
且设置强制迁移,才会等待页面回写完成,否则该页面直接忽略不会迁移。wait_on_page_writeback
函数等待页面回写完成 -
在页面迁移过程中,无法知道
anon_vma
结构体是否被释放,page_get_anon_vma
函数增加anon_vma->_refcount
引用计数防止被其他进程释放 -
-
处理非LRU页面
调用
move_to_new_page
函数,通过调用驱动程序注册的migratepage
函数来进行页面迁移,迁移完成后,unmap_and_move
直接返回。 -
处理LRU页面
- 交换缓存页面从交换分区读取后,被添加到LRU链表,他没有设置RMAP,所以做特殊处理,跳转到
out_unlock_both
标签处 page_mapped
判断页面_mapcount
如果大于或者等于零,说明有用户PTE映射该页面- 使用
try_to_unmap
解除页面所有映射的用户PTE - 已经解除了PTE映射的页面,调用
move_to_new_page
把他们迁移到新分配的页面 - 对于迁移失败的页面,调用
remove_migration_ptes
删掉迁移的PTE
- 交换缓存页面从交换分区读取后,被添加到LRU链表,他没有设置RMAP,所以做特殊处理,跳转到
-
-
处理退出
- 对于非LRU页面,调用
put_page
把newpage
的_refcount
减1 - 对于传统LRU页面,把
newpage
添加到LRU链表中
- 对于非LRU页面,调用
二、move_to_new_page函数
move_to_new_page
用于迁移旧页面到新页面
static int move_to_new_page(struct page *newpage, struct page *page,
enum migrate_mode mode)
is_lru
表示页面是否属于传统LRU页面page_mapping
返回页面的mapping
- 页面是匿名页面并且分配了交换分区,
mapping
指向交换缓存 - 页面是匿名页面但是没有分配交换分区,
mapping
指向anon_vma
,page_mapping
返回NULL - 页面是文件映射页面,
mapping
指向文件映射对应的地址空间
- 页面是匿名页面并且分配了交换分区,
- 页面属于传统LRU页面
mapping
为空,调用migrate_page
来迁移页面- 若页面实现了
migratepage
,直接调用mapping->a_ops_migratepage
来迁移页面 - 其他情况调用
fallback_migrate_page
函数
- 非LRU页面通过调用驱动程序注册的
migratepage
函数来进行页面迁移
迁移的核心函数是migrate_page
:
1.migrate_page
int migrate_page(struct address_space *mapping,
struct page *newpage, struct page *page,
enum migrate_mode mode)
-
调用
migrate_page_move_mapping
函数- 复制
page->index
到新页面 - 新页面
mapping
指向旧页面mapping
指向的地方
- 复制
-
调用
copy_highpage
函数,复制旧页面内容到新页面static inline void copy_highpage(struct page *to, struct page *from) { char *vfrom, *vto; vfrom = kmap_atomic(from); vto = kmap_atomic(to); copy_page(vto, vfrom); }
-
调用
migrate_page_states
把旧页面标志复制到新页面
三、迁移页表
迁移页表在remove_migration_ptes
函数中实现:
void remove_migration_ptes(struct page *old, struct page *new, bool locked)
{
struct rmap_walk_control rwc = {
.rmap_one = remove_migration_pte,
.arg = old,
};
rmap_walk(new, &rwc);
}
用到RMAP机制,核心回调函数是rmap_one
,实现函数是remove_migration_pte
函数
- while循环,
page_vma_mapped_walk
遍历页表,通过虚拟地址找到对应的PTE - 根据新页面和vma属性生成一个PTE
- 新生成的PTE映射到原来映射的页表,完成PTE迁移,用户就可以通过原来的PTE访问新页面
- 把新页面放到RMAP系统中
- 调用
update_mmu_cache
更新相应高速缓存
四、迁移非LRU页面
如果驱动想实现页面迁移,必须在页面地址空间方法集address_spacew_operations
中实现三个方法(以zsmalloc
模块为例):
static const struct address_space_operations zsmalloc_aops = {
.isolate_page = zs_page_isolate,
.migratepage = zs_page_migrate,
.putback_page = zs_page_putback,
};
1.isolate_page方法
页面迁移的isolate_movable_page
会调用这个方法分离页面,分离后页面被标记为PG_isolated
,这样其他页面在分离的时候就会忽略这个页面。页面分离成功之后,页面迁移机制可以使用page数据结构中的lru成员。
2.migratepage方法
页面分离之后,页面迁移机制就会调用这个方法来迁移页面。迁移旧的页面内容到新页面中,设置page对应的成员和属性。驱动实现方法之后调用__ClearPageMovable
函数清除PAGE_MAPPING_MOVABLE
标志位。
3.putback页面方法
页面迁移失败时,这个方法把页面迁移回原来的地方。
驱动程序不能直接操作PAGE_MAPPING_MOVABLE
标志位,应该使用内核提供的接口函数:
__PageMovable
用于分辨是传统LRU页面还是非LRU页面:
static __always_inline int __PageMovable(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) ==
PAGE_MAPPING_MOVABLE;
}
__SetPageMovable
设置页面为非LRU页面:
void __SetPageMovable(struct page *page, struct address_space *mapping)
{
page->mapping = (void *)((unsigned long)mapping | PAGE_MAPPING_MOVABLE);
}
__ClearPageMovable
清除页面的PAGE_MAPPING_MOVABLE
标志位:
void __ClearPageMovable(struct page *page)
{
page->mapping = (void *)((unsigned long)page->mapping &
PAGE_MAPPING_MOVABLE);
}