From c979d8c1ca642e3cf161eb93e08804e5da9e5ee3 Mon Sep 17 00:00:00 2001 From: gatieme Date: Tue, 21 Sep 2021 22:00:29 +0800 Subject: [PATCH] description/memory: khugepaged --- study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md index e600af2..d18db99 100644 --- a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md +++ b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md @@ -417,7 +417,7 @@ MTE 实现了锁和密钥访问内存. 这样在内存访问期间, 可以在内 |:----:|:----:|:---:|:----:|:---------:|:----:| | 2006/08/21 | Mel Gorman | [Sizing zones and holes in an architecture independent manner V9](https://lore.kernel.org/patchwork/cover/63170) | NA | v1 ☑ v2.6.19-rc1 | [PatchWork 0/2](https://lore.kernel.org/patchwork/cover/63170) | | 2007/12/11 | Mel Gorman | [Use two zonelists per node instead of multiple zonelists v11r2](https://lore.kernel.org/patchwork/cover/99109) | 优化分配器处理区域列表, 区域列表指示分配目标区域的顺序. 类似地, 页面的直接回收会在区域数组上迭代. 为了保持一致性, 这组补丁将直接回收转换为使用分区列表, 并简化 zonelist 迭代器.
将每个节点的多个(两组)分区列表替换为两个分区列表, 一组用于系统中的每个分区类型, 另一组用于 GFP_THISNODE 分配. 根据 gfp 掩码允许的分区, 选择其中一个分区列表. 所有这些分区列表都会消耗内存并占用缓存线. | v1 ☑ v2.6.19-rc1 | [PatchWork v11r2,0/6](https://lore.kernel.org/patchwork/cover/99109) | -| 2021/08/10 | Baoquan He | [Avoid requesting page from DMA zone when no managed pages](https://lore.kernel.org/patchwork/cover/1474378) | 在当前内核的某些地方, 它假定 DMA 区域必须具有托管页面, 并在启用 CONFIG_ZONE_DMA 时尝试请求页面. 但这并不总是正确的. 例如, 在 x86_64 的 kdump 内核中, 在启动的早期阶段, 只显示并锁定低 1M, 因此在 DMA 区域中根本没有托管页面. 如果从 DMA 区域请求页面, 此异常将始终导致页面分配失败. 这造成在 x86_64 的 kdump 内核中, 使用 GFP_DMA 创建的 atomic_pool_dma 会导致页面分配失败. dma-kmalloc 初始化也会导致页面分配失败. | v2 ☐ | [PatchWork RFC,v2,0/5](https://patchwork.kernel.org/project/linux-mm/cover/20210810094835.13402-1-bhe@redhat.com) | +| 2021/08/10 | Baoquan He | [Avoid requesting page from DMA zone when no managed pages](https://lore.kernel.org/patchwork/cover/1474378) | 在当前内核的某些地方, 它假定 DMA 区域必须具有托管页面, 并在启用 CONFIG_ZONE_DMA 时尝试请求页面. 但这并不总是正确的. 例如, 在 x86_64 的 kdump 内核中, 在启动的早期阶段, 只显示并锁定低 1M, 因此在 DMA 区域中根本没有托管页面. 如果从 DMA 区域请求页面, 此异常将始终导致页面分配失败. 这造成在 x86_64 的 kdump 内核中, 使用 GFP_DMA 创建的 atomic_pool_dma 会导致页面分配失败. dma-kmalloc 初始化也会导致页面分配失败. | v2 ☐ | [PatchWork RFC,v2,0/5](https://patchwork.kernel.org/project/linux-mm/cover/20210810094835.13402-1-bhe@redhat.com) | @@ -537,7 +537,7 @@ a206231bbe6 [PATCH] hot-n-cold pages: page allocator core | 2021/03/25 | Mel Gorman | [Introduce a bulk order-0 page allocator with two in-tree users](https://lore.kernel.org/patchwork/cover/1399888) | 批量 order-0 页面分配器, 目前 sunrpc 和 network 页面池是这个特性的第一个用户 | v6 ☑ 5.13-rc1 | [RFC](https://lore.kernel.org/patchwork/cover/1383906)
*-*-*-*-*-*-*-*
[v1](https://lore.kernel.org/patchwork/cover/1385629)
*-*-*-*-*-*-*-*
[v2](https://lore.kernel.org/patchwork/cover/1392670)
*-*-*-*-*-*-*-*
[v3](https://lore.kernel.org/patchwork/cover/1393519)
*-*-*-*-*-*-*-*
[v4](https://lore.kernel.org/patchwork/cover/1394347)
*-*-*-*-*-*-*-*
[v5](https://lore.kernel.org/patchwork/cover/1399888)
*-*-*-*-*-*-*-*
[v6](https://lore.kernel.org/patchwork/cover/1402140) | | 2021/03/29 | Mel Gorman | [Use local_lock for pcp protection and reduce stat overhead](https://lore.kernel.org/patchwork/cover/1404513) | Bulk memory allocation 的第一组修复补丁, PCP 与 vmstat 共享锁定要求, 这很不方便, 并且会导致一些问题. 可能因为这个原因, PCP 链表和 vmstat 共享相同的 Per CPU 空间, 这意味着 vmstat 可能跨 CPU 更新包含 Per CPU 列表的脏缓存行, 除非使用填充. 该补丁集拆分该结构并分离了锁. | RFC ☐ | [RFC](https://lore.kernel.org/patchwork/cover/1404513) | | 2020/03/20 | Mel Gorman | [mm/page_alloc: Add a bulk page allocator -fix -fix](https://lore.kernel.org/patchwork/cover/1405057) | Bulk memory allocation 的第二组修复补丁 | v1 ☐ | [RFC](https://lore.kernel.org/patchwork/cover/1405057) | -| 2020/03/20 | Mel Gorman | [mm/page_alloc: enable alloc bulk when page owner is on](https://lore.kernel.org/patchwork/cover/1461499) | 上一个 alloc bulk 版本有一个bug, 当 page_owner 打开时, 系统可能会由于 irq 禁用上下文中的 alloc bulk 调用 prep_new_page() 而崩溃, 这个问题是由于 set_page_owner() 在 local_irq 关闭的情况下通过 GFP_KERNEL 标志分配内存来保存栈信息导致的. 所以, 我们不能假设 alloc 标志应该与 new page 相同, prep_new_page() 应该准备/跟踪页面 gfp, 但不应该使用相同的gfp来获取内存, 这取决于调用方. 现在, 这里有两个gfp标志,alloc_gfp 用于分配内存, 取决于调用方, page_gfp 是 page 的 gfp, 用于跟踪/准备自身. 在大多数情况下, 两个 flag 相同是可以的, 在 alloc_pages_bulk() 中, 使用 GFP_ATOMIC, 因为 irq 被禁用. | v1 ☐ | [RFC](https://lore.kernel.org/patchwork/cover/1461499) | +| 2020/03/20 | Mel Gorman | [mm/page_alloc: enable alloc bulk when page owner is on](https://lore.kernel.org/patchwork/cover/1461499) | 上一个 alloc bulk 版本有一个bug, 当 page_owner 打开时, 系统可能会由于 irq 禁用上下文中的 alloc bulk 调用 prep_new_page() 而崩溃, 这个问题是由于 set_page_owner() 在 local_irq 关闭的情况下通过 GFP_KERNEL 标志分配内存来保存栈信息导致的. 所以, 我们不能假设 alloc 标志应该与 new page 相同, prep_new_page() 应该准备/跟踪页面 gfp, 但不应该使用相同的gfp来获取内存, 这取决于调用方. 现在, 这里有两个gfp标志, alloc_gfp 用于分配内存, 取决于调用方, page_gfp 是 page 的 gfp, 用于跟踪/准备自身. 在大多数情况下, 两个 flag 相同是可以的, 在 alloc_pages_bulk() 中, 使用 GFP_ATOMIC, 因为 irq 被禁用. | v1 ☐ | [RFC](https://lore.kernel.org/patchwork/cover/1461499) | @@ -1576,7 +1576,7 @@ Refault Distance 算法是为了解决前者, 在第二次读时, 人为地把 p | workingset_eviction() | 在不活跃链表末尾的页面会被踢出LRU链表并被释放, 在被踢出LRU链表时, 通过 [workingset_eviction()](https://elixir.bootlin.com/linux/v3.15/source/mm/workingset.c#L213) 把当前的 zone->inactive_age 计数保存到该页对应的 radix_tree 的 shadow 中. | 高速缓存页面 [`__remove_mapping`, v3.15](https://elixir.bootlin.com/linux/v3.15/source/mm/vmscan.c#L526) 流程中释放页面时调用. | [`__remove_mapping`, v5.9](https://elixir.bootlin.com/linux/v5.9/source/mm/vmscan.c#L901) 流程中对于 PageSwapCache(page) 释放时调用 | | workingset_activation() | 当前 zone 中 INACTIVE LRU 中的页面在被激活到 ACTIVE LRU 时调用, 会调用 workingset_activation() 更新(增加) zone->inactive_age 的计数 | 1. 当在文件缓存不活跃链表(LRU_INACTIVE_FILE)里的页面被再一次读取时, 会被激活到活跃 LRU 链表, 这时候会通过 mark_page_accessed(page) -=> workingset_activation(page) 更新(增加) zone->inactive_age 的计数.
*-*-*-*-*-*-*-*
2. 当 page cache 第二次读取时, 在 [add_to_page_cache_lru()](https://elixir.bootlin.com/linux/v3.15/source/mm/filemap.c#L636) 中, 首先会通过 [workingset_refault()](https://elixir.bootlin.com/linux/v3.15/source/mm/workingset.c#L231) -=> [unpack_shadow](https://elixir.bootlin.com/linux/v3.15/source/mm/workingset.c#L164) 计算 Refault Distance, 并且判断是否需要把 page cache 加入到活跃链表中, 以避免下一次读之前被踢出 LRU 链表. 如果发现需要将页面激活, 则调用 [workingset_activation()](https://elixir.bootlin.com/linux/v3.15/source/mm/filemap.c#L638). 其中 unpack_shadow() 函数只把该页面之前存放的 shadow 值重新解码, 得出了图中 T1 时刻的 inactive_age 值, 然后把当前的 inactive_age 减去 T1, 得到 Refault Distance. | 同高速缓存页面 1, mark_page_accessed(page) 激活页面时调用. | | workingset_refault() | 计算页面的 Refault Distance, 并判断是否需要把页面加入到活跃 LRU 链表中. | add_to_page_cache_lru() 中激活高速缓存页面的时候, 通过 workingset_refault() 计算 Refault Distance. | 1. 匿名页面缺页处理时, 如果页面 pte 不为 0, 表示此时 pte 的内容所对应的页面在 swap 空间中, 缺页异常时会通过 do_swap_page() 来分配页面. 在 do_swap_page() 中 get_shadow_from_swap_cache() 获取到页面后, 通过 [workingset_refault()](https://elixir.bootlin.com/linux/v5.9/source/mm/memory.c#L3298) 判断页面是否需要加到活跃 LRU.
*-*-*-*-*-*-*-*
2. `__read_swap_cache_async()` 预读 SWAP 页面的时候时, 同样需要通过 [workingset_refault()](https://elixir.bootlin.com/linux/v5.9/source/mm/swap_state.c#L503) 判定要将页面加入到哪个 LRU 链表中去. | -| workingset_init() | [449dd6984d0e mm: keep page cache radix tree nodes in check](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=449dd6984d0e47643c04c807f609dd56d48d5bcc) 引入, 3.15 之前, 页面缓存基数树节点在回收它们的页面指针后会被释放. 但是现在, 将 shadow 存储在它们的位置上, 只有在索引节点本身被回收时才会回收 shadow. 这对于在回收了大量缓存后仍然在使用的较大文件来说是有问题的,这些文件中的任何页面实际上都没有再 refault. shadow 将只是停留在那里并浪费内存. 在最坏的情况下, shadow 会不断累积,直到机器耗尽内存.
为了控制这一点, 跟踪每个 numa 节点列表中只包含 shadow 的基数树节点. 使用每个 numa 而不是全局的, 是因为希望基数树节点本身是在本地分配的, 减少对其他独立缓存工作负载的跨节点引用. 引入一个简单的[收缩器 workingset_shadow_shrinker](https://elixir.bootlin.com/linux/v3.15/source/mm/workingset.c#L385) 将回收这些节点上的内存压力。 | NA | NA | +| workingset_init() | [449dd6984d0e mm: keep page cache radix tree nodes in check](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=449dd6984d0e47643c04c807f609dd56d48d5bcc) 引入, 3.15 之前, 页面缓存基数树节点在回收它们的页面指针后会被释放. 但是现在, 将 shadow 存储在它们的位置上, 只有在索引节点本身被回收时才会回收 shadow. 这对于在回收了大量缓存后仍然在使用的较大文件来说是有问题的, 这些文件中的任何页面实际上都没有再 refault. shadow 将只是停留在那里并浪费内存. 在最坏的情况下, shadow 会不断累积, 直到机器耗尽内存.
为了控制这一点, 跟踪每个 numa 节点列表中只包含 shadow 的基数树节点. 使用每个 numa 而不是全局的, 是因为希望基数树节点本身是在本地分配的, 减少对其他独立缓存工作负载的跨节点引用. 引入一个简单的[收缩器 workingset_shadow_shrinker](https://elixir.bootlin.com/linux/v3.15/source/mm/workingset.c#L385) 将回收这些节点上的内存压力. | NA | NA | @@ -1590,7 +1590,7 @@ Refault Distance 算法是为了解决前者, 在第二次读时, 人为地把 p | 2014/04/03 | Johannes Weiner | [mm: thrash detection-based file cache sizing v9](https://lwn.net/Articles/495543) | 实现基于 Refault Distance 算法的文件高速缓存页的工作集探测, 引入 WORKINGSET_REFAULT, WORKINGSET_ACTIVATE, WORKINGSET_NODERECLAIM 三种工作集. | v9 ☑ [3.15](https://kernelnewbies.org/Linux_3.15#head-dbe2430cd9e5ed1d3f2362367758cd490aba4b9d) | [PatchWork v9](https://lore.kernel.org/patchwork/cover/437949) | | 2016/01/24 | Vladimir Davydov / | [Make workingset detection logic memcg aware](https://lwn.net/Articles/586023) | 工作集探测感知 memcg.
目前, 工作集检测逻辑不是 memcg 感知的 - inactive_age 是每个 zone 域维护的. 因此, 如果使用了内存组, 则会随机激活错误的文件页. 这个补丁集使每个 lruvec 具有 inactive_age, 以便工作集检测将正确地工作在内存 cgroup 回收. | v2 ☐ | [PatchWork 0/3](https://lore.kernel.org/patchwork/cover/586023)
*-*-*-*-*-*-*-*
[2016/01/24 PatchWork v2](https://lore.kernel.org/patchwork/patch/638421/) | | 2016/01/29 | Johannes Weiner | [mm: workingset: per-cgroup thrash detection](https://lore.kernel.org/patchwork/cover/641469) | 这组补丁使工作集检测具备 cgroup 感知的能力, 因此当使用 cgroup 内存控制器时, 页面回收可以正常工作.
缓存抖动检测目前仅在系统级别工作, 而不在 cgroup 层次工作. 更糟糕的是, 由于将 refaults 与 active LRU 的全局数量相比较, 当 cgroup 的页面比其他组的页面更热时, 它们可能会错误地激活所有 refaults 的页面.
1. 将 refault 的统计 inactive_age 从区域移动到 lruvec.
2. 然后用 memcg ID 标记逐出条目. | v2 ☑ [4.6-rc1](https://kernelnewbies.org/Linux_4.20#Memory_management) | [2016/01/26 PatchWork 0/5](https://lore.kernel.org/patchwork/cover/639567)
*-*-*-*-*-*-*-*
[2016/01/29 PatchWork v2,0/5](https://lore.kernel.org/patchwork/cover/641469) | -| 2016/02/09 | Vladimir Davydov | [mm: workingset: make shadow node shrinker memcg aware](https://lwn.net/Articles/586023) | 工作集探测已经被 memcg 识别, 但阴影节点收缩器仍然是全局的. 因此, 一个小 cgroup 可能会消耗阴影节点的所有可用内存, 通过回收某个 cgroup 的阴影节点可能会损害其他 cgroup, 即使回收这些阴影节点中存储的距离没有任何效果. 为了避免这种情况, 我们需要使阴影节点收缩器也变成 memcg 感知的.
实际工作在本系列的补丁6中完成. 修补程序1和2为 memcg/shrinker 基础架构的更改做好准备. 补丁3只是一个附带的清理. 补丁4使基数树节点占位, 这是使阴影节点收缩器 memcg 感知所必需的. 补丁5在工作负载主要使用匿名页面的情况下减少了阴影节点的开销. | v2 ☑ 4.6-rc1 | [2016/02/07 PatchWork 0/5](https://lore.kernel.org/patchwork/cover/644646)
*-*-*-*-*-*-*-*
[2016/02/09 PatchWork v2,0/6](https://lore.kernel.org/patchwork/cover/645395) | +| 2016/02/09 | Vladimir Davydov | [mm: workingset: make shadow node shrinker memcg aware](https://lwn.net/Articles/586023) | 工作集探测已经被 memcg 识别, 但阴影节点收缩器仍然是全局的. 因此, 一个小 cgroup 可能会消耗阴影节点的所有可用内存, 通过回收某个 cgroup 的阴影节点可能会损害其他 cgroup, 即使回收这些阴影节点中存储的距离没有任何效果. 为了避免这种情况, 我们需要使阴影节点收缩器也变成 memcg 感知的.
实际工作在本系列的补丁6中完成. 修补程序1和2为 memcg/shrinker 基础架构的更改做好准备. 补丁3只是一个附带的清理. 补丁4使基数树节点占位, 这是使阴影节点收缩器 memcg 感知所必需的. 补丁5在工作负载主要使用匿名页面的情况下减少了阴影节点的开销. | v2 ☑ 4.6-rc1 | [2016/02/07 PatchWork 0/5](https://lore.kernel.org/patchwork/cover/644646)
*-*-*-*-*-*-*-*
[2016/02/09 PatchWork v2,0/6](https://lore.kernel.org/patchwork/cover/645395) | | 2016/04/04 | Johannes Weiner | [mm: support bigger cache workingsets and protect against writes](https://lore.kernel.org/patchwork/cover/664653) | 支持更大的缓存工作集并防止写入. 由于某些特定 PostgreSQL 工作负载存在问题, 做了两项更改.
1. 停止升级写入或重写到活动文件页列表中的页, 因为数据很有可能不会再次读取, 甚至可能在读取之前再次重写, 因此, 这些页面可能会占用活动文件列表中的空间, 这些空间可供频繁读取的页面使用.
2. 删除要求[非活动文件列表的最小文件缓存大小为 50% ](https://elixir.bootlin.com/linux/v4.6/source/mm/vmscan.c#L1929)的强制措施. 通过 workingset refault() 来进行探测, 可以使在较短时间间隔后再次访问的最近收回的文件页直接升级到活动列表. 有了这种机制, 我们可以(在更大的系统上)为活动文件列表提供更多的内存, 这样我们就可以在内存中缓存更多经常使用的文件页, 而不会让它们被流式写入、一旦使用流式文件读取等推出. 这个版本不同时允许在匿名内存使用的相同比率上增加活动列表, 这同样将有助于数据库工作负载, 其中只有一半的页面缓存可用于缓存数据库工作集. | v1 ☑ [4.7-rc1](https://kernelnewbies.org/Linux_4.7#Memory_management) | [PatchWork v1](https://lore.kernel.org/patchwork/cover/664653) | | 2016/06/24 | Johannes Weiner | [mm: fix vm-scalability regression in cgroup-aware workingset code](https://lore.kernel.org/patchwork/cover/692559) | commit 23047a96d7cf ("mm:workingset:per cgroup cache thrash detection") 在缓存逐出 workingset_evictio()、探测 workingset_refault() 和激活 workingset_activation() 路径中添加了 page->mem_cgroup 查找, 并[锁定到激活路径](https://elixir.bootlin.com/linux/v4.6/source/mm/workingset.c#L310), vm可伸缩性测试显示了-23%的回归.
虽然所讨论的测试是一种在实际工作负载中不会发生的人为最坏情况场景(并行读取两个稀疏文件, 只是为了敲碎 LRU 路径), 但仍然可以在这些路径中进行一些优化.
1. 内联查找函数以消除调用.
此外, 在计算激活次数时, 不需要持续 hold page->mem_cgroup, 我们只需要[保持 RCU 锁以防止 memcg 被释放](https://elixir.bootlin.com/linux/v4.8/source/mm/workingset.c#L316). | v1 ☑ 4.8-rc1 | [2016/06/22 PatchWork v1](https://lore.kernel.org/patchwork/cover/691734)
*-*-*-*-*-*-*-*
[2016/06/24 PatchWork rebase](https://lore.kernel.org/patchwork/patch/692559), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=55779ec759ccc3c12b917b3712a7716e1140c652) | @@ -2064,6 +2064,11 @@ Linux 内核在脏页数量到达一定门槛时, 或者用户在命令行输入 **2.6.38(2011年3月发布)** +| LWN 归档 | Index entries for this article | +|:--------:|:------------------------------:| +| 0 | [Huge pages](https://lwn.net/Kernel/Index/#Huge_pages) | +| 1 | [Memory management/Huge pages](https://lwn.net/Kernel/Index/#Memory_management-Huge_pages) | + [Transparent huge pages in 2.6.38](https://lwn.net/Articles/423584) [Transparent huge page reference counting](https://lwn.net/Articles/619738) @@ -2093,14 +2098,22 @@ https://lore.kernel.org/patchwork/cover/1118785 ------- +* khugepaged_max_ptes_none (`/sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none`) + +khugepaged 中如果发现当前连续的映射区间内有[超过 `khugepaged_max_ptes_none` 个页面是 pte_none 的](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1643), 则将其合并为大页. 参见 [linux v2.6.38, mm/huge_memory.c#L1643](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1643). + + | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | |:----:|:----:|:---:|:----:|:---------:|:----:| +| 2015/01/29 | Ebru Akagunduz | [mm: incorporate read-only pages into transparent huge pages](https://lore.kernel.org/patchwork/cover/538503) | 允许 THP 转换只读 pte, 就像 do_swap_page 在读取错误后留下的那些pte. 当在 2MB 范围内存在最多数量为 `khugepaged_max_ptes_none` 的 pte_none ptes 时, THP可以将4kB的页面压缩为一个 THP. 这个补丁增加了对只读页面的大页支持. 该补丁使用一个程序进行测试, 该程序分配了800MB的内存, 对其进行写入, 然后休眠. 迫使系统通过触摸其他内存来交换除190MB外的所有程序. 然后, 测试程序对其内存进行混合的读写操作, 并将内存交换回来. 没有补丁的情况下, 只有没有被换出的内存保留在 THP 中, 这相当于程序内存的 24%, 这个百分比并没有随着时间的推移而增加. 有了这个补丁, 经过 5分钟的等待, khugepageage 将 60% 的程序内存还原为 THP. | v4 ☑ [4.0-rc1](https://kernelnewbies.org/Linux_4.0#Memory_management) | [PatchWork v4](https://lore.kernel.org/patchwork/cover/538503), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=10359213d05acf804558bda7cc9b8422a828d1cd) | +| 2015/04/14 | Ebru Akagunduz | [mm: incorporate zero pages into transparent huge pages](https://lore.kernel.org/patchwork/cover/541944) | 通过大页允许零页面, 该补丁提高了 THP 的大页转换率. 目前, 当在 2MB 范围内存在最多数量为 khugepaged_max_ptes_none 的 pte_none ptes 时, THP可以将4kB的页面压缩为一个 THP. 这个补丁支持了将零页映射为大页. 该补丁使用一个程序进行测试, 该程序分配了800MB的内存, 并执行交错读写操作, 其模式导致大约2MB的区域首先看到读访问, 从而导致影射了较多的零 pfn 映射. 没有补丁的情况下, 只有 50% 的程序被压缩成THP, 并且百分比不会随着时间的推移而增加. 有了这个补丁, 等待10分钟后, khugepage 转换了 99% 的程序内存. | v2 ☑ [4.1-rc1](https://kernelnewbies.org/Linux_4.1#Memory_management) | [PatchWork v2](https://lore.kernel.org/patchwork/cover/541944), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ca0984caa8235762dc4e22c1c47ae6719dcc4064) | + + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2015/09/14 | Ebru Akagunduz | [mm: make swapin readahead to gain more THP performance](https://lore.kernel.org/patchwork/cover/597392) | 支持在对匿名页 swapin 的时候 readahead 及逆行大页的转换.
当 khugepaged 扫描页面时, 交换区中可能有一些页面. 有了这个补丁, 当 2MB 范围内 swap_ptes 的数目达到 max_ptes_swap 时, THP 可以将 4kB 页面转换成一个 THP 的大页.
这个补丁用来处理那些在被调出内存后访问大部分(但不是全部)内存的程序的. 补丁合入后, 这些程序不会在内存换出(swapout)后将内存转换成到 THPs 中, 而会在内存从交换分区读入(swapin)时进行转换.
测试使用了用一个测试程序, 该程序分配了 400B 的内存, 写入内存, 然后休眠. 然后强制将所有页面都换出. 之后, 测试程序通过对该内存区域进行写曹祖, 但是它在该区域的每 20 页中跳过一页.
1. 如果没有补丁, 系统就不能在 readahead 中交换. THP率为程序内存的65%, 不随时间变化.
2. 有了这个补丁, 经过10分钟的等待, khugepaged已经崩溃了程序99%的内存. | v2 ☑ [4.8-rc1](https://kernelnewbies.org/Linux_4.8#Memory_management) | [PatchWork RFC,v5,0/3](https://lore.kernel.org/patchwork/cover/597392)
*-*-*-*-*-*-*-*
[PatchWork RFC,v5,0/3](https://lore.kernel.org/lkml/1442259105-4420-1-git-send-email-ebru.akagunduz@gmail.com)
*-*-*-*-*-*-*-*
[LKML](https://lkml.org/lkml/2015/9/14/610) | | 2021/05/10 | Muchun Song | [Free some vmemmap pages of HugeTLB page](https://lore.kernel.org/patchwork/cover/1422994) | NA | v23 ☑ 5.14-rc1 | [PatchWork RFC](https://lore.kernel.org/patchwork/cover/1422994) | | 2021/07/14 | Muchun Song | [Free the 2nd vmemmap page associated with each HugeTLB page](https://lore.kernel.org/patchwork/cover/1459641) | NA | v2 ☐ | [PatchWork RFC](https://patchwork.kernel.org/project/linux-mm/cover/20210714091800.42645-1-songmuchun@bytedance.com) | -| 2015/01/29 | Ebru Akagunduz | [mm: incorporate read-only pages into transparent huge pages](https://lore.kernel.org/patchwork/cover/538503) | 允许 THP 转换只读 pte, 就像 do_swap_page 在读取错误后留下的那些pte. 当在 2MB 范围内存在最多数量为 khugepaged_max_ptes_none 的 pte_none ptes 时, THP可以将4kB的页面压缩为一个 THP. 这个补丁增加了对只读页面的大页支持. 该补丁使用一个程序进行测试, 该程序分配了800MB的内存, 对其进行写入, 然后休眠. 迫使系统通过触摸其他内存来交换除190MB外的所有程序. 然后, 测试程序对其内存进行混合的读写操作, 并将内存交换回来. 没有补丁的情况下, 只有没有被换出的内存保留在 THP 中, 这相当于程序内存的 24%, 这个百分比并没有随着时间的推移而增加. 有了这个补丁, 经过 5分钟的等待, khugepageage 将 60% 的程序内存还原为 THP. | v4 ☑ [4.0-rc1](https://kernelnewbies.org/Linux_4.0#Memory_management) | [PatchWork v4](https://lore.kernel.org/patchwork/cover/538503), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=10359213d05acf804558bda7cc9b8422a828d1cd) | -| 2015/02/11 | Ebru Akagunduz | [mm: incorporate zero pages into transparent huge pages](https://lore.kernel.org/patchwork/cover/541944) | 通过大页允许零页面, 该补丁提高了 THP 的大页转换率. 目前, 当在 2MB 范围内存在最多数量为 khugepaged_max_ptes_none 的 pte_none ptes 时, THP可以将4kB的页面压缩为一个 THP. 这个补丁支持了将零页映射为大页. 该补丁使用一个程序进行测试, 该程序分配了800MB的内存, 并执行交错读写操作, 其模式导致大约2MB的区域首先看到读访问, 从而导致影射了较多的零 pfn 映射. 没有补丁的情况下, 只有 50% 的程序被压缩成THP, 并且百分比不会随着时间的推移而增加. 有了这个补丁, 等待10分钟后, khugepage 转换了 99% 的程序内存. | v2 ☑ [4.1-rc1](https://kernelnewbies.org/Linux_4.1#Memory_management) | [PatchWork v2](https://lore.kernel.org/patchwork/cover/541944), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ca0984caa8235762dc4e22c1c47ae6719dcc4064) | -| 2015/09/14 | Ebru Akagunduz | [mm: make swapin readahead to gain more THP performance](https://lore.kernel.org/patchwork/cover/597392) | 支持在对匿名页 swapin 的时候 readahead 及逆行大页的转换.
当 khugepaged 扫描页面时, 交换区中可能有一些页面. 有了这个补丁, 当 2MB 范围内 swap_ptes 的数目达到 max_ptes_swap 时, THP 可以将 4kB 页面转换成一个 THP 的大页.
这个补丁用来处理那些在被调出内存后访问大部分(但不是全部)内存的程序的. 补丁合入后, 这些程序不会在内存换出(swapout)后将内存转换成到 THPs 中, 而会在内存从交换分区读入(swapin)时进行转换.
测试使用了用一个测试程序, 该程序分配了 400B 的内存, 写入内存, 然后休眠. 然后强制将所有页面都换出. 之后, 测试程序通过对该内存区域进行写曹祖, 但是它在该区域的每 20 页中跳过一页.
1. 如果没有补丁, 系统就不能在 readahead 中交换. THP率为程序内存的65%, 不随时间变化.
2. 有了这个补丁, 经过10分钟的等待, khugepaged已经崩溃了程序99%的内存. | v2 ☑ [4.8-rc1](https://kernelnewbies.org/Linux_4.8#Memory_management) | [PatchWork RFC,v5,0/3](https://lore.kernel.org/patchwork/cover/597392) | - ### 7.2.2 THP splitting/reclaim/migration ------- @@ -2161,6 +2174,51 @@ Anthony Yznaga 接替了之前同事 Nitin Gupta 的工作, 并基于 Mel 的思 | 2021/07/30 | Hugh Dickins | [tmpfs: HUGEPAGE and MEM_LOCK fcntls and memfds](https://patchwork.kernel.org/project/linux-mm/cover/2862852d-badd-7486-3a8e-c5ea9666d6fb@google.com) | 一系列 HUGEPAGE 和 MEM_LOCK tmpfs fcntls 和 memfd_create 标志的清理和修复工作. | v1 ☐ | [PatchWork 00/16](https://patchwork.kernel.org/project/linux-mm/cover/2862852d-badd-7486-3a8e-c5ea9666d6fb@google.com) | +### 7.2.5 khugepaged +------- + +khugepaged 是透明巨页的守护进程, 它会被定时唤醒, 并根据配置尝试将 page_size(比如 4K) 大小的普通 page 转成巨页, 减少 TLB 压力, 提高内存使用效率. + +khugepaged 的处理过程中需要各种内存锁, 会在一定程度上影响系统性能. + +khugepaged 主要的调用链如下: + +```cpp +khugepaged +-=> khugepaged_do_scan() + -=> khugepaged_scan_mm_slot() + -=> khugepaged_scan_pmd() + -=> collapse_huge_page() + -=> __collapse_huge_page_copy() +``` + + +khugepaged 控制参数, 位于 `/sys/kernel/mm/transparent_hugepage/khugepaged` 目录下: + +| 节点 | 描述 | +|:---:|:----:| +| pages_collapsed | 记录了 khugepaged 已经[合并的大页的数目](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1902) | +| pages_to_scan | 每次 khugepaged 尝试[处理的页面数目](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L2146), khugepage 扫描是有一定开销的, 因此对扫描的范围做一些限制 | +| full_scans | 完整扫描的轮数. 每次如果 khugepaged 如果完整的扫描了某个 mm_struct 的所有 VMA, 则[该计数增加 1](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L2118). | +| scan_sleep_millisecs | 扫描间隔(毫秒) | +| alloc_sleep_millisecs | 当分配大内存失败等待多少毫秒来压制下一个大内存页的分配尝试. | + +khugepaged 处理流程 + +| 关键流程 | 描述 | +|:-------:|:---:| +| khugepaged_do_scan | khugepaged 被唤醒后, 尝试处理 khugepaged_pages_to_scan 个页面. 其中每一次页面的循环这是通过调用 khugepaged_scan_mm_slot() 完成. 在 khugepaged_do_scan() 每次循环的最开始, 会尝试[调用 prealloc page 预分配一个巨页](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L2151), 需要注意. 这主要针对非 numa 系统, 对于 numa 系统, 这里不会真正分配. | +| khugepaged_scan_mm_slot | 扫描 struct mm_struct , 找到可以用巨页代替的普通页面. 这里需要扫描的 struct mm_struct 是放到[全局变量 khugepaged_scan 中的 mm_slot](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L93). 而这些 mm_slot 中的这些 mm 是在 vma 结构体发生[合并 merge](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1556)和[扩展](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L678)的时候调用 [khugepaged_enter()](https://elixir.bootlin.com/linux/v2.6.38/source/include/linux/khugepaged.h#L38) 加入的. 这里找到需要扫描的 mm_struct 之后, 扫描 mm_struct 中包含的[各个 vma](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L2039), 注意, 扫描的范围是一个巨页包括的范围. | +| [khugepaged_scan_pmd](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1915) | 完成普通页面搬运到巨页的工作. 具体来说, khugepaged_scan_pmd() 通过访问 vma 中的每一个页面, 然后判断该页面是否可以用巨页代替, 可以的话[调用 collapse_huge_page() 处理](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1982). | +| collapse_huge_page | 首先[通过 `__collapse_huge_page_copy` 将普通页面拷贝到巨页中](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L1872), 然后释放普通页面. 不过释放之前会将原来页面的 tlb 刷出去, 然后将原来的页面从 LRU 链表移除, 最后拷贝到巨页中后, 还会更新内存相关信息. 如果是 numa 系统, 这里才会[真正分配巨页](https://elixir.bootlin.com/linux/v2.6.38/source/mm/huge_memory.c#L2151). | + + + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2011/01/13 | Andrea Arcangeli | [thp: khugepaged](https://lkml.org/lkml/2010/12/23/306) | 实现了一个 khugepaged 的内核线程, 用来完成将标准的小页面合并成大页的操作. | ☑ 2.6.38-rc1 | [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ba76149f47d8c939efa0acc07a191237af900471) | +| 2015/09/14 | Ebru Akagunduz | [mm: add tracepoint for scanning pages](https://lkml.org/lkml/2015/9/14/611) | [mm: make swapin readahead to gain more THP performance](https://lore.kernel.org/lkml/1442259105-4420-1-git-send-email-ebru.akagunduz@gmail.com) 系列的其中一个补丁, 为 khuagepaged 引入了 tracepoint 跟踪点. 用 scan_result 标记了 khugepaged 扫描的结果. | v5 ☑ 4.5-rc1 | [PatchWork RFC,v5,0/3](https://lore.kernel.org/lkml/1442259105-4420-2-git-send-email-ebru.akagunduz@gmail.com)
*-*-*-*-*-*-*-*
[LKML](https://lkml.org/lkml/2015/9/14/611)
*-*-*-*-*-*-*-*
[commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7d2eba0557c18f7522b98befed98799990dd4fdb) | + # 8 进程虚拟地址空间(VMA) ------- @@ -2853,7 +2911,7 @@ AMD 正在研究一种异构超级计算机架构, 该架构在 CPU 和 GPU 之 补丁集 [Support DEVICE_GENERIC memory in migrate_vma_*](https://lore.kernel.org/patchwork/cover/1472581) 进行了一些更改, 尝试通过使用 `migrate_vma_*()` 辅助函数将数据迁移到该内存中, 以便在统一内存分配中支持基于页面的迁移, 同时还支持 CPU 直接访问对这些页面. -这项工作基于 HMM 和 SVM 内存管理器,该内存管理器最近被上传到 Dave Airlie 的 [drm-next](https://cgit.freedesktop.org/drm/drm/log/?h=drm-next), 其中对用于迁移的 VRAM 管理进行了一些返工, 以消除一些不正确的假设, 允许在 VRAM 和系统内存中混合页面的部分成功迁移和 GPU 内存映射, 参见 [drm/amdkfd: add owner ref param to get hmm pages Felix](https://lore.kernel.org/dri-devel/20210527205606.2660-6-Felix.Kuehling@amd.com/T/#r996356015e295780eb50453e7dbd5d0d68b47cbc) +这项工作基于 HMM 和 SVM 内存管理器, 该内存管理器最近被上传到 Dave Airlie 的 [drm-next](https://cgit.freedesktop.org/drm/drm/log/?h=drm-next), 其中对用于迁移的 VRAM 管理进行了一些返工, 以消除一些不正确的假设, 允许在 VRAM 和系统内存中混合页面的部分成功迁移和 GPU 内存映射, 参见 [drm/amdkfd: add owner ref param to get hmm pages Felix](https://lore.kernel.org/dri-devel/20210527205606.2660-6-Felix.Kuehling@amd.com/T/#r996356015e295780eb50453e7dbd5d0d68b47cbc) | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 |