目录
- SLUB 批量分配延迟 freelist 构建优化
- Per-NUMA-Node drop_caches 接口
- userfaultfd: UFFDIO_MOVE 写保护状态丢失修复
- vmstat_shepherd 双重调度 vmstat_update 修复
- userfaultfd: VMA 类型变更竞态修复
- GUP-fast 对 NULL-mapping order-0 folio 的回退修复
- userfaultfd: 允许注册低于 mmap_min_addr 的地址范围
- KHO: 修复 restore 路径缺少 metadata 初始化
1. SLUB 批量分配延迟 freelist 构建优化
系列:[PATCH v5] mm/slub: defer freelist construction until after bulk allocation from a new slab作者: hu.shengming版本: v5(1个patch)
背景
SLUB allocator 在分配新 slab 时,allocate_slab() 函数会立即构建完整的 freelist——遍历 slab 中的所有 object,对每个 object 调用 setup_object() 进行初始化,然后通过 set_freepointer() 将它们串联成单链表。这个 freelist 构建发生在 slab 页面分配之后、任何 object 被实际使用之前。
问题在于,在 bulk allocation 场景(如 kmem_cache_alloc_bulk() 和 sheaf refill 路径 refill_objects())中,调用者往往会立即消耗掉新 slab 上的大部分甚至全部 object。在极端情况下——请求数量恰好等于 slab->objects 时——所有 object 都被取走,之前构建的 freelist 完全被丢弃。代码中甚至有一条 TODO 注释指出了这个优化机会。此外,当 CONFIG_SLAB_FREELIST_RANDOM=y 时,freelist 构建还涉及 shuffle_freelist() 的随机化遍历,开销更大。
解决的问题
- 新 slab 分配时无条件构建完整 freelist,即使后续 bulk allocation 会立即消耗全部 object,导致 CPU 周期完全浪费
CONFIG_SLAB_FREELIST_RANDOM=y 和 =n 走不同的代码路径,增加维护复杂度setup_object() 未标记为 inline,可能在热路径上产生额外函数调用开销
如何做
核心思路是将 freelist 构建推迟到 object 分配完成之后,仅为剩余未分配的 object 构建 freelist。
1. 引入 slab_obj_iter 迭代器抽象
在 mm/slab.h 中新增 struct slab_obj_iter,统一 freelist random 和非 random 两种遍历方式:
structslab_obj_iter {
unsignedlong pos;
void *start;
#ifdef CONFIG_SLAB_FREELIST_RANDOM
unsignedlong freelist_count;
unsignedlong page_limit;
bool random;
#endif
};
init_slab_obj_iter() 初始化迭代器;next_slab_obj() 返回下一个 object 并调用 setup_object() 完成初始化——关键设计是 object 的初始化从 freelist 构建阶段延迟到了实际分配/迭代时。
2. allocate_slab() 不再构建 freelist
删除了 shuffle_freelist() 和线性遍历构建 freelist 的代码,allocate_slab() 现在只负责分配页面和初始化元数据。
3. 调用方按需构建 freelist
alloc_from_new_slab()(bulk allocation 路径)先通过迭代器直接分配 target_inuse 个 object,然后调用 build_slab_freelist() 仅为剩余 object 构建 freelist。当 count >= slab->objects 时,完全跳过 freelist 构建。锁的获取也被推迟到分配完成之后,减少了持锁时间。
4. 删除冗余代码
next_freelist_entry() 和 shuffle_freelist() 被完全删除,功能被 slab_obj_iter 迭代器吸收。setup_object() 被标记为 inline。
收益
作者使用 slub_bulk_bench 基准测试,在 QEMU KVM 环境(x86_64, 8 CPU, 1024M)下测量:
CONFIG_SLAB_FREELIST_RANDOM=n 时,性能提升 32%-71%:
CONFIG_SLAB_FREELIST_RANDOM=y 时,性能提升 52%-70%:
作者指出:"This benchmark is intended to isolate the cost removed by this change: each iteration allocates exactly slab->objects from a fresh slab." 现实负载的收益取决于 fresh-slab refill 路径的命中频率。由于 refill_objects() 也被 sheaf refill 使用,常规分配通过 sheaf refill 触及 fresh slab 时也能受益。
2. Per-NUMA-Node drop_caches 接口
系列:[PATCH RFC] fs: drop_caches: introduce per-node drop_caches interface作者: Kefeng Wang版本: RFC v1(1个patch)
背景
Linux 提供 /proc/sys/vm/drop_caches 全局接口释放 page cache 和 slab 对象。然而,在大型 NUMA 系统中,全局操作会波及所有节点,将其他节点上的热缓存也一并驱逐。典型场景如 NUMA 热插拔:执行 memory hot-remove 时需要清除目标节点上的 page cache,全局 drop_caches 会不必要地影响其他节点;而 page migration 对大量页面效率低,还存在访问故障内存的风险。作者指出:"during hot-remove, simply dropping pagecache is far more efficient than migrating large amounts of pages to other nodes, which also eliminating the risk of accessing potentially faulty memory."
解决的问题
- 全局
drop_caches 无法针对单个 NUMA 节点操作,导致不必要的缓存驱逐 - NUMA 热插拔场景缺少高效的 per-node cache 清除机制
如何做
在 sysfs 中为每个 NUMA 节点暴露 /sys/devices/system/node/nodeX/drop_caches 接口,允许管理员针对特定节点释放缓存。
1. sysfs 接口层(fs/drop_caches.c)
将原 drop_caches_sysctl_handler() 核心逻辑提取为 drop_caches_handler(int flags, int nid) 共享函数。nid == NUMA_NO_NODE 时行为与全局接口一致;nid >= 0 时仅操作指定节点。新增 has_caches(nid) 快捷检查,通过 node_page_state(NR_FILE_PAGES) 避免无缓存时的无谓遍历。
2. Page cache invalidation 层(mm/truncate.c, mm/filemap.c)
find_lock_entries() 重构为 __find_lock_entries(),新增 int nid 参数,遍历 xarray 时过滤:
if (nid >= 0 && folio_nid(folio) != nid)
goto put;
新增 invalidate_node_mapping_pages() 导出函数供 drop_pagecache_sb() 调用。
3. Slab 释放层(mm/vmscan.c)
drop_slab() 签名改为 void drop_slab(int nid),内部使用 nodemask_t 控制遍历范围,nid >= 0 时只遍历指定节点。
收益
精确性——只清除存在内存压力的节点缓存,避免影响其他节点热数据。效率——per-node 操作将扫描范围限定在单个节点,显著缩短操作时间。接口设计与全局 drop_caches 保持一致(相同输入值语义 1/2/3/4)。通过 NUMA_NO_NODE 约定和 inline wrapper 确保全局路径零开销。
3. userfaultfd: UFFDIO_MOVE 写保护状态丢失修复
系列:[PATCH] userfaultfd: preserve write protection across UFFDIO_MOVE作者: Gregory Price版本: v1(1个patch) | Fixes: adef440691ba | Cc: stable
背景
UFFDIO_MOVE 允许在同一进程地址空间内将物理页面从一个虚拟地址移动到另一个。userfaultfd 同时支持 write-protection (uffd-wp) 功能,允许用户态 fault handler 对特定页面设置写保护标记,广泛用于增量快照、实时迁移和脏页跟踪。用户期望通过 UFFDIO_MOVE 移动页面时写保护状态被完整保留。mremap 的 move_ptes() 通过 get_and_clear_ptes + set_ptes 天然保留写权限和 uffd-wp 状态,但 UFFDIO_MOVE 的实现并未做到。
解决的问题
move_present_ptes() 中无条件调用 pte_mkwrite(orig_dst_pte, dst_vma) 将目标 PTE 设为可写,完全丢弃源 PTE 上的写保护状态- 源 PTE 上的
uffd_wp 标记位未被传播到目标 PTE,导致丢失脏页跟踪能力
如何做
修改 mm/userfaultfd.c 中 move_present_ptes() 的 PTE 构建逻辑:
// 修改前:
orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
// 修改后:
if (pte_write(orig_src_pte))
orig_dst_pte = pte_mkwrite(orig_dst_pte, dst_vma);
if (pte_uffd_wp(orig_src_pte))
orig_dst_pte = pte_mkuffd_wp(orig_dst_pte);
只有当源 PTE 本身可写时才添加写权限,若源 PTE 带有 uffd_wp 标记则在目标 PTE 上恢复。修改仅涉及 4 行代码,与 mremap 的语义保持一致。
收益
修复了 UFFDIO_MOVE 操作正确保留 uffd-wp 写保护状态。对 QEMU postcopy 迁移、CRIU 快照等依赖 uffd-wp + UFFDIO_MOVE 组合的场景,数据一致性得到保证。标记 Cc: stable 确保回溯到所有受影响版本。
4. vmstat_shepherd 双重调度 vmstat_update 修复
系列:[PATCH v2] mm/vmstat: fix vmstat_shepherd double-scheduling vmstat_update作者: Breno Leitao版本: v2(1个patch) | Fixes 标签
背景
Linux 内核通过 per-CPU 的 vmstat_work(delayed_work)定期刷新 vmstat 差值计数器。vmstat_shepherd 全局巡检 worker 使用 delayed_work_pending() 判断 vmstat_update 是否已被调度。然而 delayed_work_pending() 仅检查 WORK_STRUCT_PENDING_BIT,该位在 worker 线程拾取 work 时就被清除。这意味着在 vmstat_update 正在执行期间,delayed_work_pending() 返回 false,造成 shepherd 误判。
解决的问题
- 当
vmstat_update 正在执行时,shepherd 误判其未调度,以 delay=0 再次调度,导致 worker 完毕后立即再次运行 - 多余的
vmstat_update 触发 free_pcppages_bulk(),需要获取 zone spinlock,在多 CPU 系统上显著加剧 zone lock 争用
如何做
将 vmstat_shepherd() 中的检查从 delayed_work_pending() 替换为 work_busy():
// 修改前:
if (!delayed_work_pending(dw) && need_update(cpu))
// 修改后:
if (!work_busy(&dw->work) && need_update(cpu))
work_busy() 返回 WORK_BUSY_PENDING | WORK_BUSY_RUNNING 位掩码,只有在 work 既不在队列中也没有正在运行时才为 true,正确跳过执行中的 CPU。
收益
作者在 72-CPU 系统上用 stress-ng 实测:
free_pcppages_bulk 争用次数减少约 55%free_pcppages_bulk 总等待时间减少约 57%free_pcppages_bulk 最大等待时间减少约 47%
已获得 Vlastimil Babka 的 Reviewed-by。
5. userfaultfd: VMA 类型变更竞态修复
系列:[PATCH v5] mm/userfaultfd: detect VMA type change after copy retry in mfill_copy_folio_retry()作者: David Carlier版本: v5(1个patch) | Fixes: 59da5c32ffa3
背景
userfaultfd 的 UFFDIO_COPY 在拷贝过程中,如果源用户地址页面未就绪,进入重试路径 mfill_copy_folio_retry()。重试路径必须先释放 mmap_lock,在无锁状态下执行 copy_from_user(),再重新获取锁。调用者 __mfill_atomic_pte() 在进入前已根据 VMA 类型选择了 vm_uffd_ops 操作集。
解决的问题
- 在释放 mmap_lock 窗口期间,另一线程可替换目标 VMA 类型(如从匿名映射变为 hugetlb),导致 ops 指针 stale
- 使用 stale ops 指针继续操作可导致页表损坏或内核 crash
如何做
在 mfill_copy_folio_retry() 中增加 ops 参数,重新获取锁后验证 VMA 类型一致性:
if (vma_uffd_ops(state->vma) != ops)
return -EAGAIN;
不一致时返回 -EAGAIN,触发上层完整重试。该 "detect and retry" 模式是内核处理 mmap_lock drop/reacquire 窗口的标准惯例。
收益
消除了一个可从用户态触发的竞态条件,防止潜在的内核崩溃或页表损坏。对 QEMU postcopy、CRIU 等多线程场景的健壮性增强。
6. hugetlb 早期启动崩溃修复
系列:[PATCH] mm/hugetlb: fix early boot crash on parameters without '=' separator作者: Thorsten Blum版本: v1(1个patch) | Fixes: 5b47c02967ab | Cc: stable
背景
Commit 5b47c02967ab 将 hugetlb 命令行参数从 __setup() 转换为 early_param()。early_param() 框架在参数缺少 = 时将 s 设为 NULL 传给回调,而旧的 __setup() 机制下回调不会收到 NULL。转换后的 hugetlb_add_param() 直接调用 strlen(s) 未处理 NULL。
解决的问题
- 内核命令行中误写
hugepages(缺少 =512)时,strlen(NULL) 导致早期启动 NULL pointer dereference 崩溃 - 早期启动阶段无异常处理能力,系统直接挂死,极难排查
如何做
在 hugetlb_add_param() 入口增加 NULL 检查:
if (!s)
return -EINVAL;
一处修复保护 hugepages、hugepagesz、default_hugepagesz 三个参数路径。
收益
消除了用户输入错误即可触发的启动崩溃。带 Cc: stable 回溯到所有受影响版本。
7. GUP-fast 对 NULL-mapping order-0 folio 的回退修复
系列:[PATCH] mm/gup: fix GUP-fast fallback for NULL-mapping order-0 folios作者: John Hubbard版本: v1(1个patch) | Fixes: f002882ca369
背景
Commit f002882ca369 合并 secretmem 检测和 file-backed folio pin 许可到 gup_fast_folio_allowed()。合并后代码在 folio->mapping == NULL 时无条件回退到 slow path。对 reject_file_backed 场景正确,但对仅需 check_secretmem 的场景过于保守。GPU/RDMA 驱动常通过 alloc_page() + vm_insert_page() 插入页面,这些页面 folio->mapping 天然为 NULL,而 secretmem folio 必有非空 mapping。
解决的问题
CONFIG_SECRETMEM=y 时,所有 order-0 且 folio->mapping == NULL 的页面被错误回退到 slow path- GPU/RDMA 驱动的高频 pin 场景产生性能回退
如何做
// 修改前:
if (!mapping)
returnfalse;
// 修改后:
if (!mapping)
return !reject_file_backed;
reject_file_backed == true 时仍回退(无法确认是否为截断的文件页);reject_file_backed == false 时允许继续 fast path(NULL mapping 证明不是 secretmem)。
收益
恢复了合并前的 GUP-fast 性能特性,NVIDIA 工程师完成测试验证(Tested-by: Sourab Gupta)。
8. userfaultfd: 允许注册低于 mmap_min_addr 的地址范围
系列:[PATCH v2] userfaultfd: allow registration of ranges below mmap_min_addr作者: Denis M. Karpov版本: v2(1个patch) | Fixes: 86039bd3b4e6
背景
validate_unaligned_range() 包含对 mmap_min_addr 的检查,但 UFFDIO_REGISTER 不创建映射,只在已存在的 VMA 上注册回调。VMA 已存在说明已通过 mmap() 的地址合法性检查,重复检查属于防御过度。
解决的问题
- 二进制编译器、仿真器等在已合法映射的低地址区域无法注册 userfaultfd 处理
如何做
从 validate_unaligned_range() 删除 mmap_min_addr 检查(2行):
- if (start < mmap_min_addr)
- return -EINVAL;
收益
解除对低地址合法映射使用 UFFD 的限制,mmap_min_addr 安全保护仍在 mmap() 层完整保留。
9. KHO: 修复 restore 路径缺少 metadata 初始化
系列:[PATCH] kho: call kho_kexec_metadata_init() for both boot paths作者: Breno Leitao版本: v1(1个patch) | Fixes 标签
背景
KHO(Kexec Handover)允许在 kexec 重启过程中传递内核状态。kho_init() 存在 restore 路径(有 FDT)和 fresh boot 路径(无 FDT)。由于 linux-next rebase 改变了控制流,kho_kexec_metadata_init() 被放在 if (fdt) { return 0; } 之后,导致 restore 路径永远不执行该函数。
解决的问题
- KHO restore 路径中 metadata 未初始化,无法再次执行 kexec handover,破坏链式活更新能力
如何做
将 kho_kexec_metadata_init(fdt) 调用从 if (fdt) 检查之后移到之前,确保两条路径都执行。
收益
恢复 KHO 链式活更新能力(A -> B -> C -> ...),对零停机更新场景至关重要。
总结
新机制 / 新接口
| | |
|---|
| per-node drop_caches | 新增 /sys/devices/system/node/nodeX/drop_caches,支持按 NUMA 节点定向释放缓存 |
性能优化
| | |
|---|
| SLUB defer freelist construction | 每对象分配开销降低 32%-71%(RANDOM=n)/ 52%-70%(RANDOM=y) |
Bug Fix
| | |
|---|
| userfaultfd UFFDIO_MOVE uffd-wp | 写保护状态丢失,影响实时迁移/快照 (Fixes: adef440691ba, Cc: stable) |
| vmstat_shepherd double-scheduling | zone lock 争用减少 55%,72-CPU 系统实测 |
| userfaultfd VMA type change | mmap_lock drop 窗口竞态导致潜在内核崩溃 (Fixes: 59da5c32ffa3) |
| hugetlb boot crash | 命令行缺 '=' 导致早期启动 NULL deref (Fixes: 5b47c02967ab, Cc: stable) |
| GUP-fast NULL-mapping | GPU/RDMA 驱动 pin 性能回退 (Fixes: f002882ca369) |
| userfaultfd mmap_min_addr | 低地址合法映射无法注册 UFFD (Fixes: 86039bd3b4e6) |
| KHO metadata init | |
内部优化 / 清理