diff --git a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md index f5cc6bd..08aefc6 100644 --- a/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md +++ b/study/kernel/00-DESCRIPTION/MEMORY_MANAGER.md @@ -311,7 +311,7 @@ Linux 一开始是在一台i386上的机器开发的, i386 的硬件页表是2 |:----:|:----:|:---:|:----:|:---------:|:----:| | 2022/01/04 | "Matthew Wilcox (Oracle)" | [Separate struct slab from struct page](https://patchwork.kernel.org/project/linux-mm/cover/20211004134650.4031813-1-willy@infradead.org) | struct page 结构定义中比较复杂的部分之一是 slab 分配器所使用的部分. 一般来说, 如果将 slab 的数据类型从 page 结构体中分离是有好处的, 而且它还有助于防止尾页滑落到任何地方. | v2 ☐ | [2021/07/15 PatchWork RFC,00/62](https://patchwork.kernel.org/project/linux-mm/cover/20211004134650.4031813-1-willy@infradead.org)
*-*-*-*-*-*-*-*
[2021/12/01 PatchWork v2,00/33](https://patchwork.kernel.org/project/linux-mm/cover/20211201181510.18784-1-vbabka@suse.cz)
*-*-*-*-*-*-*-*
[2022/01/04 PatchWork v4,00/32](https://patchwork.kernel.org/project/linux-mm/cover/20220104001046.12263-1-vbabka@suse.cz) | | 2021/10/12 | Johannes Weiner | [PageSlab: eliminate unnecessary compound_head() calls](https://patchwork.kernel.org/project/linux-mm/cover/20211012180148.1669685-1-hannes@cmpxchg.org) | 重构代码, 消除二义性, 使得代码更加简洁. PageSlab() 目前对所有调用站点施加一个 compound_head() 调用, 即使只有极少数情况会遇到尾页. 这组补丁气泡尾分辨率到少数需要它的网站, 并消除它在其他地方. 这个改动很独立, 它的灵感来自于 Willy 的补丁 Separate struct slab from struct page](https://patchwork.kernel.org/project/linux-mm/cover/20210715200030.899216-1-willy@infradead.org). 为了让逻辑更清晰, 代码更简洁. PageSlab() 的调用应该完全从对 compound_head() 的无限制调用中分离出来, 因为它们本身就有不必要的开销. | v1 ☐ | [PatchWork 00/11](https://patchwork.kernel.org/project/linux-mm/cover/20211012180148.1669685-1-hannes@cmpxchg.org), [LKML](https://lkml.org/lkml/2021/10/12/820) | -| 2022/01/04 | Vlastimil Babka | [Separate struct slab from struct page](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=9d6c59c1c0d62a314a2b46839699b200cccd2d08) | NA | RFC ☑ 5.17-rc1 | [PatchWork RFC,00/32](https://patchwork.kernel.org/project/linux-mm/cover/20211116001628.24216-1-vbabka@suse.cz)
*-*-*-*-*-*-*-*
[PatchWork v4,00/32](https://patchwork.kernel.org/project/linux-mm/cover/20220104001046.12263-1-vbabka@suse.cz) | +| 2022/01/04 | Vlastimil Babka | [Separate struct slab from struct page](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=07f910f9b7295b6a28b337fedb56e612684c5659) | NA | RFC ☑ [5.17-rc1](https://kernelnewbies.org/Linux_5.17#Memory_management) | [PatchWork RFC,00/32](https://patchwork.kernel.org/project/linux-mm/cover/20211116001628.24216-1-vbabka@suse.cz)
*-*-*-*-*-*-*-*
[PatchWork v4,00/32](https://patchwork.kernel.org/project/linux-mm/cover/20220104001046.12263-1-vbabka@suse.cz) | ### 1.4.3 MEMCG Folio @@ -576,24 +576,10 @@ MTE 实现了锁和密钥访问内存. 这样在内存访问期间, 可以在内 | 2021/02/05 | "Kirill A. Shutemov" | [Linear Address Masking enabling](https://patchwork.kernel.org/project/linux-mm/cover/20210205151631.43511-1-kirill.shutemov@linux.intel.com) | [线性地址屏蔽(LAM)](https://software.intel.com/content/dam/develop/external/us/en/documents-tps/architecture-instruction-set-extensions-programming-reference.pdf) 修改应用于 64 位线性地址的检查, 允许软件将未翻译的地址位用于元数据. 手册参见 [ISE, Chapter 14](https://patchwork.kernel.org/project/linux-mm/cover/20210205151631.43511-1-kirill.shutemov@linux.intel.com). 代码参见 [kas/linux.git](https://git.kernel.org/pub/scm/linux/kernel/git/kas/linux.git/log/?h=lam). | RFC ☐ | [PatchWork RFC,0/9](https://patchwork.kernel.org/project/linux-mm/cover/20210205151631.43511-1-kirill.shutemov@linux.intel.com) | -## 1.9 memory policy +## 1.9 page attributes ------- -NUMA 系统中 CPU 访问不同节点的内存速度很有大的差别. 位于本地 NUMA 节点(或附近节点)上的内存比远程节点上的内存访问速度更快. 因此如果能通过策略去控制优先在哪些节点上内存分配, 能有效地提升业务的性能. - - -| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | -|:----:|:----:|:---:|:----:|:---------:|:----:| -| 2021/08/03 | Feng Tang | [Introduce multi-preference mempolicy](https://lore.kernel.org/patchwork/patch/1471473) | 参见 LWN 报道 [NUMA policy and memory types](https://lwn.net/Articles/862707).
引入 MPOL_PREFERRED_MANY 的 policy, 该 mempolicy 模式可用于 set_mempolicy 或 mbind 接口.
1. 与 MPOL_PREFERRED 模式一样, 它允许应用程序为满足内存分配请求的节点设置首选项. 但是与 MPOL_PREFERRED 模式不同, 它需要一组节点.
2. 与 MPOL_BIND 接口一样, 它在一组节点上工作, 与 MPOL_BIND 不同, 如果首选节点不可用, 它不会导致 SIGSEGV 或调用 OOM killer. | v7 ☑ 5.15-rc1 | [PatchWork v7,0/5](https://patchwork.kernel.org/project/linux-mm/cover/1627970362-61305-1-git-send-email-feng.tang@intel.com), [COMMIT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/tools/perf/builtin-record.c?id=a38a59fdfa10be55d08e4530923d950e739ac6a2) | -| 2021/11/01 | "Aneesh Kumar K.V" | [mm: add new syscall set_mempolicy_home_node](https://patchwork.kernel.org/project/linux-mm/cover/20211101050206.549050-1-aneesh.kumar@linux.ibm.com) | 增加了 set_mempolicy_home_node() 来为用户空间的一段地址 [start, srart + len] 指定内存分配的主节点 home_node. 主节点 home_node 应与 MPOL_PREFERRED_MANY 或 MPOL_BIND 内存分配策略结合使用. 这些策略可以指定一组将用于新内存分配的节点, 但不能说明这些节点中的哪个节点(如果有)是首选节点. 如果设置了 home_node, 则分配内存时将优先在该节点上进行分配; 否则, 主节点 home_node 内存不足, 则将回退到有效策略允许的其他节点上分配, 首选最接近主节点的节点. 其目的是让应用程序能够更好地控制内存分配, 同时避免来自慢速节点的内存. 参见 LWN 报道 [Some upcoming memory-management patches](https://lwn.net/Articles/875587). | v1 ☐ | [PatchWork v4,0/3](https://patchwork.kernel.org/project/linux-mm/cover/20211101050206.549050-1-aneesh.kumar@linux.ibm.com) | - - - - -## 1.10 page attributes -------- - -## 1.10.1 CPA(Change Page Attribute) +## 1.9.1 CPA(Change Page Attribute) ------- | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -601,9 +587,7 @@ NUMA 系统中 CPU 访问不同节点的内存速度很有大的差别. 位于 | 2018/09/17 | Srivatsa S. Bhat | [x86/mm/cpa: Improve large page preservation handling](https://lore.kernel.org/patchwork/patch/987147) | 优化 页面属性(CPA) 代码中的 try_preserve_large_page(), 降低 CPU 消耗. | v3 ☑ 4.20-rc1 | [PatchWork RFC v3](https://lore.kernel.org/patchwork/patch/987147) | - - -## 1.11 其他页面页表相关 +## 1.10 其他页面页表相关 ------- | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -727,19 +711,154 @@ memblock 的内存占用是非常小的, 它采用静态数组的方式, 数组 | 2007/03/01 | "Matthew Wilcox (Oracle)" | [Rationalise `__alloc_pages` wrappers](https://patchwork.kernel.org/project/linux-mm/cover/20210225150642.2582252-1-willy@infradead.org) | NA | v3 ☑ 5.13-rc1 | [PatchWork v6](https://patchwork.kernel.org/project/linux-mm/cover/20210225150642.2582252-1-willy@infradead.org) | -### 2.2.2 zone 分区管理 +### 2.2.2 ZONE 分区管理 ------- +[Linux 内存描述之内存区域 zone--Linux 内存管理 (三)](https://blog.csdn.net/gatieme/article/details/52384529) + +在 NUMA 架构中存在多个内存节点, 每个内存节点在 Linux 内核用 pg_data_t(实际是 struct pglist_data) 来表示表示. 各个内存节点又被划分为多个内存区(即 struct zone), 这些内存区就记录在 pg_data_t->struct zone node_zones[MAX_NR_ZONES] + +而在分配内存时不仅可以从本地的内存节点分配内存, 还可从其他内存节点分配内存, 这样分配时如果本地 ZONE 中内存不足时, 选择从其他 ZONE(可能本地也可能远程)分配的次序就显得格外重要. 内核通过 pg_data_t 的成员 struct zonelist node_zonelists[MAX_ZONELISTS] 来约定了该内存节点分配内存时选择从本地以及远程内存分配内存的 ZONE 先后顺序. + +在系统初始化阶段, Linux 内核会遍历系统中的各个内存节点, 并根据其他邻居节点到该节点 "距离" 的 "远", " 近" 关系安插邻居节点的 zonelist 中, "距离" 越近的邻居节点所属的 struct zone, 通常在数组中的位置越靠前, 同一个邻居节点中 zones 往往按照从高到低的顺序排列 (即 ZONE_HIGH、ZONE_NORMAL...ZONE_DMA). 这样在分配内存时, 优先从最近的邻居内存节点的最高 index 的内存 zone 分配内存, 本地或者距离近的内存的 ZONE 中内存不足时, 会尝试从远端节点进行分配. + +NUMA 系统下, 内核对系统的内存定义了明确的层次结构, 即当前 Node 与系统中其他 Node 的 ZONE 域之前存在一种等级次序.(首先分配本地 Node 明显比远程要好, 其次同一个 Node 的各个 ZONE 也是分三六九等的), 因此内存总是试图优先分配 "廉价的" 内存. 如果失败, 则根据访问速度和容量, 逐渐尝试分配 "更昂贵的" 内存. + +1. 高端内存是最廉价的, 因为内核没有任何部份依赖于从该内存域分配的内存. 如果高端内存域用尽, 对内核没有任何副作用, 这也是优先分配高端内存的原因. + +2. 其次是普通内存域, 这种情况有所不同. 许多内核数据结构必须保存在该内存域, 而不能放置到高端内存域. 因此如果普通内存完全用尽, 那么内核会面临紧急情况. 所以只要高端内存域的内存没有用尽, 都不会从普通内存域分配内存. + +3. 最昂贵的是 DMA 内存域 , 因为它用于外设和系统之间的数据传输. 因此从该内存域分配内存是最后一招. + +比如内核想要分配高端内存, 它首先企图在当前结点的高端内存域找到一个大小适当的空闲段. 如果失败, 则查看该结点的普通内存域. 如果还失败, 则试图在该结点的 DMA 内存域执行分配. 如果在 3 个本地内存 ZONE 都无法找到空闲内存, 则查看其他 NUMA Node. 在这种情况下, 备选结点应该尽可能靠近主结点, 以最小化由于访问非本地内存引起的性能损失. + + +#### 2.2.2.1 NUMA zonelist +------- + +[linux 那些事之 ZONE (zonelist)(2)](https://zhikunhuo.blog.csdn.net/article/details/122800050) + +* 轮循(round robin) 的 NUMA 感知的页面分配(NUMA aware page allocation) + +[Import 2.3.29](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/mm/page_alloc.c?id=338322e6076c42a58e4cfcda8add4ef372d90274) 引入了 zonelist_t zonelists[NR_GFPINDEX], 启动过程 free_area_init() 中通过 build_zonelists() 按照既定的规则初始化了 zonelist. 然后 `__alloc_pages()` 分配内存的过程中. 这是为了实现了 NUMA aware page allocation 的准备工作. + +最早 [Import 2.3.30pre7](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/mm/numa.c?id=0c7791dbc6d086d121176e9b8c9a1ce7f3a25343) 实现了 NUMA 感知的页面分配(NUMA aware page allocation). 通过 CONFIG_DISCONTIGMEM 宏控制, 引入了结构体 pglist_data 管理 node 中的内存资源, 将原本全局(不支持 NUMA, 只有一个结点)的 zonelists[NR_GFPINDEX] 修改为 pglist_data->node_zonelists[NR_GFPINDEX]. 除了原本的 `__alloc_pages()`, 还添加了 alloc_pages_node() 来在制定的 NUMA node 上分配内存. 并增加了开启 CONFIG_DISCONTIGMEM 选项下的 [alloc_pages()](https://elixir.bootlin.com/linux/2.3.30/source/mm/numa.c#L75) 实现, 当前 alloc_pages() 试图以轮循(round robin)方式通过 alloc_pages_node() 依次从各个节点分配页面, 使用一个静态的 nextnid 存储下次分配的 NUMA node, 每次分配成功后 `nextnid++`, 从而保证以轮循的方式进行页面分配. 随后 [Import 2.3.48](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/include/linux/mmzone.h?id=6de1208584565275939efb5376cdd80b05d1fb0d) 将各个 NUMA NODE 通过 pglist_data->node_next 链起来, 于是在 [Linux 2.4.0-test10pre3](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/mm/numa.c?id=52bb34eca7a1e109192fdef2d641f8c5cf043033) 使用了这种通过 pglist_data->node_next 遍历 pglist_data 的方式替代了直接用 numa nodeid++ 的方式. alloc_pages() 中使用 `static pg_data_t *next` 和 alloc_pages_pgdat() 替代了 nextnid 和 alloc_pages_node() 的方式, 但是逻辑没变, 还是轮循. + +接着 v2.5.30 [show_free_areas() cleanup](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/mm/numa.c?id=c1ab3459d0ce0820b4bcddfa55fde62bb88d13c1) 移除了 node_lock. [for_each_pgdat macro](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/mm/numa.c?id=f183c478d32d866b7018a9980eb4b61975b8f1fb) 将 pglist_data->node_next 重命名为 pgdat_next. + +之前的 zonelist, 都是包含了本 NODE 的内存 zone 信息, 因此 `_alloc_pages()` 分配内存的时候, 只会从本 NUMA node 的内存中分配. 而通过 `alloc_pages() -=> _alloc_pages()` 轮询所有 NUMA node 的方式, NUMA 感知的页面分配(NUMA aware page allocation). 这是非常粗糙的算法. 因为页面分配的时候, 完全不考虑 NUMA node 页面的亲和性和距离问题, 分配时可能分配到距离很远的 NUMA node 的页面, 只是考虑公平切均匀的是系统中各个 NUMA node 的内存. + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2002/09/19 | Andrew Morton | [`_alloc_pages cleanup`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ccc98a67de98c912840e0a35a24115ad64ae426d) | 重新设计了 NUMA aware page allocation 的逻辑. 移除了 `_alloc_pages()` 以及轮循(round robin)的 NUMA 内存分配器, 实现了 NUMA zonelist. | v1 ☑✓ 2.5.37 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ccc98a67de98c912840e0a35a24115ad64ae426d) | + +* NUMA-aware zonelist(Walk all node and zones in the zonelist) + +v2.5.37 [commit ccc98a67de98 ("`_alloc_pages cleanup`")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ccc98a67de98c912840e0a35a24115ad64ae426d) 重新设计了 NUMA aware page allocation 的逻辑. 移除了 `_alloc_pages()` 以及轮循(round robin)的 NUMA 内存分配器, 实现了 NUMA zonelist, zonelist->zones[MAX_NUMNODES * MAX_NR_ZONES + 1] 中系统中所有的 NUMA node 的 zone [按照 [local_node, numnodes - 1, 0, local_node - 1] 的顺序](https://elixir.bootlin.com/linux/v2.5.37/source/mm/page_alloc.c#L702) 排列. 这样分配时会优先从本地 ZONE 进行分配, 本地 ZONE 不足时, 会在本地其他 ZONE 分配, 当整个本地都没有内存时, 则继续从其他 NUMA node 中进行分配. 已经有了优先本地, 其次其他 NUMA node 的思想, 但是远程的 NUMA node 依旧是[按照 numanodeid 顺序](https://elixir.bootlin.com/linux/v2.5.37/source/mm/page_alloc.c#L702)来的, 没有考虑各个 NUMA node 之间的距离. 添加了 [build_zonelists_node()](https://elixir.bootlin.com/linux/v2.5.37/source/mm/page_alloc.c#L675) 对某个 NUMA node 的 zonelist 进行构建, 然后 build_zonelists() 通过 build_zonelists_node() 完成所有节点 zonelist 的构建. 随后 v2.6.5-rc1, [commit 0eaf393b6b6e ("NUMA-aware zonelist builder")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0eaf393b6b6ec017f48aa02ba90b1dd53ad16e65) 实现了 zonelist NUMA-aware ordering of nodes, 按照近节点优先、远节点最后的顺序对区域列表 zonelists 进行排序. build_zonelists() 通过调用 [find_next_best_node()](https://elixir.bootlin.com/linux/v2.6.5/source/mm/page_alloc.c#L1140) 来选择下一个最近的节点添加到 zonelist. + +上面的补丁只有 `alloc_pages()` 感知了 NUMA-aware zonelist 的变化, 其他路径还没有了解到随后对内存的各个 2.5.40 进一步对 KSWAPD 也做了处理. 参见 [commit1](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=db7b0c9fcd4e93c07f16a0bfd449b49210fa523d), [commit2](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=36fb7f8459cc42eca202f0ad7b2d051359406d57). 实现 per-node kswapd 的时候, `_alloc_pages()` 中相应的 wakeup_kswapd() 会遍历区域列表 zonelist 中的所有区域 zone. + +此时 try_to_free_pages() 仅释放分区列表中第一个分区所属的 pgdat 中的页面, 这明显与其他路径显得格格不入. 因此在 v2.6.2 页面释放路径 try_to_free_pages() 也开始遍历 zonelist 来进行释放, 参见 [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d5d4042dd2d0352990d7ff54ea422950eba62969). + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2004/03/11 | Andrew Morton | [NUMA-aware zonelist builder](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0eaf393b6b6ec017f48aa02ba90b1dd53ad16e65) | TODO | v1 ☐☑✓ 2.6.5-rc1 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0eaf393b6b6ec017f48aa02ba90b1dd53ad16e65) | +| 2002/09/29 | Andrew Morton | [per-node kswapd instances](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=db7b0c9fcd4e93c07f16a0bfd449b49210fa523d) | TODO | v1 ☑✓ 2.5.40 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=db7b0c9fcd4e93c07f16a0bfd449b49210fa523d) | +| 2002/11/21 | Andrew Morton | [handle zones which are full of unreclaimable pages](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=36fb7f8459cc42eca202f0ad7b2d051359406d57) | TODO | v1 ☑✓ 2.5.49 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=36fb7f8459cc42eca202f0ad7b2d051359406d57) | +| 2004/01/18 | Andrew Morton | [make try_to_free_pages walk zonelist](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d5d4042dd2d0352990d7ff54ea422950eba62969) | 如果在 /proc 中设置了 lower_zone_protection, 那么 kswapd 可能永远不会清除区域列表下方区域中的数据, 而 try_to_free_pages() 需要这样做. 但是, 在 2.6.0 中, try_to_free_pages() 只释放区域列表中第一个区域所属的 pgdat 中的页面. 这可能是错误的行为, 因为页面分配器和 kswapd 都从区域列表上的所有区域中唤醒. 下面的补丁通过将区域列表作为参数传递并从列表中的所有区域释放页面, 使 try_to_free_pages() 与分配器保持一致. | v1 ☑✓ 2.6.2-rc1 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d5d4042dd2d0352990d7ff54ea422950eba62969) | + +v2.6.19 之前 GFP 到 ZONE 的转换是非常奇怪的, 内核定义了 GFP_ZONEMASK 和 GFP_ZONETYPES 来完成 GFP 到 ZONE 的映射关系, 这其实完全没必要, [commit 19655d348700 ("linearly index zone->node_zonelists")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=19655d3487001d7df0e10e9cbfc27c758b77c2b5) 实现了对 zone->node_zonelists[MAX_NR_ZONES] 直接进行线性访问的方式. zonelist 数组的定义从 node_zonelists[GFP_ZONETYPES] 变成了 node_zonelists[MAX_NR_ZONES]. 直接通过 gfp_zone(gfp) + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2006/09/25 | Christoph Lameter | [linearly index zone->node_zonelists](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=19655d3487001d7df0e10e9cbfc27c758b77c2b5) | 当前需要这个位掩码 MASK 索引到 pglist_data->node_zonelists[GFP_ZONETYPES]. 通常我们总是从最高的区域开始, 然后包括所有较低的区域来建立区域列表. 实现用索引对 pglist_data->node_zonelists[MAX_NR_ZONES] 进行线性访问, 这样 highest_zone() 也不用需要了. | v1 ☑✓ 2.6.19-rc1 | [LORE 00/13](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=19655d3487001d7df0e10e9cbfc27c758b77c2b5) | + +* build zonelist + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2006/01/06 | Christoph Lameter | [simplify build_zonelists](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=070f80326a215d8e6c4fd6f175e28eb446c492bc) | 重构了 build_zonelists 的逻辑. | v1 ☑✓ 2.6.16-rc1 | [LORE 0/4](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=070f80326a215d8e6c4fd6f175e28eb446c492bc) | +| 2017/07/14 | Michal Hocko | [cleanup zonelists initialization](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=b95046b0472f7a805fa28fbcfc7205a76ff7a7d0) | NA | v1 ☑✓ 4.14-rc1 | [LORE v1,0/9](https://lore.kernel.org/all/20170714080006.7250-1-mhocko@kernel.org)| +| 2006/08/21 | Mel Gorman | [Sizing zones and holes in an architecture independent manner V9](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=0e0b864e069c52a7b3e4a7da56e29b03a012fd75) | NA | v1 ☑ v2.6.19-rc1 | [PatchWork 0/2](https://lore.kernel.org/patchwork/patch/63170) | +| 2021/08/10 | Baoquan He | [Avoid requesting page from DMA zone when no managed pages](https://lore.kernel.org/patchwork/patch/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/30 | Bharata B Rao | [Fix NUMA nodes fallback list ordering](https://lore.kernel.org/all/20210830121603.1081-1-bharata@amd.com) | 20210830121603.1081-1-bharata@amd.com | v1 ☐☑✓ | [LORE v1,0/2](https://lore.kernel.org/all/20210830121603.1081-1-bharata@amd.com) | + + +* GFP_THISNODE 与 Two Zonelists + +v2.6.19 [commit 9b819d20 ("Add `__GFP_THISNODE` to avoid fallback to other nodes and ignore cpuset/memory policy restrictions")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9b819d204cf602eab1a53a9ec4b8d2ca51e02a1d) 添加一个新的 gfp 标志 `GFP_THISNODE` 标记, 以避免内存分配时通过 zonelists FallBack 到其他 NUMA 节点. 具体实现方式是, 如果设置了 GFP_THISNODE 则, get_page_from_freelist() 被限制只能从 `zonelist->zones[0]->zone_pgdat` 的节点上去分配内存. + +v2.6.24 [commit 523b945855a1 ("Memoryless nodes: Fix GFP_THISNODE behavior")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=523b945855a1427000ffc707c610abe5947ae607) 修改了 `GFP_THISNODE` 的实现方式. 为了将 GFP_THISNODE 的分配限制为单个节点的区域列表, 将 NUMA zonelists 数组大小直接加倍(double). 引入 MAX_ZONELISTS, NUMA 情况下, 被设置为 (2 * MAX_NR_ZONES), 其他情况下依旧是 MAX_NR_ZONES. NUMA node 的 zonelists 定义从 pglist_data->node_zonelists[MAX_NR_ZONES] 更改为 pglist_data->node_zonelists[MAX_ZONELISTS], 数组的前一半 zonelist 区域 [0 ... MAX_NR_ZONES - 1] 支持 Fallback 到其他 NUMA node, 后一半 zonelist 区域 [MAZ_NR_ZONES ... MAZ_ZONELISTS - 1] 用于 GFP_THISNODE, 不提供 Fallback 功能. + +之前的设计中, 每个 NUMA 节点的 zonelists pglist_data->node_zonelists[MAX_NR_ZONES] 中包含了 MAX_ZONELISTS(2 * MAX_NR_ZONE) 个 zonelist. 这些 zonelist 被分成了两组, 一组用于每种分区类型, 另一组用于 GFP_THISNODE 分配. 然后根据 gfp 来确定允许的 ZONE, 在选择其中一个分区列表 zonelist. 所有这些分区列表都会消耗内存. 因此 v2.6.26 [commit 19770b32609b ("Use two zonelists per node instead of multiple zonelists v11r2")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=19770b32609b6bf97a3dece2529089494cbfc549) 进一步修正了 NUMA zonelists 的实现. + +首先 [COMMIT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=54a6eb5c4765aa573a030ceeba2c14e3d2ea5706) 将每个节点的多个 zonelist 替换为两个 zonelist(直接定义 MAX_ZONELISTS 为 2). 第一个包含系统中按 NUMA 距离排序的所有填充区域, 用于在目标/首选节点没有空闲页面时进行 Fallback 分配. 第二个包含节点中适合 GFP_THISNODE 分配的所有填充区域. 之前的每个 ZONE 都有一个 zonelist, 因此遍历 zonelist 的时候, 直接从前往后直到遇到 NULL 节点为止. 但是归一为 Two zonelist 后, 原本的遍历方式无法进行, 因此引入了一个[迭代器宏 for_each_zone_zonelist()](https://elixir.bootlin.com/linux/v2.6.26/source/include/linux/mmzone.h#L809), 该宏通过所选 zonelist 中 GFP 标志允许的每个区域进行交互. 通过 [first_zones_zonelist()](https://elixir.bootlin.com/linux/v2.6.26/source/include/linux/mmzone.h#L763) 和 [next_zones_zonelist()](https://elixir.bootlin.com/linux/v2.6.26/source/include/linux/mmzone.h#L746) 来辅助遍历的工作. + +其次, 筛选 zonelists 需要非常频繁地使用 zone_idx(), 这非常耗时, 因为它涉及另一个结构的查找和减法操作. 为了快速访问, 如果发现访问 zone->node 很重要, 那么节点 idx 也可以保存下来, 这可能是在大量使用 nodemask 的工作负载上的情况. [COMMIT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=dd1a239f6f2d4d3eedd318583ec319aa145b324c) 引入了一个 struct zoneref 来存储区域指针和区域索引. 然后, zonelist 由这些 zoneref 的数组组成, 为同时访问区域索引和节点索引提供了帮助. + +v4.5 将 Two Zonelists 的 index 定义为 enum 变量. 参见 [commit c00eb15a8914 ("mm/zonelist: enumerate zonelists array index")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c00eb15a8914b8ba84032a36044a5aaf7f71709d). + +| Two Zonelists | 描述 | +|:-------------:|:---:| +| ZONELIST_FALLBACK | 不管是 NUMA 或 SMP 系统中都存在, 当内存失败时从该 FALLBACK 中管理的 zone 顺序取出下一个 zone 中申请内存. SMP 系统中只有一个节点其顺序依次从 ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA 中从高到低排序. NUMA 系统中, 首先安排本节点之后, 再根据 NUMA 系统由近到远安排其他远端节点内存, 当本节点没有内存时, 就有依次按照最近原则尽量从最近远端节点中获取内存, 决定内存分配策略. | +| ZONELIST_NOFALLBACK | NUMA 系统有时候根据需要只想从本节点中获取内存, 不想总远端中获取内存. 因此 NOFALLBACK 对应的 zonelist 只有本节点信息, zone 排序依次从高到低. ZONELIST_NOFALLBACK 主要是针对在有些特定的内存申请场景, 只希望从指定节点或者本地节点中申请内存, 当本地节点或者指定内存节点内存不足时, 能够立即知道内存不足, 而不是希望通过 FALLBACK 机制从其他节点中继续申请内存. | + | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | |:----:|:----:|:---:|:----:|:---------:|:----:| -| 2006/08/21 | Mel Gorman | [Sizing zones and holes in an architecture independent manner V9](https://lore.kernel.org/patchwork/patch/63170) | NA | v1 ☑ v2.6.19-rc1 | [PatchWork 0/2](https://lore.kernel.org/patchwork/patch/63170) | -| 2007/12/11 | Mel Gorman | [Use two zonelists per node instead of multiple zonelists v11r2](https://lore.kernel.org/patchwork/patch/99109) | 优化分配器处理区域列表, 区域列表指示分配目标区域的顺序. 类似地, 页面的直接回收会在区域数组上迭代. 为了保持一致性, 这组补丁将直接回收转换为使用分区列表, 并简化 zonelist 迭代器.
将每个节点的多个(两组)分区列表替换为两个分区列表, 一组用于系统中的每个分区类型, 另一组用于 GFP_THISNODE 分配. 根据 gfp 掩码允许的分区, 选择其中一个分区列表. 所有这些分区列表都会消耗内存并占用缓存线. | v1 ☑ v2.6.19-rc1 | [PatchWork v11r2,0/6](https://lore.kernel.org/patchwork/patch/99109) | -| 2021/08/10 | Baoquan He | [Avoid requesting page from DMA zone when no managed pages](https://lore.kernel.org/patchwork/patch/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) | +| 2006/09/25 | Christoph Lameter | [mm: handle GFP_THISNODE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=980128f223fa3c75e3ebdde650c9f1bcabd4c0a2) | 添加一个新的 gfp 标志 `GFP_THISNODE` 标记, 以避免 FallBack 到其他 NUMA 节点来分配内存. 如果分配内存时要求内存位于某个节点上, 则可以使用此标记. alloc_pages_node() 使用此标记则说明需要在指定的节点上强制分配, alloc_pages() 使用此标记表明在当前节点上强制分配.
具体实现方式是, get_page_from_freelist() 中不再从其他非 `zonelist->zones[0]->zone_pgdat` 的节点上去分配内存. | v1 ☑✓ 2.6.19-rc1 | [COMMIT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=980128f223fa3c75e3ebdde650c9f1bcabd4c0a2) | +| 2007/10/16 | Yasunori Goto | [Memoryless nodes: Fix GFP_THISNODE behavior](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=523b945855a1427000ffc707c610abe5947ae607) | [Memoryless nodes support](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=58c0a4a7864b2dad6da4090813322fcd29a11c92) 的其中一个补丁. 修改了 `GFP_THISNODE` 的实现方式. 之前是通过 get_page_from_freelist() 中不再从其他非 `zonelist->zones[0]->zone_pgdat` 的节点上去分配内存来保证的. 这个补丁为了将 GFP_THISNODE 的分配限制为单个节点的区域列表, 将 NUMA zonelists 加倍(double). pglist_data->node_zonelists[MAX_NR_ZONES] 修改为 pglist_data->node_zonelists[MAX_ZONELISTS]. MAX_ZONELISTS 被设置为 2 * MAX_NR_ZONE. 前一半支持 Fallback 到其他 NUMA node, 后一半用于 GFP_THISNODE, 不提供 Fallback 功能, 提供了 build_thisnode_zonelists() 来构建 GFP_THISNODE 的 zonelist. 然后分配时, gfp_zone() 直接返回 zonelist 上后半数组对应 zone 的索引, 这样保证只能从本地 node 上分配. | v1 ☑✓ 2.6.24-rc1 | [CGIT 00/16](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=58c0a4a7864b2dad6da4090813322fcd29a11c92) | +| 2007/12/11 | Mel Gorman | [Use two zonelists per node instead of multiple zonelists v11r2](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=19770b32609b6bf97a3dece2529089494cbfc549) | 优化分配器处理区域列表, 区域列表指示分配目标区域的顺序. 类似地, 页面的直接回收会在区域数组上迭代. 为了保持一致性, 这组补丁将直接回收转换为使用分区列表, 并简化 zonelist 迭代器.
将每个节点的多个(两组)分区列表替换为两个分区列表, 一组用于系统中的每个分区类型, 另一组用于 GFP_THISNODE 分配. 根据 gfp 掩码允许的分区, 选择其中一个分区列表. 所有这些分区列表都会消耗内存并占用缓存线. | v1 ☑ v2.6.26-rc1 | [LORE 0/6](https://lore.kernel.org/lkml/20070928142326.16783.98817.sendpatchset@skynet.skynet.ie), [LORE RFC,v5,0.6](https://lore.kernel.org/lkml/20070911151939.11117.30384.sendpatchset@skynet.skynet.ie), [PatchWork v11r2,0/6](https://lore.kernel.org/all/20071211202157.1961.27940.sendpatchset@skynet.skynet.ie) | +| 2016/01/14 | Yaowei Bai | [mm/zonelist: enumerate zonelists array index](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c00eb15a8914b8ba84032a36044a5aaf7f71709d) | 使用 enum 定义了 zonelist double 的索引, ZONELIST_FALLBACK 和 ZONELIST_NOFALLBACK. | v1 ☑✓ 4.5-rc1 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c00eb15a8914b8ba84032a36044a5aaf7f71709d) | +* Zonelist Caching -### 2.2.3 fair allocation zone policy +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2006/12/06 | Paul Jackson | [memory page_alloc zonelist caching reorder structure](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=7253f4ef04b1cd138baf2b29a95473743ac0a307) | TODO | v1 ☑✓ 2.6.20-rc1 | [LORE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7253f4ef04b1cd138baf2b29a95473743ac0a307) | +| 2015/09/21 | Mel Gorman | [Remove zonelist cache and high-order watermark checking v4](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=dd56b046426760aa0c852ad6e4b6b07891222d65) | 引入区域列表缓存(ZLC)是为了跳过最近已知已满的区域. 这避免了一些耗时昂贵的操作, 如 cpuset 检查、水印计算和 zone_reclaim. 但是到此时的版本, 情况已经大不相同, ZLC 的复杂性更难证明. 因此将其移除. | v4 ☑✓ 4.4-rc1 | [LORE v4,0/10](https://lore.kernel.org/all/1442832762-7247-1-git-send-email-mgorman@techsingularity.net), [关键 COMMIT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f77cf4e4cc9d40310a7224a1a67c733aeec78836) | + +* Numa Zonelist Order + +NUMA 系统中存在多个节点, 每个节点对应一个 struct pglist_data 结构, 每个结点中可以包含多个 zone, 如: ZONE_DMA, ZONE_NORMAL, 这样就会产生几种排列顺序. + +v2.6.32 [commit f0c0b2b808f2 ("change zonelist order: zonelist order selection logic")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f0c0b2b808f232741eadac272bd4bc51f18df0f4) 引入了启动参数 "numa_zonelist_order"(`/proc/sys/vm/numa_zonelist_order`) 来配置 NUMA zonelist 的次序. 当前实现了 3 种模式: Legacy 方式, Node 方式和 Zone 方式, 通过 [current_zonelist_order](https://elixir.bootlin.com/linux/v2.6.32/source/mm/page_alloc.c#L2345) 全局的 current_zonelist_order 变量标识了系统中的当前使用的内存域排列方式,默认配置为 ZONELIST_ORDER_DEFAULT. [zonelist_order_name[current_zonelist_order]](https://elixir.bootlin.com/linux/v2.6.32/source/mm/page_alloc.c#L2346) 就标识了当前系统中所使用的 zonelist 次序的名称 "Default", "Node", "Zone", 可用于调试和输出. + +build_zonelists() 的过程中, 如果[指定了 ZONELIST_ORDER_NODE](https://elixir.bootlin.com/linux/v2.6.32/source/mm/page_alloc.c#L2657), 则通过 build_zonelists_in_node_order() 来构建, 如果[是 ZONELIST_ORDER_ZONE 次序](https://elixir.bootlin.com/linux/v2.6.32/source/mm/page_alloc.c#L2663), 则通过 build_zonelists_in_zone_order() 来构建. + +| Zonelist Order | 排列方式 | 描述 | +|:--:|:--------------------:|:------:|:----:| +| ZONELIST_ORDER_DEFAULT | Default | 由系统智能选择 Node 或 Zone 方式 | +| ZONELIST_ORDER_NODE | Node 模式(Node-based order) | 按节点顺序依次排列, 先排列本地节点的所有 zone, 再排列其它节点的所有 zone | +| ZONELIST_ORDER_ZONE | Zone 模式(Zone-based order) | 按 zone 类型从高到低依次排列各节点的同相类型 zone | + +内核提供了不同的 zonlists order 模式的支持, 但测试结果发现它们没有什么效果, 真正使用的人也很少. 因此 v4.14 [commit c9bff3eebc09 ("mm, page_alloc: rip out ZONELIST_ORDER_ZONE")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c9bff3eebc09be23fbc868f5e6731666d23cbea3) 完全删除了 ZONELIST_ORDER_ZONE 模式(Zone-based order), 但是依旧保留了 numa_zonelist_order, 用于提醒用户已经不至此 ZONELIST_ORDER_ZONE 模式. 至此 build_zonelists() 只会通过 build_zonelists_in_node_order() 构建 Node-based order 的 zonelist. + +内存回收的很多关键路径(比如页面回收等)都已经从等都已经从 Zone-Base 切到了 NODE-base 的方式. 而 移除了 ZONELIST_ORDER_ZONE 模式后, 内核现在只使用 ZONELIST_ORDER_NODE 模式, ZONELIST 始终处于 "Node" 由近及远的 order, 然后一个 Node 内部 zonelist order 的顺序也是固定的, 因此构建 NodeList 是可以的. 参见 [LORE 讨论](https://lore.kernel.org/all/20191123013613.566bb40a.fly@kernel.page). + +另外一方面, 内存中太多的路径存在由近及远遍历所有 NUMA 节点的诉求, 而当前采用的是曲线救国策略, 使用 for_each_zone_zonelist() 和 for_each_zone_zonelist_nodemask() 遍历所有 NUMA zonelist, 由于这两个宏本质是按照 NUMA 距离由近及远的顺序遍历各个 NUMA 的各个 ZONE, 因此如果单纯只想要遍历 NUMA node, 需要[不断保存和跳过对同一个 NUMA 的遍历](https://elixir.bootlin.com/linux/v4.14/source/mm/vmscan.c#L2879). 参见 [LORE 讨论](https://lore.kernel.org/all/20191122232847.3ad94414.fly@kernel.page). + +因此, 为了减少遍历节点所需的循环数, 补丁集 [Modify zonelist to nodelist v1](https://lore.kernel.org/all/20191121151811.49742-1-fly@kernel.page) 将 ZonelList 修改为 NodeList. 并引入了 for_each_node_nlist() 和 for_each_node_nlist_nodemask() 替代之前的两个宏用来辅助 NUMA node 的遍历. 而如果依旧想遍历 NUMA zonelist, 则可以通过 for_each_zone_nlist() 和 for_each_zone_nlist_nodemask() 来完成. + +https://lore.kernel.org/all/alpine.DEB.2.21.1911221551570.10063@www.lameter.com + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2002/11/21 | Andrew Morton | [change zonelist order: zonelist order selection logic](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f0c0b2b808f232741eadac272bd4bc51f18df0f4) | TODO | v1 ☑✓ 2.6.23-rc1 | [LKML v6,0/3](https://lkml.org/lkml/2007/5/10/61) | +| 2017/07/14 | Michal Hocko | [mm, page_alloc: rip out ZONELIST_ORDER_ZONE](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c9bff3eebc09be23fbc868f5e6731666d23cbea3) | [cleanup zonelists initialization](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=b95046b0472f7a805fa28fbcfc7205a76ff7a7d0) 系列的其中一个补丁. | v1 ☑✓ 4.14-rc1 | [LORE v1,1/9](https://lore.kernel.org/all/20170714080006.7250-1-mhocko@kernel.org) | +| 2019/11/21 | Pengfei Li | [Modify zonelist to nodelist v1](https://lore.kernel.org/all/20191121151811.49742-1-fly@kernel.page) | NA | v1 ☐☑✓ | [LORE v1,0/19](https://lore.kernel.org/all/20191121151811.49742-1-fly@kernel.page) | + + +* Print Zonelist + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2008/04/28 | Mel Gorman | [mm: print out the zonelists on request for manual verification](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=68ad8df42e12037c3894c9706ab428bf5cd6426b) | [Verification and debugging of memory initialisation V4](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=68ad8df42e12037c3894c9706ab428bf5cd6426b) | v1 ☑✓ 2.6.27-rc1 | [LORE RFC,0/4](https://lore.kernel.org/all/20080416135058.1346.65546.sendpatchset@skynet.skynet.ie)
*-*-*-*-*-*-*-*
[LORE v1,0/4](https://lore.kernel.org/all/20080428192839.23649.82172.sendpatchset@skynet.skynet.ie) | +| 2021/08/30 | Bharata B Rao | [mm/page_alloc: Print node fallback order](https://lore.kernel.org/all/20210830121603.1081-1-bharata@amd.com) | [Fix NUMA nodes fallback list ordering](https://lore.kernel.org/all/20210830121603.1081-1-bharata@amd.com) 的第一个补丁, 在 build_zonelists() 完成构建后, 添加了Fallback order for Node 的调试信息. | v1 ☐☑✓ | [LORE v1,1/2](https://lore.kernel.org/all/20210830121603.1081-2-bharata@amd.com) | + + +#### 2.2.2.3 fair allocation zone policy ------- 每个拥有一个工作负载的用户空间页面的区域必须以与区域大小成比例的速度老化. 否则, 单个页面在内存中停留的时间取决于它碰巧被分配的区域. 区域老化的不对称造成相当不可预测的老化行为, 并导致错误的页面被回收, 激活等. @@ -748,7 +867,7 @@ memblock 的内存占用是非常小的, 它采用静态数组的方式, 数组 即使是最基本的测试--反复读取比内存稍大的文件, 也会显示区域老化的破坏程度. 在这种情况下, 没有一个页面能够在内存中停留足够长的时间来被引用两次并被激活, 但是激活是非常频繁的 -通过 fair zone allocator policy 来解决这个问题. 通过一个非常简单的循环分配器. 每个分区允许一批与分区大小成比例的分配, 用 NR_ALLOC_BATCH(PatchWork 早期版本用 zone->alloc_batch) 记录, 分配后将被视为已满. 当所有区域都已尝试且分配器进入慢路径并启动 kswapd 回收时, 批处理计数器将重置. 分配和回收现在公平地分布到所有可用/允许的区域. +通过 fair zone allocator policy 来解决这个问题. 通过一个非常简单的循环分配器. 每个分区允许一批与分区大小成比例的分配, 用 NR_ALLOC_BATCH(PatchWork 早期版本用 zone->alloc_batch) 记录, 分配后将被视为已满. 当所有区域都已尝试且分配器进入慢路径并启动 kswapd 回收时, 批处理计数器将重置. 分配和回收现在公平地分布到所有可用/允许的区域. | 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | @@ -757,10 +876,26 @@ memblock 的内存占用是非常小的, 它采用静态数组的方式, 数组 | 2013/12/18 | Mel Gorman | [Configurable fair allocation zone policy v4](https://lore.kernel.org/patchwork/patch/428591) | NA | v2 ☑ 3.12-rc1 | [PatchWorkRFC,0/6](https://lore.kernel.org/patchwork/patch/428591) | | 2013/12/18 | Mel Gorman | [Configurable fair allocation zone policy v4](https://lore.kernel.org/patchwork/patch/397316) | NA | v4 ☐ | [PatchWork RFC v4,0/6](https://lore.kernel.org/patchwork/patch/397316) | | 2014/03/20 | Johannes Weiner | [mm: page_alloc: spill to remote nodes before waking kswapd](https://lore.kernel.org/patchwork/patch/450947) | 这个补丁引入了 ALLOC_FAIR.
在 NUMA 系统上, 节点可能会过早的开始回收页面, 甚至交换匿名页, 而即使远程节点上仍然有空闲页时.
这是 81c0a2bb515f ("mm: page_alloc: fair zone allocator policy") 和 fff4068cba48 ("mm: page_alloc: revert NUMA aspect of fair allocation policy") 合入后导致的.
在进行这些更改之前, 分配器将首先尝试所有允许的分区, 包括远程节点上的分区, 然后再唤醒任何 kswapds.
但是现在, 分配器快速路径同时作为公平通道, 它只能考虑本地节点, 以防止仅基于耗尽公平批次的远程溢出. 远程节点只在缓慢路径中考虑, 且在 kswapds 被唤醒之后.
是如果远程节点仍然有空闲内存, 其实不应该唤醒 kswapd 来重新平衡本地节点, 否则它可能会过早地 swap.
通过在 zonelist 上再添加一个不公平的传递来修复此问题, 该传递允许在本地公平传递失败后, 在进入慢路径并唤醒 KSWAPD 之前考虑远程节点. | v1 ☑ 3.15-rc1 | [PatchWork](https://lore.kernel.org/patchwork/patch/450947), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3a025760fc158b3726eac89ee95d7f29599e9dfa) | -| 2014/07/09 | Mel Gorman | [mm: page_alloc: Reduce cost of the fair zone allocation policy](https://lore.kernel.org/patchwork/patch/481298) | [Reduce sequential read overhead](https://lore.kernel.org/patchwork/patch/481299) 系列中的一个补丁. fair zone allocation policy 按单个区域大小的比例分配页面, 以确保页面老化公平. 在回收页之前, 分配页的区域不应影响页在内存中的时间. 启用区域回收模式后, 尝试停留在快速路径中的本地区域. 如果失败, 将进入慢路径, 该路径将从本地区域开始执行另一次传递, 但最终返回到不参与此区域列表的公平循环的远程区域. | v1 ☑ 3.17-rc1 | [PatchWork 6/6](https://lore.kernel.org/patchwork/patch/481298) | +| 2014/07/09 | Mel Gorman | [mm: page_alloc: Reduce cost of the fair zone allocation policy](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=4ffeaf3560a52b4a69cc7909873d08c0ef5909d4) | [Reduce sequential read overhead](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=4ffeaf3560a52b4a69cc7909873d08c0ef5909d4) 系列中的一个补丁. fair zone allocation policy 按单个区域大小的比例分配页面, 以确保页面老化公平. 在回收页之前, 分配页的区域不应影响页在内存中的时间. 启用区域回收模式后, 尝试停留在快速路径中的本地区域. 如果失败, 将进入慢路径, 该路径将从本地区域开始执行另一次传递, 但最终返回到不参与此区域列表的公平循环的远程区域. | v1 ☑ 3.17-rc1 | [PatchWork 6/6](https://lore.kernel.org/all/1404893588-21371-1-git-send-email-mgorman@suse.de/) | | 2016/04/15 | Mel Gorman | [mm, page_alloc: Reduce cost of fair zone allocation policy retry](https://lore.kernel.org/patchwork/patch/668985) | [Optimise page alloc/free fast paths v3](https://lore.kernel.org/patchwork/patch/668967) 系列中的一个补丁. 降低了 fair zone 分配器的开销. | v3 ☑ 4.7-rc1 | [PatchWork v6 00/28](https://lore.kernel.org/patchwork/patch/668967) | | 2016/07/08 | Mel Gorman | [mm, page_alloc: remove fair zone allocation policy](https://lore.kernel.org/patchwork/patch/696437/) | [Move LRU page reclaim from zones to nodes v9](https://lore.kernel.org/patchwork/patch/696437)系列中的一个补丁. 公平区域分配策略在区域之间交叉分配请求, 以避免年龄倒置问题, 即回收新页面来平衡区域. LRU 回收现在是基于节点的, 所以这应该不再是一个问题, 公平区域分配策略本身开销也不小, 因此这个补丁移除了它. | v9 ☑ [4.8-rc1](https://kernelnewbies.org/Linux_4.8#Memory_management) | [PatchWork v21](https://lore.kernel.org/patchwork/patch/696437), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e6cbd7f2efb433d717af72aa8510a9db6f7a7e05) | +### 2.2.3 NUMA 下的内存分配 memory policy +------- + +NUMA 系统中 CPU 访问不同节点的内存速度很有大的差别. 位于本地 NUMA 节点(或附近节点)上的内存比远程节点上的内存访问速度更快. 因此如果能通过策略去控制优先在哪些节点上内存分配, 能有效地提升业务的性能. + + +| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 | +|:----:|:----:|:---:|:----:|:---------:|:----:| +| 2010/05/04 | Miao Xie | [NUMA API](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=e1e71f9b6c8dd34be36573346ecbbb00f34a7a0a) | NA | v1 ☑✓ 2.6.7-rc1 | [LORE v1,0/2](https://lore.kernel.org/all/4BDFFCC8.4040205@cn.fujitsu.com) | +| 2010/05/04 | Miao Xie | [mempolicy: restructure rebinding-mempolicy functions](https://lore.kernel.org/all/4BDFFCC8.4040205@cn.fujitsu.com) | 4BDFFCC8.4040205@cn.fujitsu.com | v1 ☐☑✓ | [LORE v1,0/2](https://lore.kernel.org/all/4BDFFCC8.4040205@cn.fujitsu.com) | +| 2017/04/11 | Vlastimil Babka | [cpuset/mempolicies related fixes and cleanups](https://lore.kernel.org/all/20170411140609.3787-1-vbabka@suse.cz) | 20170411140609.3787-1-vbabka@suse.cz | v1 ☐☑✓ | [LORE v1,0/6](https://lore.kernel.org/all/20170411140609.3787-1-vbabka@suse.cz) | +| 2021/03/17 | Feng Tang | [Introduced multi-preference mempolicy](https://lore.kernel.org/all/1615952410-36895-1-git-send-email-feng.tang@intel.com) | 1615952410-36895-1-git-send-email-feng.tang@intel.com | v4 ☐☑✓ | [LORE v4,0/13](https://lore.kernel.org/all/1615952410-36895-1-git-send-email-feng.tang@intel.com) | +| 2021/08/03 | Feng Tang | [Introduce multi-preference mempolicy](https://lore.kernel.org/patchwork/patch/1471473) | 参见 LWN 报道 [NUMA policy and memory types](https://lwn.net/Articles/862707).
引入 MPOL_PREFERRED_MANY 的 policy, 该 mempolicy 模式可用于 set_mempolicy 或 mbind 接口.
1. 与 MPOL_PREFERRED 模式一样, 它允许应用程序为满足内存分配请求的节点设置首选项. 但是与 MPOL_PREFERRED 模式不同, 它需要一组节点.
2. 与 MPOL_BIND 接口一样, 它在一组节点上工作, 与 MPOL_BIND 不同, 如果首选节点不可用, 它不会导致 SIGSEGV 或调用 OOM killer. | v7 ☑ 5.15-rc1 | [LORE v4,00/13](https://lore.kernel.org/lkml/1615952410-36895-1-git-send-email-feng.tang@intel.com)
*-*-*-*-*-*-*-*
[LORE v7,0/5](https://patchwork.kernel.org/project/linux-mm/cover/1627970362-61305-1-git-send-email-feng.tang@intel.com), [COMMIT](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/tools/perf/builtin-record.c?id=a38a59fdfa10be55d08e4530923d950e739ac6a2) | +| 2021/11/01 | "Aneesh Kumar K.V" | [mm: add new syscall set_mempolicy_home_node](https://patchwork.kernel.org/project/linux-mm/cover/20211101050206.549050-1-aneesh.kumar@linux.ibm.com) | 增加了 set_mempolicy_home_node() 来为用户空间的一段地址 [start, srart + len] 指定内存分配的主节点 home_node. 主节点 home_node 应与 MPOL_PREFERRED_MANY 或 MPOL_BIND 内存分配策略结合使用. 这些策略可以指定一组将用于新内存分配的节点, 但不能说明这些节点中的哪个节点(如果有)是首选节点. 如果设置了 home_node, 则分配内存时将优先在该节点上进行分配; 否则, 主节点 home_node 内存不足, 则将回退到有效策略允许的其他节点上分配, 首选最接近主节点的节点. 其目的是让应用程序能够更好地控制内存分配, 同时避免来自慢速节点的内存. 参见 LWN 报道 [Some upcoming memory-management patches](https://lwn.net/Articles/875587). | v1 ☐ | [PatchWork v4,0/3](https://patchwork.kernel.org/project/linux-mm/cover/20211101050206.549050-1-aneesh.kumar@linux.ibm.com) | +| 2022/04/12 | Wei Yang | [mm/page_alloc: add same penalty is enough to get round-robin order](https://lore.kernel.org/all/20220412001319.7462-1-richard.weiyang@gmail.com) | 20220412001319.7462-1-richard.weiyang@gmail.com | v3 ☐☑✓ | [LORE](https://lore.kernel.org/all/20220412001319.7462-1-richard.weiyang@gmail.com) | + ### 2.2.4 内存水线 ------- @@ -2960,6 +3095,9 @@ LRU 组织形式的变更和 LRU lock 的变更是无法割裂开的. 每次 LRU ### 4.2.7 页面老化(active 与 inactive 链表拆分) ------- +#### 4.2.7.1 页面老化 +------- + 教科书式的 PFRA 会提到要用 LRU (Least-Recently-Used) 算法, 该算法思想基于 : 最近很少使用的页, 在紧接着的未来应该也很少使用, 因此, 它可以被当作替换掉的候选页. @@ -3010,7 +3148,9 @@ active 头(热烈使用中) > active 尾 > inactive 头 > inactive 尾(被驱逐 | 2010/02/22 | Johannes Weiner | [vmscan: detect mapped file pages used only once](https://lore.kernel.org/patchwork/patch/189878) | 在文件页面线性流的场景, 源源不断的文件页面在被一次引用后, 被激活到 active LRU list 中, 将导致 active LRU list 中充斥着这样的页面. 为了解决这个问题, 通过重用 PG_referenced 页面标志检测和识别这类引用, 设置其只有在第二次引用后, 才能被激活. | v2 ☑ 2.6.31-rc1 | [PatchWork](https://lore.kernel.org/patchwork/patch/189878), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=645747462435d84c6c6a64269ed49cc3015f753d) | | 2011/08/08 | Konstantin Khlebnikov | [vmscan: promote shared file mapped pages](https://lore.kernel.org/patchwork/patch/262019) | 上面的补丁极大地减少了一次性使用的映射文件页的生存期. 不幸的是, 它还减少了所有共享映射文件页面的生存时间. 在这个补丁之后, 如果这个页面已经通过几个 ptes 被多次使用, page_check_references() 也会在第一次非活动列表扫描时激活文件映射页面. | v2 ☑ 3.3-rc1 | [PatchWork v2](https://lore.kernel.org/patchwork/patch/262019), [commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34dbc67a644f11ab3475d822d72e25409911e760) | -* 多级 LRU + +#### 4.2.7.2 多级 LRU +------- [Linux 内核页面置换算法](https://blog.eastonman.com/blog/2021/04/linux-multi-lru/) @@ -4791,6 +4931,7 @@ khugepaged 处理流程 ### 8.1.1 具名匿名页 Anonymous VMA naming ------- +参见 LWN 报道 [Not-so-anonymous virtual memory areas](https://lwn.net/Articles/867818) 用户空间进程通常有多个分配器, 每个分配器执行匿名 MMAP 以获取内存. 在整体检查单个进程或系统的内存使用情况时, 如果能够分解每个层分配的各种堆并检查它们的大小、RSS和物理内存使用情况对分析内核的内存分配和管理非常有用. @@ -4806,7 +4947,7 @@ khugepaged 处理流程 |:----:|:----:|:---:|:----:|:---------:|:----:| | 2013/07/03 | Suren Baghdasaryan | [mm: add sys_madvise2 and MADV_NAME to name vmas](https://lore.kernel.org/linux-mm/1372901537-31033-1-git-send-email-ccross@android.com) | 这组通过添加新的 MADVESE2 系统调用, 可以使用 MADV_NAME 来将名称附加到现有的 VMA 上.
1. 向每个 vma 添加一个包含名称字符串的 vma_name 结构, 系统中每个具有相同名称的 vma 都保证指向相同的 vma_name 结构. 可以直接通过比较指针进行名称相等比较.
2. 匿名 VMA 的名称在 `/proc/pid/maps` 中显示为 `[anon:]`. 所有命名 VMA 的名称显示在 `/proc/pid/smap` 中的 Name 字段中用于命名 VMA.
3. 此修补程序添加的未命名 vma 的唯一成本是检查 vm_name 指针. 对于命名 vma, 它会将 refcount 更新添加到拆分/合并/复制 vma 中, 如果命名 vma 是具有该名称的最后一个vma, 则取消映射可能需要使用全局锁. | v2 ☐ | [PatchWork RFC](https://lore.kernel.org/linux-mm/1372901537-31033-1-git-send-email-ccross@android.com) | | 2020/09/01 | Sumit Semwal | [Anonymous VMA naming patches](https://lore.kernel.org/linux-mm/20200901161459.11772-1-sumit.semwal@linaro.org) | NA | v7 ☐ 5.9-rc3 | [PatchWork v7,0/3](https://lore.kernel.org/linux-mm/20200901161459.11772-1-sumit.semwal@linaro.org) | -| 2021/10/19 | Suren Baghdasaryan | [Anonymous VMA naming patches](https://lwn.net/Articles/867818) | NA | v2 ☐ | [2021/08/27 PatchWork v8,0/3](https://patchwork.kernel.org/project/linux-mm/cover/20210827191858.2037087-1-surenb@google.com)
*-*-*-*-*-*-*-*
[2021/10/19 PatchWork v11,1/3](https://patchwork.kernel.org/project/linux-mm/patch/20211019215511.3771969-1-surenb@google.com) | +| 2021/10/19 | Suren Baghdasaryan | [Anonymous VMA naming patches](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=78db3412833dc9c479cd17412035f216cfd01a29) | NA | v11 ☑✓ 5.17-rc1 | [2021/08/27 PatchWork v8,0/3](https://patchwork.kernel.org/project/linux-mm/cover/20210827191858.2037087-1-surenb@google.com)
*-*-*-*-*-*-*-*
[2021/10/19 PatchWork v11,1/3](https://patchwork.kernel.org/project/linux-mm/patch/20211019215511.3771969-1-surenb@google.com) | ### 8.1.2 其他 @@ -6496,7 +6637,7 @@ ZONE_MOVABLE 一个 pseudo zone, 它实际是从内核划分的某个 zone 中 | 2016/01/08 | Taku Izumi | [mm: Introduce kernelcore=mirror option](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?id=342332e6a925e9ed015e5465062c38d2b86ec8f9) | 内存镜像, 提供内存可靠性分级的功能.
通过 UEFI BIOS(规范 2.5) 上报镜像内存(即高可靠内存 mirrored/reliable)的范围和非镜像内存(即低可靠内存)的两种内存. 内核默认使用高可靠的内存, 用 NORMAL ZONE 管理, 用户态优先使用普通(低可靠的)内存, 使用 MOVABLE ZONE 管理. | v4 ☑ 4.6-rc1 | [LKML RFC](https://lkml.org/lkml/2015/10/9/24)
*-*-*-*-*-*-*-*
[LKML v1](https://lkml.org/lkml/2015/10/15/9)
*-*-*-*-*-*-*-*
[LKML v2,0/2](https://lkml.org/lkml/2015/11/27/18)
*-*-*-*-*-*-*-*
[LKML v3,0/2](https://lkml.org/lkml/2015/12/8/836)
*-*-*-*-*-*-*-*
[LKML v4,0/2](https://lkml.org/lkml/2016/1/8/88), [PatchWork v4,0/2](https://lore.kernel.org/lkml/1452241523-19559-1-git-send-email-izumi.taku@jp.fujitsu.com) | | 2016/06/27 | Xishi Qiu | [mm: mirrored memory support for page buddy allocations](https://lore.kernel.org/lkml/558E084A.60900@huawei.com) | 内存镜像的功能 | RFC v2 ☐ | [PatchWork RFC,v2,0/8](https://lore.kernel.org/lkml/558E084A.60900@huawei.com) | | 2018/2/12 | David Rientjes | [mm: Introduce kernelcore=mirror option](http://lore.kernel.org/patchwork/patch/574230) | `kernelcore=` 和 `movablecore=` 都可以分别用于定义系统上 ZONE_NORMAL 和 ZONE_MOVABLE 的数量. 然而, 这需要在指定命令行时知道系统内存容量. 这个补丁引入了将 `kernelcore` 和 `movablecore` 定义为系统总内存的百分比的能力. 这对于希望将 ZONE_MOVABLE 的数量定义为系统内存的比例(而不是硬编码的字节值)的系统软件来说是很方便的. 要定义百分比, 参数的最后一个字符应该是 '%'. | v4 ☑ 4.17-rc1 | [LKML 1/2](https://lkml.org/lkml/2018/2/12/1024) | -| 2022/03/26 | mawupeng | [introduce mirrored memory support for arm64](https://patchwork.kernel.org/project/linux-mm/cover/20220326064632.131637-1-mawupeng1@huawei.com) | 镜像内存支持 arm64. | v1 ☐☑ | [LORE v1,0/9](https://lore.kernel.org/all/20220326064632.131637-1-mawupeng1@huawei.com)
*-*-*-*-*-*-*-*
[LORE v2,0/9](https://lore.kernel.org/r/20220414101314.1250667-1-mawupeng1@huawei.com) | +| 2022/03/26 | mawupeng | [introduce mirrored memory support for arm64](https://patchwork.kernel.org/project/linux-mm/cover/20220326064632.131637-1-mawupeng1@huawei.com) | 镜像内存支持 arm64. 参见 phoronix 报道 [Huawei Working On UEFI Mirrored Memory Support For Linux AArch64](https://www.phoronix.com/scan.php?page=news_item&px=Linux-AArch64-Mirrored-Memory). | v1 ☐☑ | [LORE v1,0/9](https://lore.kernel.org/all/20220326064632.131637-1-mawupeng1@huawei.com)
*-*-*-*-*-*-*-*
[LORE v2,0/9](https://lore.kernel.org/r/20220414101314.1250667-1-mawupeng1@huawei.com) | ### 14.13.4 其他 ZONE_MOVABLE 相关