系列:[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 特定页表同步等)。
pgmap/zone/migratetype 相关的隐性 bug:sparse_init 阶段 DAX vmemmap 计数不平衡、ZONE_DEVICE 复合页 pageblock migratetype 未初始化、跨 zone gigantic 大页 bootmem 处理错误等。struct page。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 初始化,显著缩短启动时间。struct page 内存。SPARSEMEM_VMEMMAP_PREINIT 的效果,无需各自适配。系列:[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 真的处在压力中。
rcv_ssthresh 最小值,严重劣化网络吞吐。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 新增):
rcv_ssthresh=19712。vmpressure() 调用源自 order-7 kswapd,这部分调用将不再误报 socket 压力。系列:[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,热路径上也有性能损失。
CONFIG_SLAB_FREELIST_RANDOM=y 的额外开销在 bulk 场景被放大。setup_object() 未被稳定 inline 带来额外函数调用成本。核心是引入一个 struct slab_obj_iter 迭代器(定义在 mm/slab.h),用统一的 next_slab_obj() 既能按线性顺序也能按预生成随机序列返回下一个空闲对象,且把 RANDOM 相关状态(freelist_count、page_limit、random 标志、随机 pos 初值)全部内聚在 #ifdef 里。然后拆分 slab 分配两步:
allocate_slab() 只分配并初始化 slab 元数据,把 slab->freelist = NULL,不再调用 shuffle_freelist() 或线性 freelist 构建循环;build_slab_freelist(),只对 slab->objects - slab->inuse 个剩余对象构建 freelist;new_slab() 保留旧行为——先 allocate_slab(),再 init_slab_obj_iter() + build_slab_freelist(),对非 bulk 调用完全透明;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%,例如
**CONFIG_SLAB_FREELIST_RANDOM=y**:per-object 时间下降 59%–71%,例如
系列:[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。
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 的循环;系列分五段: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)。
系列:[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)。
系列:[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 的目标分布。
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 * 10000ineligible_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 达成目标。
| Generalize vmemmap optimization for DAX and HugeTLB | SPARSEMEM_VMEMMAP_OPTIMIZATION Kconfig,共享 tail 页 remap 为只读 | |
| DAMON node_eligible/ineligible_mem_bp goal metrics |
| vmpressure: skip socket pressure for costly order reclaim | rcv_ssthresh 的比例从 19% (723/3843) 降至 0/3470;94% 的 vmpressure 调用源自 order-7 kswapd | |
| slub: defer freelist construction until after bulk alloc | slub_bulk_bench |
| damon/core: avoid time-quota permanently disabling scheme | esz < min_region_sz 自锁死循环;Fixes: 1cd243030059,Cc: stable 5.16+ |
| selftests/mm: make MM selftests more CI friendly | run_vmtests.sh 下沉到 C helper,9+ 大型用例首次接入 kselftest TAP,run_vmtests.sh 净删 ~170 行,新增 hugepage_settings 库 |