Linux 内存管理(九)页面迁移和内存规整

原图

声明本文转载自知乎 Linux 内存管理:页面迁移Linux 内存管理 (16) 内存规整 内存相关文章

页面迁移

概述

页面迁移 (migrate) 是内存管理中很多功能实现的基础,比方说内存规整、NUMA balance、内存热插拔、CMA、HugeTLB、内存气球等等,本文主要讲一下页面迁移的主要流程和应用。

页面迁移的 API

migrate_pages( ) 这是页面迁移在内核态的主要接口,内核中涉及到页面迁移的功能大都会调到它。当然,在用户空间也存在着内存迁移相关的系统调用,最终也会调到它。这里我们通过 migrate_pages( ) 的几个形参展开全文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* migrate_pages - migrate the pages specified in a list, to the free pages
* supplied as the target for the page migration
*
* @from: The list of pages to be migrated.
* @get_new_page: The function used to allocate free pages to be used
* as the target of the page migration.
* @put_new_page: The function used to free target pages if migration
* fails, or NULL if no special handling is necessary.
* @private: Private data to be passed on to get_new_page()
* @mode: The migration mode that specifies the constraints for
* page migration, if any.
* @reason: The reason for page migration.
*/
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)

形参含义根据上面的注释很好理解,我们单独说下 mode 和 reason,他们分别表示迁移模式和迁移原因。

迁移模式

迁移模式主要会影响迁移过程中对一些行为的过滤:

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

举例:内存规整 (compact) 中会调用 migrate_pages(),同时也会设置迁移模式(位于 compact_control->mode)。若是 sysfs 主动触发的内存规整会用 MIGRATE_SYNC 模式;若是 kcompactd 触发的规整会用 MIGRATE_SYNC_LIGHT 模式;若是内存分配 slowpath 中触发的会根据 compact prior 去设置用 MIGRATE_ASYNC 或 MIGRATE_SYNC_LIGHT 模式。

迁移原因

迁移原因主要使用来记录是什么功能触发了迁移的行为,毕竟页面迁移对系统本身是个不小的 overhead,所以知道迁移的原因很有必。

1
2
3
4
5
6
7
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_MISPLACED //numa balance 触发的页面迁移 (node 之间)
MR_CONTIG_RANGE //调用 alloc_contig_range() 为 CMA 或 HugeTLB 分配连续内存时触发的迁移(和 compact 相关)

延伸:对上面 MR_SYSCALL 迁移原因中提到的两个 API(位于 libnuma 库)的简单介绍。

1
2
3
4
5
// 含义:把某 pid 进程的所有页面从源 numa node 迁移到目标 numa nodes 组中,对应系统调用 sys_migrate_pages()
migrate_pages() //和 kernel 中接口同名,别搞混了

// 含义:把进程到部分页面迁移目标 numa node 中,对应系统调用 sys_move_pages()
move_pages()

migrate_pages() 介绍

通过上面对迁移原因的介绍,我们知道了页面迁移的原因各种各样,这里我们通过内存规整 (memory compact) 这样一个实际的例子展开介绍,下图主要体现了内存规整和页面迁移的关系:

通过上图,我们看出页面迁移在内存规整中举足轻重的地位。下面则是内存规整调用 migrate_pages() 的函数关系:

1
2
3
compact_zone           //内存规整
->migrate_pages //页面迁移的入口函数
->unmap_and_move //页面迁移的核心实现

现在切入重点,页面迁移 (migrate) 过程做了那些事情:

迁移前的准备

  • 获取 old page 的页面锁 PG_locked (异步模式拿不到锁就直接跳过此页面)
  • 若是正在 writeback 的页面,则根据迁移模式判断是否等待页面 wirteback(MIGRATE_SYNC_LIGHT 和 MIGRATE_ASYNC 不等待)
  • 获取 new page 的页面锁 PG_locked (new page 正常情况下不会获取失败)

解除 old page 的页表映射

调用 try_to_unmap(),通过反向映射机制解除 old page 所有相关的 PTEs

设置 new page 的页面内容、元数据及映射关系

调用 move_to_new_page 拷贝 old page 的内容和 struct page 元数据到 new page,并通过反向映射机制建立 new page 的映射关系。

页面迁移的收尾工作

  • 迁移完成释放新旧页面的 PG_locked
  • 设置迁移原因
  • 调用 put_page 释放 old page 引用计数 (_refcount 减 1)

补充:什么样的页面才支持迁移呢?

  • lru 上的 page,因为上面挂的是 user spcace 用的 pages,都是从 buddy 分配器 migrate type 为 movable 或 reclaim 的 pageblock 上分来的
  • no-lru 但是 movable 的页面。no-lru 的页面通常是为 kernel space 分配的 page,这种页面通常是 unmovable 的,但是下面这个 commit 使得驱动中用到的页面也可以支持迁移。但是在驱动实现的过程中需要置位 page->mapping 中的 second bit,并且实现 page->mapping->a_ops 中的相关方法

内存规整

内存碎片的产生:伙伴系统以页为单位进行管理,经过大量申请释放,造成大量离散且不连续的页面。这时就产生了很多碎片。 n 内存规整也即内存碎片整理,内存碎片也是以页面为单位的。实现基础是内存页面按照可移动性进行分组。内存规整的实现基础是页面迁移。 Linux 内核以 pageblock 为单位来管理页的迁移属性。

内存规整的触发

下面是内存页面分配,以及分配失败之后采取的措施,以便促成分配成功。 可以看出采取的措施,越来越重。首先采用 kswapd 来进行页面回收,然后尝试页面规整、直接页面回收,最后是 OOM 杀死进程来获取更多内存空间

1
2
3
4
5
6
7
8
9
alloc_pages-------------------------------------页面分配的入口
->__alloc_pages_nodemask
->get_page_from_freelist--------------------直接从 zonelist 的空闲列表中分配页面
->__alloc_pages_slowpath--------------------在初次尝试分配失败后,进入 slowpath 路径分配页面
->wake_all_kswapds------------------------唤醒 kswapd 内核线程进行页面回收
->get_page_from_freelist------------------kswapd 页面回收后再次进行页面分配
->__alloc_pages_direct_compact------------进行页面规整,然后进行页面分配
->__alloc_pages_direct_reclaim------------直接页面回收,然后进行页面分配
->__alloc_pages_may_oom-------------------尝试触发 OOM

另一条路径是在 kswapd 的 balance_pgdat 中会判断是否需要进行内存规整。

1
2
3
kswapd
->balance_pgdat-------------------------------遍历内存节点的 zone,判断是否处于平衡状态即 WMARK_HIGH。
->compact_pgdat-----------------------------针对整个内存节点进行内存规整

其中 compact_pddat->__compact_pgdat->compact_zone,最终的实现和__alloc_pages_direct_compact 调用 compact_zone 一样。

内存规整相关节点

内存规整相关有两个节点,compact_memory 用于触发内存规整;extfrag_threshold 影响内核决策是采用内存规整还是直接回收来满足大内存分配。

节点入口代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct ctl_table vm_table[] = {
...
#ifdef CONFIG_COMPACTION
{
.procname = "compact_memory",
.data = &sysctl_compact_memory,
.maxlen = sizeof(int),
.mode = ,
.proc_handler = sysctl_compaction_handler,
},
{
.procname = "extfrag_threshold",
.data = &sysctl_extfrag_threshold,
.maxlen = sizeof(int),
.mode = ,
.proc_handler = sysctl_extfrag_handler,
.extra1 = &min_extfrag_threshold,
.extra2 = &max_extfrag_threshold,
},

#endif /* CONFIG_COMPACTION */
...
{ }
}
  • /proc/sys/vm/compact_memory: 打开 compaction Tracepoint:echo 1 > /sys/kernel/debug/tracing/events/compaction/enable
    触发内存规整:sysctl -w vm.compact_memory=1
    查看 Tracepoint:cat /sys/kernel/debug/tracing/trace

  • /proc/sys/vm/extfrag_threshold: 在 compact_zone 中调用函数 compaction_suitable->__compaction_suitable 进行判断是否进行内存规整
    和 extfrag_threshold 相关部分如下,如果当前 fragindex 不超过 sysctl_extfrag_threshold,则不会继续进行内存规整
    所以这个参数越小越倾向于进行内存规整,越大越不容易进行内存规整

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static unsigned long __compaction_suitable(struct zone *zone, int order,
    int alloc_flags, int classzone_idx)
    {
    ...
    fragindex = fragmentation_index(zone, order);
    if (fragindex >= 0 && fragindex <= sysctl_extfrag_threshold)
    return COMPACT_NOT_SUITABLE_ZONE;

    return COMPACT_CONTINUE;
    }

    设置 extfrag_threshold:sysctl -w vm.extfrag_threshold=500

  • 其它 Debug 信息: /sys/kernel/debug/extfrag/extfrag_index /sys/kernel/debug/extfrag/unusable_index

参考文献

https://zhuanlan.zhihu.com/p/270366827
https://www.bbsmax.com/A/ZOJPrQgodv/