2026-06-26 内核社区日报

Meta 生产环境因 slabobj_ext 从同一 slab 分配导致内核崩溃,修复方案是强制分配从更大的 kmalloc cache 进行;Wilcox 提交 filemap 页面异常路径简化系列,使所有页面异常变为 killable;DAMON 检测内部访问变化超过 max_nr_regions/2 的能力得到优化。

🧠 重点 · 内存

1. slabobj_ext 跨 slab 循环引用导致 Meta 生产环境栈溢出

发生了什么: Meta 一台 6.16 生产主机(启用 memory allocation profiling)因内核栈溢出 panic,调用链显示 __free_slabkfree 递归达 125 层。根因是 slab 的 obj_exts 数组可能由另一个 slab 的同一大小类分配,形成循环引用:kmalloc-512 slab 的 obj_exts 数组(1024 字节)由 kmalloc-1k 分配,而 kmalloc-1k slab 的 obj_exts 数组(512 字节)由 kmalloc-512 分配;释放任一 slab 时触发另一方释放,导致无限递归。此前修正(280ea9c3154b)仅处理数组来自同一 slab cache 的情况,未消除跨 cache 循环。Shakeel Butt 提交的补丁将检测条件改为 obj_exts_cache->object_size <= s->object_size,当数组本应从同尺寸或更小 cache 分配时,将请求大小提升至 s->object_size + 1,强制从更大的 kmalloc cache 分配,使所有引用边形成有向无环图,彻底消除循环。来源

为什么重要: 该递归仅在 memory allocation profiling 启用时触发,直接导致内核栈溢出和 panic。补丁从根本上消除了 slab 间 obj_exts 数组循环引用的可能性,使 slab 释放路径不再受递归风险,对开启该功能的部署(如 Meta)意义关键。

2. filemap 页面异常路径调整:锁 folio 可 killable,fault 下调整 readahead

发生了什么: Matthew Wilcox (Oracle) 提交了一个 11 补丁系列,对 filemap 页面异常路径进行统一调整:
(1)__filemap_get_folio_mpol() 中锁定 folio 变为 killable;
(2)使所有页面错误 (page faults) 变为 killable;
(3)同步及异步 readahead 均在 fault lock 下进行;
(4)锁定 folio 时不再 pin 住 file,同步读也在 fault lock 下进行;
(5)移除 maybe_unlock_mmap_for_io() 的 fpin 参数,在创建 folio 时遵循 FAULT_FLAG_RETRY_NOWAIT,并在 __filemap_get_folio() 中处理 folio 锁定;
(6)处理写 fault 时不再释放 fault lock,移除 VM_FAULT_COMPLETED 标志。

为什么重要: 该系列统一使文件映射页错误可被可杀死信号中断,减少因等待 folio 锁而陷入不可中断睡眠的场景;同时将 readahead 置于 fault lock 下执行并移除不必要的参数与标志,使代码路径更简洁、锁定行为更一致,有助于提升系统在 I/O 压力下的响应性与可维护性。

来源

3. DAMON:检测 region 内部访问变化超过 max_nr_regions/2 的情况

发生了什么: Jiayuan Chen 提交 v2 系列优化 DAMON 的 region 分裂逻辑。原实现中,kdamond_split_regions()nr_regions 已超过 max_nr_regions / 2 时会直接返回,导致一个大 region 内部的访问变种(access variation)被忽略——即虽然总 region 数较多,但若某 region 内部冷热差异大,DAMON 无法及时分裂它。新补丁改为:即使总 region 数已超出半数,仍选择一部分 region 进行分裂,具体做法是对大 region 按比例减少分裂后代数,但确保至少分裂一个 region 的一部分,从而让 DAMON 能够发现内部变化并调整 region 边界。同时增加了 kunit 测试。

为什么重要: DAMON 的 region 分裂决策直接影响监测精度。原逻辑在总 region 数接近上限时过早放弃分裂,导致大 region 内部的访问热区被掩盖,监测粒度下降。该补丁在 region 数较高的情况下仍能保证必要精度,对 DAMON 用于内存压力预测、reclaim 等场景有正面影响。

来源

4. mempool 扩容优化:避免无意义的分配和拷贝

发生了什么: Vitaly Wool 提交 v1 补丁优化了 mempool_resize() 的逻辑。原实现中,当 mempool 需要扩容到更大的 min_nr 时,总是先分配一个更大的 elements 数组,将旧数据拷贝过去,然后释放旧数组;即使新 min_nr 比旧值小(即缩容),也会执行同样操作——这导致了不必要的内存分配和拷贝。新补丁改为:只在扩容时进行拷贝,缩容时直接调整 min_nr 并释放多余元素,而不改变数组本身。另外,对于扩容但当前 curr_nr(已填充的元素数)小于新 min_nr 的情况,也避免重新分配整个数组(原实现会分配新大数组后立即释放旧数组)。

为什么重要: mempool 常用于块设备、NVMe 等 IO 密集子系统,其 resize 操作在设备热插拔、队列深度调整时会频繁发生。消除无意义的分配/拷贝可减少内存碎片和延迟,对需要动态调整 mempool 规模的驱动有直接收益。

来源

5. 修复 /proc/kpageflags 对所有匿名页错误报告 KPF_KSM

发生了什么: Jinjiang Tu 提交 v2 补丁 fs/proc: fix KPF_KSM reported for all anonymous pages,旨在修复 /proc/kpageflags 对所有匿名页错误报告 KPF_KSM 的问题。

为什么重要: 该补丁修正了页面属性标志位的错误报告,使 KPF_KSM 仅针对真正的 KSM 页面设置,提升了 /proc/kpageflags 接口的准确性。

来源

6. 将 page/folio 的 nid/zonenum 独占位断言移至实际访问站点

发生了什么: Hui Zhu 提交 v7 补丁,将 ASSERT_EXCLUSIVE_BITS() 检查从间接的 memdesc_nid()/memdesc_zonenum()(检查按值复制的 flags 副本)移至 page_zonenum()/folio_zonenum()(直接解引用 page->flags/folio->flags),并将 memdesc_nid() 改为宏让断言直接作用于调用者的 page->flags/folio->flags。对于 CONFIG_NUMA=n,page_to_nid()/folio_nid() 保持为 return 0 的静态内联函数。

为什么重要: 之前的断言检查的是 flags 的本地副本,不能可靠地在并发读取中捕获竞争。补丁使断言直接检查真实共享 flags,提高了此类数据竞争的检测能力,使代码更健壮。

来源

7. cpu_needs_drain() 数据竞争标注

发生了什么: Xuewen Wang 提交 v3 补丁标注了 cpu_needs_drain() 中的 data-race。KCSAN 报告该函数读取另一个 CPU 的 per-cpu folio_batch->nr 时,没有任何同步,而拥有该 CPU 可能同时在 folio_batch_add() 中写入 nr。这是一个故意允许的读:cpu_needs_drain() 是轮询检查是否有 folio 需要 drain,读到的 nr 即使暂时错误(stale)也不会造成破坏。补丁使用 data_race() 宏显式标注,告诉 KCSAN 这个竞争是预期的。

为什么重要: 显式标注 data-race 可以消除误报,减少 KCSAN 的噪声,让开发者更容易聚焦真正的竞争 bug。同时提高了代码可读性。

来源

🔧 其它子系统

eBPF / BPF

  • inner map 查找的 NULL 性优化问题已被修复:Sun Jian 指出 commit d2102f2f5d75 对 inner map 查找的 NULL 性优化在模板场景下可能不适用,但 Eduard Zingerman 指出该问题已被 commit 53040a81ae57 修复,并提醒不要发送旧补丁,Sun Jian 随后放弃该系列。来源
  • libbpf: relo_core.c 中 -Wformat 警告补丁的不一致:补丁为 insn->src_reg、dst_reg、off 和 imm 添加了 (unsigned) 强制转换以匹配 %x 格式,但 insn->code(同为 __u8,也以 %x 打印)未做转换。AI 审查指出这一不一致,询问是否故意遗漏或应统一添加。来源
  • bootconfig 嵌入内核 cmdline(v7):Breno Leitao 继续推进将 CONFIG_BOOT_CONFIG_EMBED_FILE 中的 “kernel” 子树渲染为 flat cmdline 的系列。v7 新增了 x86 架构支持,在 setup_arch() 中调用 xbc_prepend_embedded_cmdline(),确保嵌入式 bootconfig 的 cmdline 能被早期参数处理器解析。同时完善了清理规则和文档。来源

👀 值得追的讨论 / Patch

  • mm/vmscan: 用 vmstat 驱动的扫描平衡成本来减少 lru_lock 争用:Usama Arif 提交 RFC,尝试在 shrink_lruvec 中使用 vmstat 统计(如 pgsteal_*)来动态调整扫描比例,减少对 lru_lock 的过度争夺。这是一个方向性探索,尚未给出性能数据。来源
  • 迁移页表(migration PTE)竞态讨论:Alexander Krabler 报告 mlockall() 进程在迁移页表上等待,但未能复现该竞态;Wandun 通过调试信息证明在非 mlock 进程上 migration_entry_wait 频繁命中,堆栈显示其通过 mmap 系统调用和缺页异常路径被触发。来源

⚡ 一句话速览

  • zsmalloc 锁竞争优化 v6:编码 class index 到 obj 值以实现无锁 class 查找;在 64 位系统上从 zs_free() 中移除 pool->lock;在释放 zspage 之前释放 class 锁,并文档化 free_zspage 辅助变体。来源
  • MGLRU reparenting后stale batch更新修复v3:Qi Zheng 发布第3版补丁,修复memcg reparenting过程中MGLRU页表遍历器批量更新与lruvec锁不一致导致nr_pages计数低估的问题。该补丁通过在reset_batch_size()中于RCU下检查CSS_DYING,将待处理delta重定向至第一个非退出祖先,避免触发lru_gen_exit_memcg警告及潜在的OOM。来源
  • page_owner 清理 v2:Ye Liu 提交 6 个清理补丁,包含提取跳过 buddy 页的 helper、用 MIGRATE_REASON_NONE 替换 -1、将 free_ts_nsec 移到 free 段等,消除代码重复,提升可读性。来源
  • per-memcg-per-node kmem 计账:Alexandre Ghiti 发布 v2 RFC,将 task->objcg 改为 per-node objcgs array,并对 zswap/zsmalloc 启用 per-node 计账,使跨 NUMA 的 kmem 计账更准确。来源
  • shmem_fallocate 整数下溢修复:Yahia Ahmed 修复 shmem_fallocate()unmap_end = round_down(offset + len, PAGE_SIZE) - 1offset + len 小于 PAGE_SIZE 时产生 -1,随后条件 (u64)unmap_end > (u64)unmap_start 因无符号转换恒为真,导致不必要的 unmap_mapping_range TLB shootdown。修复方法是移除强制转换。来源
  • selftests: mincore: 使用默认 huge page size 测试 hugetlb:Yijia Wang 发送补丁,使 mincore selftest 在 hugetlb 测试中使用默认的 huge page size。来源
  • hugetlb_cma 支持百分比保留:Sourav Panda 为 hugetlb_cma 添加百分比指定,允许用户以总内存的百分比而不是固定页数来预留 CMA 区域。来源
  • mm: memcg: 初始化 memcg1_oom_prepare() stub 中的 locked:Breno Leitao 修复当 !CONFIG_MEMCG_V1 时,memcg1_oom_prepare() 的空实现中 locked 变量未被初始化即返回的问题,可能引起 use-of-uninitialized-value 行为。来源