目录
- MGLRU 回收循环与 dirty folio 处理优化
- 可执行内存的 large folio readahead 与地址对齐优化
- mm/mprotect 微优化 -- 缩小热路径生成代码
- 为 guest_memfd 添加 userfaultfd 支持
- vmpressure 在高阶回收时跳过 socket 压力通知
- selftests/mm: THP 不可用时跳过相关测试
- selftests/cgroup: zswap 测试增强与大页支持
- lkdtm: 新增 folio_lock 死锁测试场景
- mm/damon/stat: 修复 damon_call() 失败时内存泄漏
1. MGLRU 回收循环与 dirty folio 处理优化
系列:[PATCH v3 00/14] mm/mglru: improve reclaim loop and dirty folio handling作者: Kairui Song (Tencent)版本: v3(14个patch)
背景
MGLRU(Multi-Gen LRU)自引入以来,其回收循环(reclaim loop)的内部实现相当复杂:aging(代际老化)、scan number 计算和回收循环三者紧密耦合,dirty folio 的处理逻辑也与经典 LRU 差异很大。具体而言,MGLRU 采用了一种特殊的 dirty/writeback folio 处理方式——将这些 folio 提升到第二老的 generation 而非像经典 LRU 那样 reactivate 它们,同时 flusher 唤醒机制也不够及时(在整个回收循环结束后才检查,而非每个 batch 后检查)。此外,回收循环在触发 aging 后会立即中止扫描,浪费回收周期,并在并发回收场景下导致所有回收者可能同时失败。这些问题在生产环境中暴露出性能不佳和意外 OOM 的问题,作者指出 "some of the problems were found in our production environment"。
解决的问题
- 回收循环效率低:每次迭代重新计算 scan number,aging 触发后立即中止扫描,浪费回收周期
- Dirty folio 处理不当:将 dirty/writeback folio 移到下一代而非隔离后 reactivate,降低扫描效率
- 意外 OOM:在特定 file + anon 混合压力场景下,尽管仍有可回收的 file folio,MGLRU 仍然触发 OOM
- Writeback throttling 缺失:MGLRU 完全忽略了经典 LRU 中的 writeback throttling 逻辑
如何做
系列分为三个阶段:
回收循环重构(Patch 1-7): 提取 lruvec_evictable_size() 统一计算可回收 folio 数量。重构 try_to_shrink_lruvec() 的核心循环:将 scan budget 计算移到循环开始处一次性完成,通过 get_nr_to_scan() 计算后始终应用 sc->priority 进行右移。将 aging 判断从 scan number 计算中解耦。关键改进——aging 触发后不再中止扫描,而是继续回收;对于 global reclaim 仅在一个 batch 后中止以维持公平性,对于 cgroup reclaim 则完全不中止,因为 "for cgroup reclaim, fairness is handled by iterator, not rotation"。
Dirty folio 处理统一(Patch 8-11): 删除 sort_folio() 中对 dirty/writeback folio 的特殊处理(移到下一代),改为让这些 folio 走 shrink_folio_list() 的标准 reactivation 路径。将 flusher 唤醒逻辑从循环外部移入 evict_folios() 中每个 batch 之后,使响应更及时。
Writeback throttling 统一(Patch 12-14): 提取 handle_reclaim_writeback() 公共函数,将经典 LRU 和 MGLRU 的 dirty 统计和 throttling 逻辑统一。
收益
测试环境:48c96t NUMA 双节点 128G 内存,NVME 存储:
- MongoDB + YCSB workloadb(10G cgroup,无 swap):吞吐量从 62485 ops/sec 提升至 79760 ops/sec(**+27.6%),平均延迟从 500.97us 降至 391.25us(-21.9%),
workingset_refault_file 从 34522071 降至 19566366(-43.3%**) - 作者指出 "the test is done on NVME and the performance gap would be even larger for slow devices, such as HDD or network storage. We observed over 100% gain for some workloads with slow IO"
- Chrome & Node.js、MySQL sysbench、FIO randread、Build kernel 等场景均无回归
- OOM 修复:file+anon 混合压力测试中,修复前约 10-20 轮后 OOM,修复后完成全部 128 轮
2. 可执行内存的 large folio readahead 与地址对齐优化
系列:[PATCH v3 0/4] mm: improve large folio readahead and alignment for exec memory作者: Usama Arif版本: v3(4个patch)
背景
exec_folio_order() 在 v6.16 开发周期中被引入,允许架构请求一个首选的 folio order 用于可执行内存的 readahead,从而启用硬件 PTE 合并(如 arm64 contpte)和 PMD 映射。然而在实际部署中(尤其是 arm64 64K base page 配置下),多个因素阻碍了该机制的正常工作:mmap_miss 启发式计数器在 100 次 page fault 后静默禁用所有 readahead;arm64 的 exec_folio_order() 对 64K page 返回 order 0 无任何合并收益;PIE 二进制的 ASLR 以 PAGE_SIZE 粒度随机化加载地址,导致 contpte 无法对齐。
解决的问题
mmap_miss 计数器误杀 exec readahead:在 arm64 64K page 下计数器单调递增,100 次 fault 后永久禁用exec_folio_order() 在 64K page 下返回 order 0:contpte 需要 2M(32 个 PTE),退化为单页- 虚拟地址未对齐:ASLR 随机化导致绝大多数情况下无法满足 contpte 对齐要求
- 共享库无法享受对齐:
thp_get_unmapped_area() 仅尝试 PMD_SIZE 对齐,对共享库过大
如何做
Patch 1 — 在 do_sync_mmap_readahead() 中将 VM_EXEC 加入与 VM_SEQ_READ 相同的豁免逻辑,使 exec readahead 不受 mmap_miss 计数器限制。
Patch 2 — 修改 arm64 exec_folio_order() 64K page 下返回 order 5(2M contpte)。在 do_sync_mmap_readahead() 中实现分层策略:VMA 足够大则请求 HPAGE_PMD_ORDER,否则 fallback 到 exec_folio_order()。使用 gfp &= ~__GFP_RECLAIM 使大 folio 分配为机会性的。
Patch 3 — 在 fs/binfmt_elf.c 中新增 folio_alignment() 函数,为每个 PT_LOAD 段计算最大 folio 对齐,确保 ASLR 后虚拟地址满足对齐要求。
Patch 4 — 在 thp_get_unmapped_area_vmflags() 中添加 exec_folio_order() 级别的对齐 fallback,使共享库也能享受 contpte 映射。
收益
在 Neoverse V2 (Grace),arm64 64K base page,512MB 可执行文件:
3. mm/mprotect 微优化 -- 缩小热路径生成代码
系列:[PATCH v3 0/2] mm/mprotect: micro-optimization work作者: Pedro Falcato (SUSE)版本: v3(2个patch)
背景
mprotect() 系统调用的核心路径 change_protection() -> change_pte_range() 在一个极其紧密的循环中遍历页表条目。作者指出 "even small inefficiencies are incredibly evident when spun hundreds, thousands or hundreds of thousands of times"。当前函数体非常庞大,所有逻辑内联在同一函数中,导致 icache 占用大、分支预测效率低。同时 order-0(小页)这一最常见场景没有被特殊优化。
解决的问题
change_pte_range() 函数体过于庞大,softleaf 处理代码与 present PTE 处理混杂- order-0 folio 最常见路径没有被特殊优化,需执行与 large folio batching 相同的复杂逻辑
- 关键辅助函数缺乏
__always_inline 标注,编译器无法在 nr_ptes == 1 时消除死代码
如何做
Patch 1:将 change_pte_range() 中处理 non-present PTE(softleaf)的约 60 行代码提取为独立函数 change_softleaf_pte()。
Patch 2:将 present PTE 的权限修改逻辑提取为 change_present_ptes() 并标注 __always_inline。在调用点对 nr_ptes == 1(小页)进行特判,编译器通过常量传播消除 batching 循环中与 nr_ptes > 1 相关的分支,生成精简的直线代码路径。"compiler constant propagation plus copious amounts of __always_inline does wonders"。
收益
x86 平台 Google Benchmark 微基准测试:
- Baseline: 85967 ns → After patchset: 70684 ns,提升约 18%
- Luke Yang (Red Hat) 独立验证
libmicro/mprot_tw4m:"improvements ranging from a minimum of 5% to a maximum of 55%, with most improvements showing around a 25% speed up"
4. 为 guest_memfd 添加 userfaultfd 支持
系列:[PATCH v4 00/15] mm, kvm: allow uffd support in guest_memfd作者: Mike Rapoport (Microsoft),协作者 Nikita Kalyazin (Amazon)、Peter Xu版本: v4(15个patch)
背景
userfaultfd (uffd) 是虚拟机 post-copy live migration 的关键基础设施。然而当前 uffd 实现与 anonymous memory、shmem、hugetlb 三种内存类型深度耦合,代码中充斥条件判断,新的内存类型(如 KVM 的 guest_memfd)无法接入。guest_memfd 是 KVM 为 Confidential Computing(机密计算,如 AMD SEV、Intel TDX)引入的专用内存后端,为支持使用 guest_memfd 的 VM 进行 post-copy live migration,必须让其与 uffd 配合工作。
解决的问题
- uffd 的 UFFDIO_COPY/CONTINUE/ZEROPAGE 等操作与 anonymous/shmem 硬编码耦合,无法扩展
mfill_atomic() 主循环参数传递分散,retry 逻辑与主循环交织- guest_memfd 的
->fault() 路径中缺少 uffd 集成点
如何做
15 个 patch 分三阶段完成:
阶段一(Patch 1-6):重构 uffd 核心代码。 引入 struct mfill_state 聚合所有状态,提取 mfill_copy_folio_locked()、mfill_establish_pmd()、mfill_get_vma()/mfill_put_vma() 等 helper,将 retry 逻辑移入 mfill_copy_folio_retry()。
阶段二(Patch 7-11):引入 vm_uffd_ops 抽象层。 核心数据结构 struct vm_uffd_ops 挂载于 vm_operations_struct->uffd_ops,包含 can_userfault、get_folio_noalloc、alloc_folio、filemap_add、filemap_remove 五个回调。删除旧的 shmem_mfill_atomic_pte(),shmem 通过 shmem_uffd_ops 回调实现。
阶段三(Patch 12-15):guest_memfd 集成与测试。 在 __do_fault() 中添加 __do_userfault() hook。在 virt/kvm/guest_memfd.c 中实现 kvm_gmem_uffd_ops。添加 KVM selftests 覆盖 MINOR 和 MISSING fault 场景。
收益
架构层面的核心收益:通过 vm_uffd_ops 回调抽象,任何内存后端都可接入 uffd;guest_memfd 获得完整的 UFFDIO_COPY 和 UFFDIO_CONTINUE 支持,使 Confidential VM 能够使用 post-copy live migration。代码变更 +860/-448 行。
5. vmpressure 在高阶回收时跳过 socket 压力通知
系列:[PATCH v2] mm/vmpressure: skip socket pressure for costly order reclaim作者: JP Kobryn (Meta)版本: v2(1个patch)
背景
vmpressure 机制通过监控 reclaim 的 scan:reclaim 比率评估内存压力,超阈值时通知 TCP/IP 栈进行 socket buffer 节流。当 kswapd 因内存碎片化进行高阶(high order)回收时,会扫描大量页面但回收很少——系统并不缺少空闲内存,只是缺少连续大块。但较差的比率会误导 vmpressure,不必要地限制 TCP 吞吐量。
解决的问题
- kswapd 为高阶分配(如 huge page 碎片整理)扫描大量页面,错误触发 socket pressure
- vmpressure 完全不感知回收的 order
如何做
为 vmpressure() 添加 int order 参数,在所有 4 个调用点传递 sc->order。在 socket pressure 判断处添加过滤:
if (level > VMPRESSURE_LOW && order <= PAGE_ALLOC_COSTLY_ORDER) {
/* Let the socket buffer allocator know ... */
作者注释:"Don't throttle sockets because somebody is attempting something crazy like an order-7 and predictably struggling." vmpressure_prio() 紧急路径始终传入 order = 0,确保极端低优先级回收不被误屏蔽。
收益
作者未提供性能数据。预期收益:消除高阶回收场景下对 TCP socket buffer 的误限制,在碎片化但总体空闲内存充足时保持网络吞吐量稳定。改动仅 +21/-11 行。
6. HMM 框架增加 userfaultfd 支持
系列:[RFC PATCH] mm/hmm: Add userfaultfd support to fault handling作者: Stanislav Kinsburskii (Microsoft)版本: RFC v1(1个patch)
背景
HMM(Heterogeneous Memory Management)框架用于让设备驱动(如 GPU)通过页表遍历获取用户空间内存物理地址映射。当 HMM 遇到缺页时调用 handle_mm_fault(),但当前完全不支持 userfaultfd 注册的 VMA。handle_mm_fault() 在 uffd VMA 上返回 VM_FAULT_RETRY 或 VM_FAULT_COMPLETED 并释放 mmap_read_lock,原有 HMM 代码未处理这些返回值。
解决的问题
- HMM 无法处理 userfaultfd 注册的 VMA,设备驱动访问 uffd 管理的内存时行为异常
mmap_lock 状态管理不正确,可能在锁已释放状态下继续操作
如何做
新增 hmm_handle_mm_fault() 函数,对 userfaultfd_armed(vma) 的 VMA 添加 FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_USER | FAULT_FLAG_KILLABLE 标志。处理 VM_FAULT_COMPLETED 和 VM_FAULT_RETRY 时重新获取 mmap_read_lock,统一返回 -EBUSY 让调用者决定重试。作者坦承 "This approach is inefficient for userfaultfd-backed VMAs, as HMM can only populate one page at a time, but keeps the framework simple"。
收益
核心价值在功能正确性:使 HMM 框架(及依赖它的 GPU 驱动如 NVIDIA UVM、AMD SVM 等)能够正确处理 userfaultfd 管理的内存区域,对 VM postcopy live migration 场景下 GPU 直通设备的内存访问至关重要。
7. selftests/mm: THP 不可用时跳过相关测试
系列:[PATCH v8 0/6] selftests/mm: skip several tests when thp is not available作者: Chunyu Hu (Red Hat)版本: v8(6个patch)
背景
mm selftests 中多个测试隐式依赖透明大页(THP)功能,在禁用 THP 的内核(如 realtime 内核)上运行会产生 false negative。例如 guard-regions 的 collapse 子测试报告 "FAILED: 87 / 90 tests passed",soft-dirty 的 test_hugepage 跳过时 TAP plan 计数不匹配被误判为 FAIL。
解决的问题
guard-regions、soft-dirty、split_huge_page_test、transhuge-stress 在 THP 不可用时误报失败soft-dirty 的 SKIP 计数不匹配导致 TAP harness 误判write_file() helper 在两处有不同实现,缺少健壮性检查
如何做
在各测试入口添加 thp_available() / thp_is_enabled() 检查,不可用时正确报告 SKIP。修复 soft-dirty 的 SKIP 计数。将 write_file() 统一到 vm_util.c,增强错误处理(校验 buflen、保存 errno、区分写入错误和截断)。
收益
THP 禁用环境下 guard-regions 从 "FAILED: 87/90" 变为 "PASSED: 90/90",soft-dirty 从 FAIL 变为 PASS。已获 Lorenzo Stoakes、David Hildenbrand、Zi Yan、Mike Rapoport 四位 reviewer 的 Reviewed-by/Acked-by。
8. selftests/cgroup: zswap 测试增强与大页支持
系列:[PATCH v6 0/8] selftests/cgroup: improve zswap tests robustness and support large page sizes作者: Li Wang (Red Hat),部分贡献 Waiman Long (Red Hat)版本: v6(8个patch)
背景
cgroup 的 zswap selftests 在设计时假设 4K page size,大量使用硬编码的 4095/4096。在 arm64 和 ppc64le 等 64K page size 架构上,sub-page 粒度访问、不准确的 page 计数、不足的内存压力导致测试失败。
解决的问题
test_swapin_nozswap 硬编码 memory.max=8M 导致 OOM kill- 所有 page stride 硬编码为 4095/4096,64K page size 下行为异常
attempt_writeback() 数据量不足无法触发 writeback
如何做
新增 check_zswap_enabled() 检查全局开关。将所有硬编码尺寸替换为 page_size * N 动态计算。重写 test_no_invasive_cgroup_shrink 使用 getrandom() 生成难压缩数据触发 writeback。新增 wait_for_writeback() 轮询函数(最多 5 秒,每 100ms 检查)解决异步竞态。将 cgroup_util.h 中误导性的 PAGE_SIZE 宏重命名为 BUF_SIZE。
收益
"Test all passed on: x86_64(4k), aarch64(4K, 64K), ppc64le(64K)"。已获 Yosry Ahmed 和 Nhat Pham 的 Acked-by/Reviewed-by。
9. lkdtm: 新增 folio_lock 死锁测试场景
系列:[PATCH] lkdtm: Add folio_lock deadlock scenarios作者: Yunseong Kim版本: v1(1个patch)
背景
folio_lock() 基于 wait-on-bit 机制实现,不被 lockdep 追踪,涉及 folio lock 的死锁难以检测。LKDTM 目前缺少此类测试用例。社区正在开发的 DEPT (Dependency Tracker) 机制能检测基于 wait/event 的依赖关系死锁,本 patch 为其提供可复现的验证用例。
解决的问题
- LKDTM 缺少 folio_lock 死锁测试场景
- 开发者缺少可靠手段验证 hung task detector 和 DEPT 的检测能力
如何做
新增 drivers/misc/lkdtm/deadlock.c(304 行),实现四种场景:
FOLIO_LOCK_AA — 自死锁:同一上下文对同一 folio 两次 folio_lock()FOLIO_LOCK_ABBA — 经典 ABBA:两个 kthread 以相反顺序获取两个 folio lockFOLIO_MUTEX_LOCK_ABBA — 跨锁类型 ABBA:folio_lock 与 mutex 交叉获取FOLIO_DEFERRED_EVENT_ABBA — 延迟死锁:通过 workqueue 间接形成的循环依赖
收益
为内核死锁检测机制提供标准化验证手段。DEPT 能在死锁实际发生前通过依赖分析报告 ABBA 场景,展示了其对 wait-on-bit 类锁的检测能力。
10. mm/damon/stat: 修复 damon_call() 失败时内存泄漏
系列:[PATCH v2] mm/damon/stat: deallocate damon_call() failure leaking damon_ctx作者: SeongJae Park(DAMON maintainer)版本: v2(1个patch,重要 Bug Fix)
背景
damon_stat_start() 通过 damon_stat_build_ctx() 分配 damon_ctx,然后调用 damon_call() 注册回调。当 damon_call() 失败时,已分配的 damon_ctx 既没有释放也没有清空,用户再次触发 damon_stat_start() 时直接覆盖指针导致永久泄漏。Bug 由 commit 405f61996d9d 引入。
解决的问题
damon_call() 失败时 damon_ctx 内存泄漏- 简单地立即释放会导致 use-after-free(kdamond 可能仍在访问)
如何做
v2 采用延迟释放策略:在 damon_stat_start() 开头检查残留的 damon_stat_context,若 kdamond 仍在运行返回 -EAGAIN,若已终止则安全 damon_destroy_ctx()。同时在 damon_stat_stop() 中置 NULL。
if (damon_stat_context) {
if (damon_is_running(damon_stat_context))
return -EAGAIN;
damon_destroy_ctx(damon_stat_context);
}
收益
标记 Fixes 和 Cc: <stable@vger.kernel.org> # 6.17.x,同时修复内存泄漏和潜在 use-after-free。
总结
新机制 / 新接口
| | |
|---|
| guest_memfd uffd 支持 | 引入 vm_uffd_ops 回调抽象,使 guest_memfd 支持 post-copy live migration |
| HMM uffd 支持 | HMM 框架支持 userfaultfd VMA,使 GPU 驱动正确处理 uffd 管理的内存 |
| lkdtm folio_lock 死锁测试 | 4 种 folio_lock 死锁场景,验证 hung task detector 和 DEPT |
性能优化
| | |
|---|
| MGLRU 回收循环优化 | MongoDB +27.6% 吞吐量,延迟 -21.9%,refault -43.3% |
| exec 内存 large folio 对齐 | arm64 cold fault 50% faster,random 23% faster |
| mprotect 微优化 | mprotect 基准 +18%,外部验证 5%-55% |
| vmpressure order 感知 | 消除高阶回收误触发 socket pressure |
Bug Fix
| | |
|---|
| damon_stat 内存泄漏 | Fixes: 405f61996d9d,Cc: stable 6.17.x,修复泄漏 + use-after-free |
内部优化 / 清理
| | |
|---|
| selftests/mm THP 跳过 | 4 个测试在 THP 禁用时正确 SKIP,统一 write_file() |
| selftests/cgroup zswap 增强 | 全面支持 64K page size,修复异步竞态 |