当前位置:首页>Linux>Linux mm 2026-04-06 最新 Feature 分析报告

Linux mm 2026-04-06 最新 Feature 分析报告

  • 2026-09-09 03:31:22
Linux mm 2026-04-06 最新 Feature 分析报告
目录
  1. mm: Generalize vmemmap optimization for DAX and HugeTLB
  2. mm/vmpressure:高 order 回收时跳过 socket 压力上报
  3. mm/slub:将 freelist 构建推迟到新 slab 的 bulk 分配之后
  4. selftests/mm: make MM selftests more CI friendly
  5. mm/damon/core: avoid time-quota permanently disabling scheme
  6. mm/damon: add node_eligible_mem_bp / node_ineligible_mem_bp goal metrics

1. mm: Generalize vmemmap optimization for DAX and HugeTLB

系列:[PATCH 00/49] mm: Generalize vmemmap optimization for DAX and HugeTLB作者: Muchun Song版本: v1(49 patches)

背景

HugeTLB Vmemmap Optimization(HVO)最早作为 HugeTLB 专属特性引入:对于 2MB/1GB 大页,其多个 tail 的 struct page 内容完全一致,可以把它们的 vmemmap 物理页合并为同一张只读页,从而节省大量 struct page 内存。随着时间推移,Device DAX 也有同样的诉求——对 2MB PMD 映射或更大粒度的持久内存区域,tail struct page 同样高度冗余——但 DAX 侧自己又实现了一套独立的 vmemmap 去重路径(vmemmap_populate_compound_pages 等),与 HugeTLB 的代码并行存在。结果是两条路径各自维护 arch 钩子、ARCH_WANT_OPTIMIZE_*_VMEMMAP 旗标、初始化时序以及 bootmem/hotplug 的特殊处理,代码重复、互相牵扯,并且引入了若干只在一侧被发现的 bug(如 sparse vmemmap 初始化丢失 pgmap、HVOed DAX 的 vmemmap 计数失衡、HVO DAX 缺失 arch 特定页表同步等)。

解决的问题

  • HugeTLB 与 DAX 两套 vmemmap 去重实现重复,长期维护成本高。
  • 多处与 pgmap/zone/migratetype 相关的隐性 bug:sparse_init 阶段 DAX vmemmap 计数不平衡、ZONE_DEVICE 复合页 pageblock migratetype 未初始化、跨 zone gigantic 大页 bootmem 处理错误等。
  • DAX 的 vmemmap 节省粒度不如 HugeTLB,每个 2MB huge page 仍多占 4KB struct page。
  • 现有共享 tail vmemmap 页为可写,存在被意外写坏的风险。
  • 多架构要分别适配 SPARSEMEM_VMEMMAP_PREINIT,架构门槛高。

如何做

系列按五个阶段推进:Patch 1–6 先修掉 sparse vmemmap/DAX 相关的 bug 与 migratetype 初始化缺陷;Patch 7–13 重构 sparse vmemmap 初始化流程,把 subsection_map_init/sparse_init 顺序重排,将 sparse_init 延后到 zone 初始化之后,并把 sparse_vmemmap_init_nid_late 合并入 sparse_init_nid;Patch 14–26 从 HugeTLB 解耦,引入通用的 compound page vmemmap 优化宏与函数(vmemmap_shared_tail_page()、compound order 保存到 struct mem_section、SPARSEMEM_VMEMMAP_OPTIMIZATION 新 Kconfig、通用 vmemmap_can_optimize() 等),并把 VMEMMAP_POPULATE_PAGEREF 等遗留旗标清除;Patch 27–39 让 HugeTLB 与 Device DAX 都切到这套通用框架,删除 HUGE_BOOTMEM_HVO 等 HugeTLB 私有标志,合并共享 tail 分配路径,并为 DAX 引入 section zone 字段;Patch 40–49 清理旧代码、把共享 tail 页 remap 成只读、丢弃 ARCH_WANT_OPTIMIZE_DAX_VMEMMAP、从 vmemmap populate API 中去掉 @pgmap 参数,最后把 HVO 的含义从 "HugeTLB" 正式改名为 "Hugepage Vmemmap Optimization" 并重写 Documentation/mm/vmemmap_dedup.rst。粗略统计,整个系列净删除约 700 行代码(+513/-1197)。

收益

作者未提供 benchmark 数字,但从封面信可推断:

  • 关闭 CONFIG_DEFERRED_STRUCT_PAGE_INIT 时,所有使用 HVO 的 struct page 都可跳过 memmap_init 初始化,显著缩短启动时间。
  • Device DAX 每个 2MB 大页额外节省 4KB struct page 内存。
  • 所有支持 HVO 的架构自动获得 SPARSEMEM_VMEMMAP_PREINIT 的效果,无需各自适配。
  • 共享 tail vmemmap 页变为只读,提升内核安全性。
  • HugeTLB 与 DAX 共用一套代码,减少 ~700 行重复实现与长期维护负担。

2. mm/vmpressure:高 order 回收时跳过 socket 压力上报

系列:[PATCH v4] mm/vmpressure: skip socket pressure for costly order reclaim作者: JP Kobryn (Meta)版本: v4(单 patch,已获 Rik van Riel Reviewed-by 与 Hannes/Shakeel/Kuba Acked-by)

背景

vmpressure() 通过 scanned:reclaimed 比值来推断内存压力等级,并在 level > VMPRESSURE_LOW 时调用 vmpressure_socket 通知 TCP 栈降低 sk_rcvbuf/收窗以缓解内存紧张。这个启发式本就不完美,而当触发 reclaim 的是 高 order 分配(> PAGE_ALLOC_COSTLY_ORDER,即 order ≥ 4)时会更加失真:碎片化系统上,kswapd 为了凑出 order-7 物理连续区会扫很多页却几乎无法回收(页面都在用),扫/回收比很差,但整机其实还有数十 GB 空闲。内核自身对 costly order 的语义就是"失败允许、主要靠 compaction",不应据此判断整机或 memcg 真的处在压力中。

解决的问题

  • 高 order(如 kswapd 为 HugeTLB、slub 高阶 slab、网卡大 MTU 等分配 order-7)回收造成的假性 socket 压力。
  • 被错误上报到 socket 层的压力会把 TCP 接收窗口卡在 rcv_ssthresh 最小值,严重劣化网络吞吐。
  • 生产事故实证:某主机上 94% 的 vmpressure() 调用来自 order-7 kswapd 回收,此时尚有 ~15GB 可用内存、cgroup pressure 为 0。

如何做

给 vmpressure() 新增 int order 参数,由三处 vmscan.c 调用点从 sc->order 传入(shrink_one、shrink_node_memcgs、shrink_node)。在 vmpressure() 内部,原先的 if (level > VMPRESSURE_LOW) 条件追加 && order <= PAGE_ALLOC_COSTLY_ORDER,只有低 order 回收才会调用 vmpressure_socket。事件通知路径(eventfd 级别上报)仍照常触发,不受过滤影响。vmpressure_prio() 代表"priority 过低 → critical"的通路,显式传入 order = 0,保证低优先级产生的 critical 级事件不会被本次过滤无意压制;memcg 直接回收走 try_to_free_mem_cgroup_pages(),其 sc->order 本来就是 0,自然也不受影响。vmpressure.h 同步更新 stub 签名,并顺手移除多余的 extern。

收益

生产实测数据(v4 新增):

  • 某主机升级前:3,843 个 TCP 连接中 723 个 (19%) 卡在最小收窗 rcv_ssthresh=19712。
  • Live-patch 后约 30 分钟:3,470 个 TCP 连接中 0 个 仍卡在最小收窗。
  • 同时 bpftrace 观察到 94% 的 vmpressure() 调用源自 order-7 kswapd,这部分调用将不再误报 socket 压力。

3. mm/slub:将 freelist 构建推迟到新 slab 的 bulk 分配之后

系列:[PATCH v3] mm/slub: defer freelist construction until after bulk allocation from a new slab作者: Shengming Hu (ZTE)版本: v3(单 patch)

背景

SLUB 的 refill_objects() 在 per-cpu sheaf 为空时会向底层申请一个全新的 slab,再从中一口气拉出 min..max 个对象填充 sheaf。当前实现里,allocate_slab() 在返回新 slab 之前会先把 slab 上所有对象串成一条单向 freelist(shuffle_freelist() 或线性 setup_object() 循环);refill_objects() 随后沿着 freelist 一次吃掉大部分甚至全部对象。对于 bulk 场景,尤其是整 slab 被一次吃光时,刚刚费力构建的 freelist 立刻被丢弃,白白走了一遍 setup_object、set_freepointer、kasan_init_slab_obj 以及 CONFIG_SLAB_FREELIST_RANDOM=y 下昂贵的 random_seq/prandom_u32_state。此外作者发现 setup_object() 在新编译器下未被稳定 inline,热路径上也有性能损失。

解决的问题

  • 新 slab 的 freelist 构建与 bulk 分配路径存在重复工作,全 slab 被吃光时 100% 浪费。
  • CONFIG_SLAB_FREELIST_RANDOM=y 的额外开销在 bulk 场景被放大。
  • setup_object() 未被稳定 inline 带来额外函数调用成本。
  • 既要保留 RANDOM=y/n 的行为一致性,又不希望写两份 allocation 代码。

如何做

核心是引入一个 struct slab_obj_iter 迭代器(定义在 mm/slab.h),用统一的 next_slab_obj() 既能按线性顺序也能按预生成随机序列返回下一个空闲对象,且把 RANDOM 相关状态(freelist_count、page_limit、random 标志、随机 pos 初值)全部内聚在 #ifdef 里。然后拆分 slab 分配两步:

  1. allocate_slab() 只分配并初始化 slab 元数据,把 slab->freelist = NULL,不再调用 shuffle_freelist() 或线性 freelist 构建循环;
  2. 新加 build_slab_freelist(),只对 slab->objects - slab->inuse 个剩余对象构建 freelist;
  3. 原 new_slab() 保留旧行为——先 allocate_slab(),再 init_slab_obj_iter() + build_slab_freelist(),对非 bulk 调用完全透明;
  4. refill_objects() 改为直接调 allocate_slab()(裸 slab),交给 alloc_from_new_slab() 通过迭代器直接吐对象给调用方 p[] 数组,slab->inuse 一步置为 target_inuse = needs_add_partial ? count : slab->objects,最后仅在 slab 仍需进 partial 列表时才对剩余对象 build_slab_freelist()。顺手把 setup_object() 标成 inline,并删除旧的 next_freelist_entry() / shuffle_freelist()。

收益

作者提供了 slub_bulk_bench 详尽数据(qemu-kvm x86,Linux 7.0.0-rc6-next-20260330,8 CPU/1GB,每次 20 轮共 256MB):

**CONFIG_SLAB_FREELIST_RANDOM=n**:per-object 时间下降 41%–70%,例如

  • obj=16 B, batch=256:4.62 → 2.72 ns/object(-41.1%)
  • obj=128 B, batch=32:17.91 → 5.73 ns/object(-68.0%)
  • obj=256 B, batch=32:21.03 → 6.22 ns/object(-70.4%)

**CONFIG_SLAB_FREELIST_RANDOM=y**:per-object 时间下降 59%–71%,例如

  • obj=16 B, batch=256:8.37 → 3.38 ns/object(-59.6%)
  • obj=256 B, batch=32:26.27 → 7.54 ns/object(-71.3%)
  • obj=512 B, batch=32:26.69 → 7.73 ns/object(-71.0%)

4. selftests/mm: make MM selftests more CI friendly

系列:[PATCH 00/53] selftests/mm: make MM selftests more CI friendly作者: Mike Rapoport (Microsoft)版本: v1(53 patches)

背景

tools/testing/selftests/mm/run_vmtests.sh 长期以来承担着 HugeTLB 环境准备的重任:在脚本启动阶段读取 /proc/meminfo 计算 Hugepagesize 与 HugePages_Free,然后写 /proc/sys/vm/nr_hugepages,并在 uffd-stress、hugetlb-shm、hugetlb_fault_after_madv、uffd-wp-mremap、hugetlb-soft-offline 等 case 之间反复调整页数、SHM 上限、soft_offline 开关。这种"壳脚本包办环境"的模式导致脚本本身臃肿(本系列净删约 170 行),又与 kselftest TAP 框架格格不入:很多 HugeTLB/THP 用例还是裸 main(),没有 ksft_print_header()/ksft_test_result_*,CI 系统难以稳定解析、难以独立跑单个 case。此外旧有一些"老问题":hugetlb-read-hwpoison 访问被投毒内存会直接被 SIGBUS 干掉;khugepaged 的 collapse_single_pte_entry_compound 对 tmpfs 直接 skip(注释说"MADV_DONTNEED 不能驱逐 tmpfs 页",但实际可以用 MADV_REMOVE);migration 测试硬编码 2MB huge page。

解决的问题

  • run_vmtests.sh 承担过多 HugeTLB 环境探测/配置/还原逻辑,难维护且在不同机器上易失败;
  • 多个 hugetlb/THP/ksm/uffd/pkey/va_high_addr_switch 用例未接入 kselftest 框架,TAP 输出缺失;
  • hugetlb-read-hwpoison 无 SIGBUS handler 导致测试提前终止;
  • khugepaged 的 collapse_single_pte_entry_compound 对 shmem 完全被 skip;
  • migration 假设 hugepage=2MB 在 arm64/ppc 等平台不成立;
  • uffd-wp-mremap 要求每种 size 都至少一页,脚本里一堆 for f in /sys/.../nr_hugepages 的循环;
  • HugeTLB page size 检测和 nr_hugepages 设置散落各处,没有复用。

如何做

系列分五段:1) Patch 1–4 是小修(SIGBUS handler、tmpfs 用 MADV_REMOVE 打开 shmem collapse、migration 动态获取 huge size、THP/KSM 不再依赖 HAVE_HUGEPAGES);2) Patch 5–6 合并 map_hugetlb 到 hugepage-mmap 并把 hugepage-* 系列重命名为 hugetlb-*,统一命名;3) Patch 7–19 把 hugetlb-shm/vmemmap/madvise/madv_vs_map/read-hwpoison、khugepaged、ksm_tests、protection_keys、uffd-stress、uffd-unit-tests、va_high_addr_switch 全部切到 kselftest 框架;4) Patch 20–28 把 thp_settings.[ch] 重命名为 hugepage_settings.[ch],合并 HugeTLB helper,新增 detect_hugetlb_page_size()(unsigned long)、get/set nr_hugepages、hugetlb_save/restore,并通过 atexit()+signal handler 保证失败路径也能恢复;read_file/read_num/write_num 下沉到 vm_util,新增 SHM 上限 save/restore helper;5) Patch 29–51 给各个具体用例(cow、gup_longterm、gup_test、hmm-tests、hugepage_dio、hugetlb_fault_after_madv、hugetlb-mmap/mremap/soft-online/vmemmap、migration、pagemap_ioctl、protection_keys、thuge-gen、uffd-stress、uffd-unit-tests、uffd-wp-mremap、va_high_addr_switch 等)加入库化的 HugeTLB 准备/清理;6) Patch 52–53 最终删光 run_vmtests.sh 里所有 HugeTLB 探测和 nr_hugepages 调整逻辑,新增"空闲内存过低时自动 drop_caches"的兜底。

收益

作者未提供 benchmark 数据,但从代码变化可推断:run_vmtests.sh 减少约 170 行 shell,不再有"两次尝试写 nr_hugepages"这类启发式;所有 HugeTLB 用例自描述所需 hugepage 数量并通过 atexit() 自恢复,CI 可以独立跑单个 case 而不依赖外层脚本;覆盖率提高(khugepaged shmem 变体从 skip 转为实跑,hugetlb-read-hwpoison 不再被 SIGBUS 截断);TAP 输出覆盖面显著扩大(khugepaged/ksm_tests/protection_keys/uffd-* 等 9+ 个大型测试首次成为标准 kselftest)。


5. mm/damon/core: avoid time-quota permanently disabling scheme

系列:[RFC PATCH] mm/damon/core: avoid time-quota permanently disabling scheme作者: SeongJae Park版本: RFC(1 patch,附 Fixes + Cc stable 5.16+)

背景

DAMOS 的时间配额(quota->ms)机制把"每个 charge 窗口内最多花多少 CPU 时间"换算成一个"有效 size 配额 (esz)":esz = throughput * quota->ms,其中 throughput = total_charged_sz / total_charged_ns。DAMON 以 region 为单位施加 action,而 region 操作有下限 damon_ctx->min_region_sz——若 esz < min_region_sz,damos_apply_scheme() 会直接跳过该 region 的 action。

解决的问题

当 scheme 吞吐极低(大 region 但 action 很慢,例如页迁移被 throttled),throughput * quota->ms 计算出的 esz 会小于 min_region_sz,于是 action 被跳过;action 被跳过意味着 total_charged_sz 与 total_charged_ns 都不会增长;下一个 charge 窗口开启时 damos_set_effective_quota() 再次用不变的 throughput 重新计算 esz,仍然小于 min_region_sz——scheme 进入一个自锁死循环,无任何进展。由于 esz 只能被向下修正(quota goals、sz quota 都只能收紧),DAMOS quota goals 也救不回来。用户只能手动在线改 quota->ms 或重装 scheme;且 DAMON 目前缺乏观测手段让用户意识到这种"悄悄停摆"。

如何做

在 damos_set_effective_quota() 中把 damon_ctx 作为额外参数传入,计算完 esz = min(throughput * quota->ms, esz) 之后追加一行 esz = max(ctx->min_region_sz, esz);——保证时间配额计算出的 esz 下限至少为 min_region_sz,从而至少有一个 region 的 action 会被实际执行,进而产生新的 total_charged_sz/ns 样本,后续 charge 窗口可以正常地重新估算 throughput。同步更新了两处调用者(首个 charge 窗口和新窗口开启时)。

收益

修复"DAMOS scheme 被时间配额永久悄悄禁用"这一退化场景;避免需要用户介入的在线 mitigation;无性能数据(属于 correctness fix)。


6. mm/damon: add node_eligible_mem_bp / node_ineligible_mem_bp goal metrics

系列:[PATCH v6 0/1] mm/damon: add node_eligible_mem_bp and node_ineligible_mem_bp goal metrics作者: Ravi Jonnalagadda版本: v6(1 patch)

背景

异构内存(DRAM + CXL)系统下,管理员希望按"访问热度"把热页按比例分布在不同 tier 上,例如"70% 热数据在 DRAM、30% 在 CXL"。DAMOS 现有的 quota goal metrics(NODE_MEM_USED_BP、NODE_MEM_FREE_BP、NODE_MEMCG_*、ACTIVE/INACTIVE_MEM_BP 等)描述的是节点或 cgroup 的整体内存占用比率,无法表达"符合当前 scheme 过滤条件(migrate_hot 等)的内存在某节点占总符合条件内存的比例"。因此目前无法用纯 DAMON 自动维持热数据跨 tier 的目标分布。

解决的问题

  • 缺少"scheme-eligible 内存跨 NUMA 节点分布"的反馈信号;
  • 无法构建 push/pull 双向 migrate_hot scheme 的闭环:PUSH (DRAM→CXL) 与 PULL (CXL→DRAM) 需要互补指标才能稳定收敛到目标比例;
  • 管理员只能用静态阈值或外部控制器代替闭环。

如何做

新增两个枚举值 DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP、DAMOS_QUOTA_NODE_INELIGIBLE_MEM_BP(include/linux/damon.h),定义为:

  • eligible_bp = scheme_eligible_bytes_on_node / total_scheme_eligible_bytes * 10000
  • ineligible_bp = 10000 − eligible_bp

核心是在 mm/damon/core.c 新增 damos_calc_eligible_bytes():遍历 scheme 的 targets 和 regions,对每个通过 __damos_valid_target() 的 region 按 addr_unit 换算到物理地址后,用 damon_get_folio(PHYS_PFN(addr)) 取 folio,累加 folio_size 到 total_eligible,若 folio_nid(folio) == nid 则累加到 node_eligible;对跨 region 边界的 folio 做 min(folio_sz, end_addr − addr) 裁剪;未拿到 folio 时按 PAGE_SIZE 步进。因依赖 damon_get_folio(),整块逻辑用 #ifdef CONFIG_DAMON_PADDR 保护,non-PADDR 和 non-NUMA 都提供返回 0 的桩。配套地,damos_set_quota_goal_current_value()、damos_quota_score()、damos_goal_tune_esz_bp_consist/temporal()、damos_set_effective_quota() 全部从"只拿 damos_quota*"改成"拿 damon_ctx* 和 damos*",以便把上下文透传给 eligible 计算。sysfs 侧 mm/damon/sysfs-schemes.c 追加两个 metric 名字 node_eligible_mem_bp、node_ineligible_mem_bp,并在 damos_sysfs_add_quota_score() 里像 NODE_MEM_FREE_BP 一样接收 nid。

收益

作者提供功能性测试而非硬 benchmark:两节点 DRAM+CXL 平台上用 PUSH(nid=0, target ineligible=3000) + PULL(nid=0, target eligible=7000) 组合 migrate_hot scheme,配合 damon/next 的 TEMPORAL goal tuner 能快速收敛到 7:3 热内存比例;CONSIST tuner 也能收敛但更慢(靠配额反馈震荡自调)。这将原本需要外部控制器的分层迁移变为 DAMON 内部闭环;管理员只需声明期望比例,scheme 自己通过 quota goal 调整 esz 达成目标。


总结

新机制 / 新接口

#
系列
要点
1
Generalize vmemmap optimization for DAX and HugeTLB
 [v1, 49p]
HVO 从 HugeTLB 专属升级为通用 compound page vmemmap 优化框架,DAX 与 HugeTLB 共用一套代码,新增 SPARSEMEM_VMEMMAP_OPTIMIZATION Kconfig,共享 tail 页 remap 为只读
6
DAMON node_eligible/ineligible_mem_bp goal metrics
 [v6, 1p]
新增两个 DAMOS quota goal metric,用于按 NUMA 节点反馈"scheme-eligible 内存比例",使 DRAM/CXL 分层迁移可在 DAMON 内部闭环

性能优化

#
系列
量化数据
2
vmpressure: skip socket pressure for costly order reclaim
 [v4, 1p]
生产 live-patch 后,TCP 连接卡在最小 rcv_ssthresh 的比例从 19% (723/3843) 降至 0/3470;94% 的 vmpressure 调用源自 order-7 kswapd
3
slub: defer freelist construction until after bulk alloc
 [v3, 1p]
slub_bulk_bench
 per-object 时间下降 41%–71%,RANDOM=y 时最多 -71.3%(256B/batch=32:26.27 → 7.54 ns/obj)

Bug Fix

#
系列
影响
5
damon/core: avoid time-quota permanently disabling scheme
 [RFC, 1p]
修复 DAMOS scheme 在低吞吐场景因 esz < min_region_sz 自锁死循环;Fixes: 1cd243030059,Cc: stable 5.16+

内部优化 / 清理

#
系列
要点
4
selftests/mm: make MM selftests more CI friendly
 [v1, 53p]
把 HugeTLB 环境准备从 run_vmtests.sh 下沉到 C helper,9+ 大型用例首次接入 kselftest TAP,run_vmtests.sh 净删 ~170 行,新增 hugepage_settings 库

最新文章

随机文章