Linux内存管理(十): 页面迁移

kernel: 5.10
Arch: aarch64


页面迁移

页面迁移可以指定一个进程的页面至其指定的内存节点上。它的设计初衷是为了:通过将页面移动到该进程所处的NUMA节点上来减少内存访问的延迟。后来内存规整和内存热插拔等场景都使用了此功能。

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)

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

MIGRATE_ASYNC异步迁移,过程中不会发生阻塞,
MIGRATE_SYNC_LIGHT轻度同步迁移,允许大部分的阻塞操作,唯独不允许脏页的回写操作
MIGRATE_SYNC同步迁移,迁移过程会发生阻塞,若需要迁移的某个page正在writeback或被locked会等待它完成
MIGRATE_SYNC_NO_COPY同步迁移,但不等待页面的拷贝过程。页面的拷贝通过回调migratepage(),过程可能会涉及DMA

reason: 迁移因素

MR_COMPACTION内存规整导致的迁移
MR_MEMORY_FAILURE当内存出现硬件问题(ECC校验失败等)时触发的页面迁移(memory–failure.c)
MR_MEMORY_HOTPLUG内存热插拔导致的迁移
MR_SYSCALL应用层主动调用migrate_pages()或move_pages()触发的迁移。
MR_MEMPOLICY_MBIND调用mbind系统调用设置memory policy时触发的迁移
MR_NUMA_MISPLACEDnuma balance触发的页面迁移
MR_CONTIG_RANGE调用alloc_contig_range()为CMA或HugeTLB分配连续内存时触发的迁移(和compact相关)。

从migrate reasons可以看出, 内存规整、NUMA balance、内存热插拔、CMA、HugeTLB等是应用页面迁移的主要场景。

内核实现

migrate_pages()函数的主要流程如下:
在这里插入图片描述
LRU页面的迁移:

  1. 分配new page
  2. 获取old page的页面锁PG_locked
  3. 若是正在writeback的页面,则根据迁移模式判断是否等待页面wirteback(MIGRATE_SYNC_LIGHTMIGRATE_ASYNC不等待)
  4. 获取new page的页面锁PG_locked
  5. 调用try_to_unmap_page解除old page的页表映射
  6. 调用move_to_new_page拷贝old page的内容和struct page元数据到new page
  7. 调用remove_migration_ptes迁移页表,通过反向映射机制RMAP来建立new page的映射关系。

在这里插入图片描述
可以发现, 页面迁移不是简单的把一个page从A位置移动到B位置, 它的本质是一个分配新页面, 将旧页面的内容拷贝至新页面, 解除旧页面的映射关系,并将映射关系映射到新页面,最后释放旧页面的过程。

为什么页面迁移的过程中都需要获取old page和 new page的锁呢?
试想一下, 我们将old page A的数据拷贝到B, 取消了A的映射, 如果此时B的映射还没有建立, 这个时候产生地址访问会发生什么?

理论上虚拟地址没有对应物理地址的映射,应该产生一个page fault, 在page fault 处理流程中, 也会尝试获取PG_locked, 如果获取不到,就会重新retry 缺页的流程。

/*
 * lock_page_or_retry - Lock the page, unless this would block and the
 * caller indicated that it can handle a retry.
 *
 * Return value and mmap_lock implications depend on flags; see
 * __lock_page_or_retry().
 */
static inline int lock_page_or_retry(struct page *page, struct mm_struct *mm,
				     unsigned int flags)
{
	might_sleep();
	return trylock_page(page) || __lock_page_or_retry(page, mm, flags);
}

所以migrate_pages流程通过try_lockpage, 能够让page的移动看上去是无缝连接的过程。

非LRU页面的迁移:
bda807d44(“mm: migrate: support non-lru movable page migration”), 该补丁使得驱动中用到的页面也可以支持迁移。
从该补丁可知, 如果一个驱动想要支持页面迁移, 那么它必须实现struct address_space_operations 数据结构中的三个方法:

struct address_space_operations {
	..
	int (*migratepage) (struct address_space *,
			struct page *, struct page *, enum migrate_mode);
	bool (*isolate_page)(struct page *, isolate_mode_t);
	void (*putback_page)(struct page *);
	..
}
  1. bool (*isolate_page)();
    在页面迁移的某些场景中,如memory hotplugmemory compaction 会调用isolate_memory_page()函数来分离页面, 当页面分离之后,这些页面会被标记成PG_isolated, 这样其他CPU在并发分离页面时会忽略这个页面。

  2. int (*migratepage)();
    页面成功完成分离后, 内存管理的页面迁移就会调用migratepage 来迁移页面, 如上述流程图中所示。migratepage的作用是将旧页面的内容移动到新页面,并设置new page的字段。 在完成页面迁移的动作后,驱动还需要`__ClearPageMovable(page), 来指old page不再可移动。

  3. void (*putback_page)( );
    在页面迁移失败时, 驱动需要把分离的页面返回到自己的数据结构中。

此外驱动还需要支持两个标志位。

  1. PG_movable
    通过__SetPageMovable() 函数可以设置改标志位, 该函数会位page->mapping的低bit设置PAGE_MAPPING_MOVABLE, 可以用于判断是否为movable的non-lru页面。
  2. PG_isolated
    该标志位是page数据结构的新增标志位,主要是为了防止多个CPU同时分离同1个页面。如果驱动发现某一个页面是PG_isolated, 说明页面迁移机制已经分离了该页面, 驱动程序就不能在使用这个页面的page中的lru成员。

Numa balance中的页面迁移

现在我们结合Numa balance进行分析, 看看页面迁移的实际应用。
内核会周期性的扫描task的地址空间:

task_tick_fair  //周期性更新
	task_tick_numa
		init_task_work(work, task_numa_work);
		task_work_add(curr, work, true);

numa balance会在周期性的tick更新中穿插一个task, 该task会在task_work_run()执行时被调用。

task_numa_work()的核心函数是change_prot_numa()

unsigned long change_prot_numa(struct vm_area_struct *vma,
			unsigned long addr, unsigned long end)
{
	int nr_updated;

	nr_updated = change_protection(vma, addr, end, PAGE_NONE, MM_CP_PROT_NUMA);
	if (nr_updated)
		count_vm_numa_events(NUMA_PTE_UPDATES, nr_updated);

	return nr_updated;
}

change_protection() 会将所有映射到VMA的PTE页表项该为PAGE_NONE,使得下次进程访问页表的时候产生缺页中断,
handle_pte_fault() 函数会由于缺页中断的机会调用到do_numa_page(),

tatic int handle_pte_fault(struct vm_fault *vmf)
{
	if (pte_protnone(vmf->orig_pte) && vma_is_accessible(vmf->vma))
		return do_numa_page(vmf);

do_numa_page()选择更好的node并进行迁移

static vm_fault_t do_numa_page(struct vm_fault *vmf)
{
	 //修改页表为原始权限,在下次扫描到该页时访问不再发生异常
	pte = pte_modify(old_pte, vma->vm_page_prot);

	last_cpupid = page_cpupid_last(page);
	page_nid = page_to_nid(page);
	// 需要迁移目的node id。如果等于-1,则表示不用迁移
	target_nid = numa_migrate_prep(page, vma, vmf->address, page_nid,
			&flags);
	pte_unmap_unlock(vmf->pte, vmf->ptl);
	if (target_nid == NUMA_NO_NODE) {
		put_page(page);
		goto out;
	}

	/* Migrate to the requested node */
	migrated = migrate_misplaced_page(page, vma, target_nid);
}
  • numa_migrate_prep()会根据mbind设置的策略决定目标node,在可迁移的情况下还会根据numa balance当前缺页统计来决定是否迁移。
    主要通过group_faults_cpu()来对比本节点和target节点的page faults数量;
  • migrate_misplaced_page() 会调用migate_pages进行实际的页面迁移工作

我们可以通过 cat /proc/vmstat | grep numa 来观察一些页面迁移的指标

numa_hint_faults            //page fault数
numa_hint_faults_local      //page fault发生在本地的数
numa_pages_migrated //迁移页的数

如何避免页面迁移?

假设进程申请了某个page A, 然后将该地址通过参数的方式传递给设备使用, 如果在设备使用前, page A被迁移到page B, 那么设备继续使用之前的地址就会出问题;

这种情况在使用用户态DMA的时候比较常见;

那么该如何避免页面迁移呢?
我们回到之前的介绍, 调用move_to_new_page时会拷贝old page的内容和struct page元数据到new page
move_to_new_page() -> migrate_page() -> migrate_page_move_mapping()

int migrate_page_move_mapping(struct address_space *mapping,
		struct page *newpage, struct page *page, int extra_count)
{
	XA_STATE(xas, &mapping->i_pages, page_index(page));
	struct zone *oldzone, *newzone;
	int dirty;
	int expected_count = expected_page_refs(mapping, page) + extra_count;
	int nr = thp_nr_pages(page);

	if (!mapping) {
		/* Anonymous page without mapping */
		if (page_count(page) != expected_count)          ------- (1)
			return -EAGAIN;

		/* No turning back from here */
		newpage->index = page->index;
		newpage->mapping = page->mapping;
		if (PageSwapBacked(page))
			__SetPageSwapBacked(newpage);

		return MIGRATEPAGE_SUCCESS;
	}

	oldzone = page_zone(page);
	newzone = page_zone(newpage);

	xas_lock_irq(&xas);
	if (page_count(page) != expected_count || xas_load(&xas) != page) {   ------ (2)
		xas_unlock_irq(&xas);
		return -EAGAIN;
	}

	if (!page_ref_freeze(page, expected_count)) {
		xas_unlock_irq(&xas);
		return -EAGAIN;
	}

从(1)和 (2) 可以看出, 在做页面迁移时, 会先check page的refcnt是否符合预期;

所以我们可以通过类似pin_user_pages()这样的实现, “不合理”的增大page的引用计数, 来实现避免页面迁移的操作。

pin_user_pages()-> __gup_longterm_locked() -> __get_user_pages() -> try_grab_page()
在这里插入图片描述
GUP_PIN_COUNTING_BIAS的值是1024;

所以在做用户态DMA时, 可以通过GUP来pin住memory, 防止DMA的地址被swap、迁移或释放。

参考资料

  1. https://www.kernel.org/doc/html/latest/vm/page_migration.html
  2. Linux内存管理:页面迁移
  3. Documentation/vm/page_migration
  4. 论Linux的页迁移(Page Migration)完整版
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内核的内存管理一直是一个重要的领域,因为内存管理对于操作系统的性能和稳定性都有着很大的影响。随着计算机系统的不断发展,内存管理也需要不断地优化和改进。下面列出了一些 Linux 内核内存管理的优化措施: 1. Slab 分配器的优化:Slab 分配器是 Linux 内核中的一种高效的内存分配器,它可以在不同的缓存层次上进行对象的缓存和分配。在 Linux 内核的不同版本中,Slab 分配器都得到了很多的优化和改进,例如优化了缓存的查找和分配算法,增加了缓存的预先分配等。 2. HugeTLB 页面的使用:HugeTLB 页面Linux 中的一种大页面,相比于普通页面,它具有更高的性能和更少的 TLB 缓存失效。Linux 内核中可以通过 HugeTLB 来分配大块的物理内存,并将其映射到用户空间,从而提高应用程序的性能。 3. 内存压缩:内存压缩是 Linux 内核中的一种新特性,它可以将内存中的数据进行压缩,从而减少内存的使用量。内存压缩可以在内存紧张的情况下,提高系统的可用内存量,并减少系统的交换行为。 4. 内存回收的改进:内存回收是 Linux 内核中的一个重要功能,它可以回收未使用的内存,并将其重新分配给需要的应用程序。Linux 内核中的内存回收机制也得到了很多的优化和改进,例如增加了 KSM(Kernel Same-Page Merging)机制来减少内存的使用量,增加了 THP(Transparent Huge Pages)机制来提高内存的使用效率等。 5. NUMA(Non-Uniform Memory Access)优化:NUMA 是一种多处理器架构,它包括多个处理器和多个内存控制器,内存的访问时间不同。Linux 内核中可以通过 NUMA 优化来提高系统的性能,例如通过 NUMA 映射来减少不必要的内存访问和数据迁移等。 6. 内存管理的锁优化:内存管理Linux 内核中的一个核心功能,它需要对内存的访问进行管理,因此需要使用锁来保证数据的一致性。在 Linux 内核中,内存管理的锁也得到了很多的优化和改进,例如采用了读写锁和自旋锁等机制来提高锁的效率,减少锁的争用等。 以上是一些 Linux 内核内存管理的优化措施,这些措施在不同的场景下可以提高系统的性能和稳定性,让 Linux 操作系统更加出色。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值