2026-06-24 内核社区日报

今天最值得关注的是 lru_add_drain 优化系列 v2:Barry Song 在 anon folio 复用路径中彻底消除冗余的 LRU drain,并在 do_swap_page 中用更精确的 refcount 和 swapcache 释放代替无条件 drain,性能提升显著。同时,device-private PMD 处理方案迎来 v4 hotfix,Wei Yang 针对 arm64 下 PMD 检查的 bug 提交了完整修复,社区活跃推动下该 fix 趋于成熟。

🧠 重点 · 内存

1. anon folio 复用路径大幅削减冗余 lru_add_drain,will-it-scale 预期收益显著

发生了什么: Barry Song (Xiaomi) 发布了 v2 系列(共 4 个 patch),核心改动有两部分:

  • (1)wp_can_reuse_anon_folio(): 原来每次 write-fault 尝试复用 anon folio 时都会无条件调用 lru_add_drain() 刷新当前 CPU 的 LRU 缓存,以确保 folio 不在 per-CPU 缓存中、refcount 是真实值。v2 改为:先假设 !LRU 的 anon folio 在 lru_cache 中,直接用 refcount 判断,只有在 refcount 显示需要 drain 时才调用一次,避免了绝大多数不必要的 LRU 锁竞争。
  • (2)do_swap_page(): 同样存在大量冗余的 lru_add_drain(),特别是对同步 I/O 设备(如 zram)。patch 3 完全移除了 do_swap_page 中的 lru_add_drain,转而通过 patch 4 的新机制:在 write swap-in 时,如果 folio 不在 LRU 上(即还在 per-CPU LRU 缓存中),尝试直接释放其 swapcache entry 而非先 drain 再释放,从而避免 LRU 锁的全局竞态。

系列的测试程序显示,在持续 swap-in 场景下,原先 100% 的 page fault 都会触发一次 drain,优化后几乎为零。

为什么重要: lru_add_drain 需要获取 LRU 锁并唤醒远程 CPU,是 page fault 热路径中的高开销操作。该系列在 wp_can_reuse_anon_folio(匿名页写时复用)和 do_swap_page(缺页换入)两个核心路径中消除了几乎全部不必要的 drain,显著降低 LRU 锁争用,预期对 will-it-scale 等多线程基准测试有可观的吞吐量提升(v1 报告提升 37.6%,v2 进一步优化)。

来源

2. device-private PMD 处理修复(v4):正确识别并处理 PMD 级设备私有映射

发生了什么: 自 commit 65edfda6f3f2(引入 device-private 支持)以来,内核的 rmap 和 page_vma_mapped_walk 需要能正确处理以 PMD 形式存在的设备私有映射。但现有代码存在两个问题:

  • (1)page_vma_mapped_walk() 中对 device-private PMD 的检测不完善check_pmd() 判断 PMD 是否为 device-private 时,未经 proper revalidate,可能误把非 device-private 的 PMD 判断为 device-private,导致后续操作(如 migration)访问错误的 PTE 或 PMD 内容。
  • (2)HPAGE_PMD_NR 宏在 check_pmd() 中的引用缺少 CONFIG_TRANSPARENT_HUGEPAGE 保护:当内核未启用 THP 时,HPAGE_PMD_NR 未定义,导致 parisc 等架构编译失败。

Wei Yang 提交的 v4 hotfix 系列(含两个 patch)修正了这些问题:

  • page_vma_mapped_walk() 中增加对 PMD 内容的 revalidate 检查,确认 PMD 确实是设备私有映射后才返回,否则退化为逐 PTE 遍历。
  • CONFIG_TRANSPARENT_HUGEPAGE 守卫 check_pmd() 函数及其中对 HPAGE_PMD_NR 的引用,避免未启用 THP 时的编译错误。

该系列已获得 David Hildenbrand 和 Lorenzo Stoakes 的 Ack/Reviewed-by,并进入 mm-hotfixes 队列。

为什么重要: 设备私有映射(device-private)是 GPU/加速器内存的直接映射形式,通常以 huge page(2M PMD)建立。若内核在 rmap 遍历中不能正确识别和处理 device-private PMD,将导致内存迁移(migrate_device)、gup 等操作访问损坏的页表数据,造成数据损坏或内核崩溃。此次修复保证了该机制的健壮性,对 CXL/NPU 等设备场景至关重要。

来源
来源

3. memcg stock 下放至 page_counter:实现按 page_counter 粒度的批量缓存

发生了什么: Joshua Hahn 发布 v4 系列,将 memcg 的 stock(per-CPU 批量缓存)机制从 mem_cgroup 结构体下移至 page_counter 层面。

现有 mem_cgroup 的 stock 机制通过 struct memcg_stock_pcp 管理:每当 try_charge() 需要分配页或 obj 时,优先从本 CPU 的 stock 中取用,避免直接访问全局资源限制(如 page_counter->limitmemcg->kmem.usage)。但 stock 以 memcg 为粒度存储,当系统存在大量 memcg(如容器场景)时,stock 数组占用过大,且跨 memcg 的批量操作(如 drain_all_stock())需要遍历所有 stock。

v4 系列将 stock 移至 page_counter 结构体内部(每个 page_counter 拥有自己的 per-CPU stock),核心改动:

  • struct page_counter 新增 pcp_stock 字段,内含与之前 memcg_stock_pcp 类似的批量计数和 drain_work 信息。
  • try_charge() 流程简化:评估 page_counter 的 stock 是否足够,若不足再检查 page_counter->limit 并可能触发 reclaim。
  • drain_all_stock() 改为向所有 CPU 广播,让每个 CPU 根据当前所在 memcg 的 stock 自行 drain,避免全局遍历。

系列共 5 个 patch:先引入 page_counter_stock 结构,然后扁平化 try_charge_memcg 控制流,再将 memcg 核心的 try_charge() 切换到新的 stock,最后处理用户态 memory.max 等场景的兼容。

为什么重要: 该重构消除了 stock 与 memcg 的强绑定,使得任何使用 page_counter 的子系统(如 memory.max、kmem、compressed & kernel memory by cgroup v2)都能独立获得 per-CPU 批量缓存,减少全局锁争用。特别是在高密度容器或 per-memcg 精细化限流的场景下,drain_all_stock() 的 O(N) 遍历开销变为 O(NCPU) 广播,有明显收益。社区已有多位 reviewer 表示兴趣。

来源

4. zswap 绕过 swap readahead:避免同步设备无效读预取

发生了什么: Alexandre Ghiti 提交 RFC patch,在 zswap 设备上禁用 swap readahead
当前内核的 swap-in 路径中,若判断设备为 SWP_SYNCHRONOUS_IO(如 zram 等同步 I/O 设备),会跳过 swapcache,直接从设备读取页面。但 zswap 虽使用 SWP_SYNCHRONOUS_IO_DEV 类型,却无法直接读取后续页面,因为 zswap 是按 entry 独立压缩存储的,没有物理块概念。当 filemap 进行 swap readahead(预读多个连续的 swap entry)时,zswap 会为预读的每个 slot 执行一次 zswap_load(),而大多数预读的页面可能从未被实际访问,造成不必要的 CPU 和内存开销。

patch 的做法是在 swap_swap_info 初始化时,对 zswap 后端设备额外标记 SWP_DISCARDABLE 标志(zswap 的页面在写回时由 zswap 自身管理,swap area 可忽略),并在 swap readahead 路径中通过该标志跳过预读。该思路早前已有人提出,但当时被拒绝,如今因 zswap 在 arm64 上的广泛使用而重新提上议程。

为什么重要: swap readahead 对于传统的硬盘/闪存设备可以有效减少 I/O 次数,但对于 zswap(内存压缩)而言弊大于利——它只是浪费 CPU 压缩和解压循环。禁用后可减少 30% 以上的 swap-in 延迟(取决于压缩比和 readahead 窗口),同时降低 CPU 占用。该 patch 是 RFC,但已引起多位 MM maintainer 的注意,认为方向正确。

来源

5. DAMON 硬件采样访问报告(RFC):PMU-agnostic 的访问模式采集

发生了什么: Ravi Jonnalagadda 提交 RFC 系列(6 个 patch),为 DAMON 引入 vendor 和 PMU 无关的硬件采样访问报告
当前 DAMON 基于软件的访问检测(如 pte_young),需要在 page fault 或 rmap 遍历中清除、检查页表 accessed bit,存在额外开销和延迟(特别是对匿名页/共享页)。该系列在 DAMON 之上增加一层抽象:允许用 PMU(Performance Monitoring Unit)采样事件来监测页的访问状态。核心机制:

  • 新增 struct damon_hw_sample_ops 定义 PMU 无关的采样回调:start_sampling()stop_sampling()get_next_sampled_page() 等。
  • 实例化为 arm64 SPE(Statistical Profiling Extensions)后端:在 arm64 上利用 SPE 的采样周期事件,在 TLB miss/页表查找时报告被访问页的物理地址,实现低开销、高精度的访问热图。
  • 与现有 DAMON 调度器集成:DAMON 的 region of interest 仍以虚拟地址区间定义,但访问检查改为调用 get_next_sampled_page() 从 PMU 采样队列获取。

系列目前为 RFC,SeongJae Park 已给出初步 review 意见,指出需要明确 PMU 采样与 DAMON 现有 access_freq 机制的兼容性。

为什么重要: 硬件采样可以以极低的开销覆盖传统 DAMON 难以触及的场景:例如大内存(几百 GB)的访问模式,或无法轻易修改页表的虚拟化/加密内存。该方向若落地,DAMON 将获得类似硬件 PTE 遍历的性能,从而推动其在云数据中心和企业级服务器中的广泛部署。

来源

6. kexec 保留 HWPoison 页列表(RFC):跨 kexec 传递坏页信息

发生了什么: Pratyush Yadav 提交 RFC,利用 KHO(Kexec-handover)机制在 kexec 重启时将硬件中毒页(hard-offlined pages)列表传递给新内核。当前内核在检测到可修复或不可修复的硬件错误(如 uncorrectable ECC)时,会通过 HWPoison 机制将故障页标记为 bad,并将其放入 hard_offline_page_list。但一旦 kexec 重启,新内核完全不认识这些坏页,可能再次分配给用户,导致数据损坏。

RFC 方案:

  • 在 HM(硬件监控)需要 hard-offline 一个页时,将其 PFN 加入一个 KHO-可传递的链表(使用 kho_register_chunkkho_restore_chunk 接口)。
  • kexec 时将链表序列化到 cmdline 或预留的内存区域(KHO chunk)。
  • 新内核(启动时)从 KHO chunk 读取中毒页列表,在 buddy system 初始化前将对应 PFN 标记为 PageReserved 并移除出伙伴分配器,或统一加入 hwpoisoned_pfn_list

讨论中提到:该机制还需要考虑 cold reboot(冷重启)的情况——如果硬件不保存坏页记录,则仍可能重新分配;需要用户态或 firmware 配合持久化(如 populating ACPI HEST)。

为什么重要: 在数据中心、HPC 和移动设备中,硬件内存错误是常见故障。通过 kexec 进行热升级或故障切换是常见的运维手段。若不能保留坏页信息,新内核可能很快跌入重复的错误处理循环,甚至直接分配坏页导致系统崩溃。该 RFC 提供了一个清晰可行的技术路线。

来源

🔧 其它子系统

eBPF / BPF

  • 全局 percpu 数据支持(v7):Leon Hwang 的“BPF 全局 percpu 变量”系列收到 Andrii Nakryiko 的详细 review,集中在 libbpf 探测、bpftool 骨架生成等方面。该机制允许 BPF 程序访问内核或用户态的 per-CPU 全局变量,对高性能 trace/计数器很有用。
  • bpf_tcp_ops 作为 struct_ops 加入:系列 v2 为 TCP 拥塞控制/头部选项操作提供 BPF struct_ops 接口,但 AI review bot 指出其 allow sleepable 可能会导致 panic,社区正重新评估安全性。
  • resolve_btfids selftest 现代化:Ihor Solodrai 提交 v2,使用最新 ASSERT_* 宏并新增 kfunc set 测试,修复 PIE 构建下的 BTF ID 读取问题。

文件系统 / VFS

  • fscrypt:folio 化清理:引入 fscrypt_finalize_bounce_folio() 作为 bounce page 的 folio 等效 API,继续从 pagefolio 的迁移。
  • xarray 节点泄漏修复:Matthew Wilcox 提交 patch,在 clear_inode() 中释放 inode 时归还所有多余的 xarray node,避免在内存回收路径中残留未释放的 xarray node 导致 OOM。

内存 / 杂项

  • folio_batch_count() data race 修复:Xuewen Wang 报告 KCSAN 检测到 __lru_add_drain_allfolio_batch_add_and_move 之间的 data race,通过 smp_load_acquire 保证顺序一致性。
  • shrinker rcu_dereference NULL 检查:Qingshuang Fu 为 set_shrinker_bit() 等函数增加 rcu_dereference 后对 shrinker 指针的 NULL 检查,修复可能的 NULL 解引用。
  • slub NUMA policy 计算去重:Hao Li 在 v3 中统一 alloc_from_pcs()__slab_alloc_node() 的 NUMA policy 获取逻辑,减少重复计算和函数调用。

👀 值得追的讨论 / Patch

  • folio refcount scalability 优化系列进展:Ilya Gladyshev 的 v4 发布后,社区讨论聚焦于 speculative get 与 folio_put 之间的 ABA race 可能性。经 Linus Torvalds 与 David Hildenbrand 澄清,该 race 并非此系列特有,而是已存在的普遍问题,folio_put() 始终能正确处理非 folio 对象。Ilya 计划基于 AI review 的 cosmetic fixes 发布 v5。来源
  • zswap per-cgroup 主动写回:昨天已报道的系列今天没有新进展,但讨论仍在继续。该设计涉及在 zswap 中引入基于 memcg 的 LRU 和写回策略,结构复杂。来源
  • compaction 跳过 mlocked pages 的 RFC:Vlastimil Babka 建议在 compact_unevictable_allowed=0 时,compaction 的 isolat 阶段应跳过 mlocked 页面,作者正在调整实现。来源
  • DAMON probe 文件支持:SeongJae Park 在 RFC 11-patch 系列中增加 /sys/kernel/debug/damon/probe 文件,允许用户进程注入自定义访问事件到 DAMON 模拟器中,用于开发和基准测试。来源

⚡ 一句话速览

  • MGLRU stale batch 修复 v2:Qi Zheng 提交 v2,通过引入 lrugen->reparented 字段记录 reparent 后的新 owner,使 reset_batch_size() 能将页面遍历中累积的 delta 计入正确的 lruvec,避免并发 memcg reparenting 导致的警告;讨论中另提出通过检查 CSS_DYING 向上查找非 dying 父 memcg 的替代方案。来源
  • memcg proactive reclaim 提前退出 dying cgroup:Jiayuan Chen 的 patch 使 memory.reclaim 在目标 cgroup 已启动 dying 时立即返回 0,避免无谓的扫描和统计操作。来源
  • zs_free 路径移除 pool->lock 补丁获 Reviewed-by:Wenchao Hao 修改 zs_free() 使其在 64-bit 系统上通过 class_idx 定位 size_class,无需 pool->lock,消除多核间 rwlock 缓存行 bouncing。Barry Song 给出 Reviewed-by,并指出原函数名 obj_handle_class_lock() 有误导性;Wenchao Hao 回复建议改名 obj_to_class_and_lock()来源
  • /proc/buddyinfo 读取时跳过 zone lock:Imran Khan 的 patch 将 frag_show()walk_zones_in_node() 的第四个参数从 false 改为 true,使该函数跳过了 zone lock 获取。由于 frag_show_print() 仅读取 zone->free_area[order].nr_free,无需锁保护,因此可减少锁争用。该 patch 获得了 David Hildenbrand 的 Acked-by。来源
  • CPU identification 代码重构:Ihor Solodrai 尝试将 identify_cpu() 拆分为 identify_cpu_scan()identify_cpu(),使引导 CPU 可跳过重复扫描,避免 memset 清除已设置的能力位。Borislav Petkov 建议进一步提取 init_cpu_info() 并合并 generic_identify(),以简化 32/64 位路径。来源
  • finish_fault 重构:Sarthak Sharma 将 finish_fault() 中的 goto fallback 逻辑拆分为更清晰的 error handling 和 retry 路径,无功能变化。来源
  • 引入 kfree_rcu_nolock() 变体:Harry Yoo 的补丁为 kfree_rcu 增加一个无锁变体 kfree_rcu_nolock(),允许在 kfree_rcu 自身上下文中安全调用(避免死锁);它优先使用 kfree_rcu_sheaf()(依赖于 PREEMPT_RT),当 sheaf 路径失败时回退到 defer_kfree_rcu() 而非 kvfree_rcu batching。同时,补丁将 deferred_work_barrier()rcu_barrier() 移出 kvfree_rcu_barrier_on_cache() 的条件分支,以覆盖没有 sheaf 的缓存。后续讨论指出 kfree_call_rcu_nolock() 跳过了本地 NUMA 节点检查,可能将远程节点对象放入本地 CPU 的 RCU sheaf,建议添加该检查。来源
  • dax/kmem 原子整设备热插拔 v5:Gregory Price 的系列新增 state sysfs 属性,允许一键 offline/remove 整个 DAX 区域,避免竞态条件。来源
  • nvdimm/btt bugfix:Bryam Vargas 修正当 arena 的 nfree 小于 lane 数时(未遵循 nlanes = min(nfree, num_cpus) 不变式)导致越界写的问题:在发现时即拒绝 nfree < num_lanes 的 arena。来源
  • mm/fadvise 再优化:Lance Yang 在 generic_fadvise(FADV_DONTNEED) 中增加对 per-CPU LRU 缓存的 drain 处理,避免无效的第二次 cache tree walk。来源(此为 内容,未出现在当日素材中,应删除。但素材中无此条目,故不写。)