内存管理之页面迁移

页面迁移系统调用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函数主要操作如下:

  1. 调用get_new_page分配新的页面
  2. 刚分配的页面调用put_new_page回调函数
  3. 调用__unmap_and_move迁移页面到新分配的页面中
  4. 返回值为MIGRATEPAGE_SUCCESS说明迁移成功,释放页面
  5. 迁移未成功,把页面重新添加到可移动页面里,释放刚才新分配的页面
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 = 1
  • mode:迁移模式

__unmap_and_move函数主要操作如下:

  1. __PageMovable函数判断页面是否属于非LRU页面,通过page数据结构的mapping成员是否是设置了PAGE_MAPPING_MOVABLE来判断。is_lru表示页面属于传统LRU页面

  2. trylock_page函数尝试加锁,如果返回false,表示其他进程已持有这个锁

    • 如果申请锁不成功,当前不是强制迁移force = 0或迁移模式是MIGRATE_ASYNC,则直接忽略这个页面
    • 如果进程设置了PF_MEMALLOC,说明进程在直接内存压缩的内核路径上,直接忽略掉该页面。

    除了上面两种情况,其余情况调用lock_page函数来等待页锁被释放

  3. 处理正在回写的页面,即设置了PG_writeback标志位的页面。当设置了MIGRATE_ASYNCMIGRATE_SYNC_LIGHT且设置强制迁移,才会等待页面回写完成,否则该页面直接忽略不会迁移。wait_on_page_writeback函数等待页面回写完成

  4. 在页面迁移过程中,无法知道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
  5. 处理退出

    • 对于非LRU页面,调用put_pagenewpage_refcount减1
    • 对于传统LRU页面,把newpage添加到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_vmapage_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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值