目录
- Remove read-only THP support for FSes without large folio support
- mm: shmem: always support large folios for internal shmem mount
- mm/page_owner: add filter infrastructure for print_mode and NUMA filtering
- mm/swap: use swap_ops to register swap device's methods
- rust: Add helper functions and constants for Tyr driver
- make MM selftests more CI friendly
- mm/mglru: Use folio_mark_accessed to replace folio_set_active in PF
- mm/damon/modules: detect and use fresh status
- mm: prepare anon_vma before swapin rmap
1. khugepaged: mTHP 支持
系列:[PATCH 7.2 v16 00/13] khugepaged: mTHP support作者: Nico Pache(Red Hat)版本: v16(13 个 patch)
背景
透明大页(Transparent Huge Page,THP)机制在 anon 内存上长期只支持 PMD 级别(x86_64 上为 2MB)的"全或无"大页:khugepaged 在后台扫描并尝试把 512 个连续 4KB 页面提升为一个 PMD THP。近年来内核引入了 mTHP(multi-size THP),允许 anon 内存使用 2^n × 4KB(例如 16KB/64KB/1MB 等)的中间大小折叠,并通过 /sys/kernel/mm/transparent_hugepage/hugepages-*kB/enabled 按 order 独立配置。然而此前 mTHP 仅能在缺页路径(page fault)中被分配,一旦落盘为普通 4KB 页,khugepaged 就无法再把它们折叠成任何 mTHP 尺寸——折叠路径在 hugepage_pmd_enabled()、collapse_huge_page()、__collapse_huge_page_*() 中硬编码了 HPAGE_PMD_ORDER,使 mTHP 成为"只生不养"的孤儿。
解决的问题
khugepaged 无法为已经碎片化为 4KB 页的 anon 区折叠出 mTHP,导致 mTHP 的内存收益无法持续- 当用户仅开启 mTHP(未开启 PMD THP)时,
khugepaged 线程根本不会启动 max_ptes_none(允许填充的空 PTE 数)在 PMD 扫描阶段会提前截断扫描,使小尺寸 mTHP 折叠机会被漏掉- 缺少 per-order 的折叠失败统计和 tracepoint,无法诊断为何 mTHP 折叠成功率偏低
- 需要避免"collapse creep":一次 mTHP 折叠引入过多新 none 页,会使后续扫描反复把它升级到更大的 order
如何做
整套方案采取"先泛化、再扩展"两步走。前半部分(patch 1–5)把 hugepage_vma_revalidate()、alloc_charge_folio()、__collapse_huge_page_isolate/copy/swapin() 和 collapse_huge_page() 统统按 order 参数泛化,并用 collapse_max_ptes_none/shared/swap() 辅助把 is_khugepaged 与 madvise_collapse 的特殊逻辑封装起来。patch 6 加入"不把已经较大的 mTHP 折叠成更小 mTHP"的防倒退检查(通过 SCAN_PTE_MAPPED_HUGEPAGE 结果向上层通知),patch 9 引入 collapse_allowable_orders(vma, tva_flags) 作为折叠时可选 order 集合的单一入口。
核心实现位于 patch 10(位图扫描)和 patch 11(早退优化)。在 struct collapse_control 中新增两段位图和一个栈:
#define KHUGEPAGED_MIN_MTHP_ORDER 2
#define MTHP_STACK_SIZE (1UL << (ilog2(MAX_PTRS_PER_PTE) - KHUGEPAGED_MIN_MTHP_ORDER))
DECLARE_BITMAP(mthp_bitmap, MAX_PTRS_PER_PTE);
DECLARE_BITMAP(mthp_bitmap_mask, MAX_PTRS_PER_PTE);
structmthp_rangemthp_bitmap_stack[MTHP_STACK_SIZE];
collapse_scan_pmd() 在扫描 PMD 的 512 个 PTE 时,若开启了任何 mTHP order,就把 max_ptes_none 抬到 KHUGEPAGED_MAX_PTES_LIMIT 以跑完整个 PMD,并对每个非空、非零的 PTE 位置 __set_bit(i, cc->mthp_bitmap)。扫描结束后进入新函数 mthp_collapse(),以 (offset, order) 为单元在栈上做非递归的二叉搜索:从 (0, HPAGE_PMD_ORDER) 开始,用 bitmap_weight_and() 统计该区间的占用 PTE 数,若满足该 order 的 max_ptes_none 门槛就调用 collapse_huge_page(..., order) 尝试折叠;失败或不合格则压入左右两半 (offset, order-1) 和 (offset + nr_ptes/2, order-1),一路降到 KHUGEPAGED_MIN_MTHP_ORDER=2 为止。patch 11 进一步根据 collapse_huge_page() 的返回结果分类:SCAN_SUCCEED 继续下一段,SCAN_LACK_REFERENCED_PAGE/SCAN_EXCEED_* 等尝试更低 order,而 OOM 或 ENOMEM 等致命错误直接 return collapsed 终止整轮。
为防止"collapse creep",patch 10 把 mTHP 路径的 max_ptes_none 限定为 0 或 HPAGE_PMD_NR-1 两个极端值——中间值会因一次折叠引入大量 none 页后触发下轮向更高 order 的"升级",形成反复折腾;collapse_max_ptes_none() 对非法值直接告警并跳过 mTHP 尝试。含 swap 或 shared 页的 mTHP 区间同样被拒绝。
patch 7 新增三项 per-order 统计 collapse_exceed_swap_pte / collapse_exceed_none_pte / collapse_exceed_shared_pte(暴露于 /sys/kernel/mm/transparent_hugepage/hugepages-*kB/stats/),patch 8 为 mm_collapse_huge_page、mm_collapse_huge_page_isolate、mm_collapse_huge_page_swapin 三个 tracepoint 追加 order 字段。patch 12 把原 hugepage_pmd_enabled() 改名并扩展为 hugepage_enabled()——只要任一 anon order 位图非零(READ_ONCE(huge_anon_orders_always/madvise/inherit))即启动 khugepaged,同时在 collapse_allowable_orders() 中对 khugepaged 扫描 anon VMA 时返回 THP_ORDERS_ALL_ANON 全集。至此仅开启 mTHP(未开启 PMD THP)的用户也能享受后台折叠。
收益
作者未提供 benchmark 数据表格,但给出以下测试观察:
- 在 x86_64、aarch64、ppc64le、s390x 上构建并通过 kernel-tests 和 selftests/mm 全量回归
- 作者自研的 khugepaged 压力脚本测试显示:"there was no significant regression noticed... In some cases my changes had better collapse latencies, and was able to scan more pages in the same amount of time/work"
- Redis 场景下配合后续 defer 系列测试,本次先合入 mTHP 基础设施
从代码逻辑推断的预期收益:mTHP 用户首次获得持续的后台折叠能力——既可以在应用运行中把已碎片化的 anon 区折回 16KB/64KB/…/2MB 等任意开启的 order,显著降低 TLB miss 与 page table 占用;bitmap + 栈的 O(N log N) 搜索相比对每个 order 独立扫描 N 次更高效;per-order 统计与 tracepoint 首次让 mTHP 折叠成功率可观测,为后续调参提供数据基础。
2. Remove read-only THP support for FSes without large folio support
系列:[PATCH 7.2 v3 00/12] Remove read-only THP support for FSes without large folio support作者: Zi Yan(NVIDIA)版本: v3(12 个 patch)
背景
CONFIG_READ_ONLY_THP_FOR_FS 是 2019 年引入的实验性选项,允许 khugepaged 和 madvise(MADV_COLLAPSE) 为不支持 large folio 的文件系统(当时几乎所有 FS 都不支持)创建只读 PMD THP。为了防止写入一个被折叠成 THP 的只读文件时产生数据损坏,它依赖一套精巧的同步机制:collapse_file() 在折叠前增加 mapping->nr_thps 计数并执行 smp_mb(),随后检查 inode->i_writecount;而 do_dentry_open() 以写模式打开文件时先 get_write_access() 自增 i_writecount、执行全内存屏障,再检测 nr_thps > 0 并截断整个页缓存。近几年 XFS、ext4、btrfs 等主流 FS 已陆续原生支持 large folio(mapping_large_folio_support()),但这条 RO-THP 旧路径仍与新路径并存,形成互斥的两套矩阵:large folio FS 走缺页路径但 khugepaged/MADV_COLLAPSE 不可用,非 large folio FS 反而能走 RO-THP。这种割裂难以维护,也让 pagecache 折叠能力在"现代"FS 上缺失。
解决的问题
- 支持 large folio 的 FS 无法通过
khugepaged 和 MADV_COLLAPSE 创建 PMD THP(只能依赖缺页路径凑出) CONFIG_READ_ONLY_THP_FOR_FS 蕴含"该 FS 不支持 large folio",两套机制相互排斥且均需 PMD 尺寸的 large folio 支持mapping->nr_thps 与 inode->i_writecount 的跨子系统屏障同步复杂、易错,长期占用 struct address_space 8 字节- 非 large folio FS 借 RO-THP 绕路合成 PMD THP,再在
truncate_inode_partial_folio() 里用 try_folio_split_to_order() 强制非均匀拆回 order-0,使拆分路径凭空多出一条分支 - 不再允许向一个已被折叠为 THP 的文件写入时 invalidate 整个 pagecache 这种"粗暴"行为
如何做
方案的关键洞见在 cover letter 的对比表:移除 READ_ONLY_THP_FOR_FS 后,矩阵从"large folio FS 只能 PF + 不支持 FS 只能 collapse"归一为"large folio FS 三条路径全通、不支持 FS 全不可用"。实施路径分两阶段。
阶段一(patch 1–4)先把 large folio FS 的折叠路径打通,防止移除旧机制时出现功能回退:
patch 1:collapse_file() 把 VM_BUG_ON(!IS_ENABLED(CONFIG_READ_ONLY_THP_FOR_FS) && !is_shmem) 替换为新 helper mapping_pmd_thp_support(),该 helper 封装 mapping_max_folio_order(mapping) >= PMD_ORDER,并在 include/linux/pagemap.h 中提供;同时 VM_BUG_ON 降级为 VM_WARN_ON_ONCE
patch 2:这是取代旧同步机制的核心。在 collapse_file() 对每个候选 folio 执行 try_to_unmap() 之后、try_to_unmap_flush() 之前,新增 folio_test_dirty() 检查:
if (!is_shmem && folio_test_dirty(folio)) {
result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
goto out_unlock;
}
原理是:folio 已被 lock 且 unmap,但某 CPU 可能持有脏 TLB 条目并在此窗口内写入。只要在 TLB flush 之前确认 folio 未被弄脏,之后任何新写都必须先走 page walk 拿 folio lock(已被 khugepaged 持有),从而保证折叠后的 THP 不会与脏页共存。
patch 3:file_thp_enabled() 去掉 IS_ENABLED(CONFIG_READ_ONLY_THP_FOR_FS) 和 !inode_is_open_for_write(inode) 两个条件,改用 mapping_pmd_thp_support()——现在读写打开的文件也能享受 PMD THP 缺页
patch 4:hugepage_pmd_enabled() 重构,不再把 READ_ONLY_THP_FOR_FS 做为 true 分支;若全局 THP 开启就允许 file/anon/shmem 三类 khugepaged 运行,否则 anon/shmem 分别回退到 per-size 控制
阶段二(patch 5–12)拆除旧机制及其所有依赖:
- patch 5 删除
CONFIG_READ_ONLY_THP_FOR_FS Kconfig 项 - patch 6 删除
filemap_nr_thps()、filemap_nr_thps_inc/dec(),同时 fs/open.c::do_dentry_open() 中那段在写打开时 invalidate 整个 pagecache 的 27 行魔法块连同 collapse_file() 的 nr_thps 自增/回滚代码一并删除 - patch 7 从
struct address_space 删除 atomic_t nr_thps 字段(64 位系统上省 8 字节,对大量 inode 缓存的系统有益) - patch 8 删除
folio_check_splittable() 中针对 READ_ONLY_THP_FOR_FS 的特判 - patch 9 在
truncate_inode_partial_folio() 中把 try_folio_split_to_order() 替换为直接 folio_split(folio, min_order, split_at, NULL)——既然大 folio 只会出现在支持 large folio 的 FS 上,就一定允许非均匀拆分;老的 try_folio_split_to_order() 整个函数被删除 - patch 10 清理 btrfs defrag 注释
- patch 11、12 更新 selftests(
khugepaged、guard-regions),新增 read-write 文件折叠测试,去除只读 fd 限制
收益
作者未提供性能 benchmark 数据,但改动本身带来明显收益:
struct address_space 缩小 8 字节(作者原话:"This shrinks struct address_space by 8 bytes on 64-bit systems which may increase the number of inodes we can cache")- 代码净删除约 73 行(全系列
+156 / -229) - 功能矩阵统一:所有支持 large folio(含 PMD_ORDER)的 FS 自动获得
khugepaged 和 MADV_COLLAPSE 能力,首次允许读写打开的文件被折叠为 PMD THP - 删除
do_dentry_open() 中"写打开即 invalidate 整个 pagecache"的粗暴回收——对大文件只读 → 读写切换的场景避免了缓存冷启动损耗 - 简化同步语义:用一次
folio_test_dirty() 检查取代跨 fs/mm 子系统的 nr_thps + i_writecount + smp_mb() 三方屏障协议
3. mm: shmem: always support large folios for internal shmem mount
系列:[PATCH v3] mm: shmem: always support large folios for internal shmem mount作者: Baolin Wang(Alibaba Cloud)版本: v3(单 patch)
背景
shmem 有两种使用形态:通过 mount -t tmpfs 或 ramfs 等挂载出的 tmpfs 用户挂载点,以及由内核内部(SB_KERNMOUNT 标志)挂起的 internal shmem mount——后者即 anonymous shmem,被 memfd_create()、/dev/shm、SysV shm、GPU/DRM 的 shmem helper 等广泛使用。tmpfs 通过 mount -o huge=always/within_size/advise/never 配置是否支持大 folio,值保存在 sbinfo->huge;而 anonymous shmem 还暴露了 /sys/kernel/mm/transparent_hugepage/shmem_enabled 以及 per-order 的 hugepages-*kB/shmem_enabled 两级 sysfs,可运行时动态切换 huge 策略。然而 __shmem_get_inode() 在创建 inode 时只根据静态的 sbinfo->huge 决定是否调用 mapping_set_large_folios()——一旦创建时 sbinfo->huge=0,后续即便用户通过 sysfs 开启了 mTHP order,该 inode 的 AS_LARGE_FOLIO_SUPPORT 标志也不会回写,导致无法分配大 folio。
解决的问题
- anonymous shmem 的
mapping_set_large_folios() 只在 inode 创建瞬间依据 sbinfo->huge 决定,无法反映 sysfs shmem_enabled 的运行时变化 - 用户看到 sysfs 配置开启了大 folio 但实际分配仍退化到 order-0,cover letter 链接 [1] 记录了已出现的用户困惑
sbinfo->huge 与 per-size sysfs 控制之间语义不一致,形成双重开关
如何做
与 David Hildenbrand 讨论后确定的方案是:对 internal shmem mount 始终设置 mapping_set_large_folios(),具体允许哪些 order 完全由动态 sysfs 接口决定。实现只改动 mm/shmem.c::__shmem_get_inode() 中的一个判断:
- /* Don't consider 'deny' for emergencies and 'force' for testing */
- if (sbinfo->huge)
+ if (sbinfo->huge || (sb->s_flags & SB_KERNMOUNT))
mapping_set_large_folios(inode->i_mapping);
通过 SB_KERNMOUNT 这个 super_block 标志识别"内核内部挂起"的 shmem(与用户 mount -t tmpfs 区分开):对内核内部挂载一律打开 large folio 支持位,这样 mapping 层不再成为限制,真正的 order 过滤交给 shmem_allowable_huge_orders() 这条路径按 shmem_enabled sysfs 动态裁剪;对用户态 tmpfs 则保持旧语义,仍尊重 mount 时的 huge= 选项。v3 相对 v2 的唯一改动就是采纳 David 的建议从"有条件打开"改为"internal mount 无条件打开",将配置权彻底让渡给 sysfs。
收益
作者未提供性能数据。从代码逻辑推断的预期收益:
- 消除了 anonymous shmem 用户"开了 sysfs
shmem_enabled 却分不到大 folio"的语义陷阱 - memfd、SysV shm、GPU 驱动 shmem 对象等内核内部 shmem 场景在运行时切换 mTHP 配置后立即生效,无需重启
- 使 shmem 大 folio 的 运行时可配置语义 与 anon mTHP 对齐,统一收敛到 sysfs 单一真源
- 改动极小(
+11 / -2 共 13 行),SB_KERNMOUNT 判定在 inode 创建快路径上开销可忽略
4. mm/page_owner: add filter infrastructure for print_mode and NUMA filtering
系列:[PATCH v2 0/3] mm/page_owner: add filter infrastructure for print_mode and NUMA filtering作者: Zhen Ni(EasyStack)版本: v2(3 个 patch)
背景
page_owner 是内核用于追踪每个物理页分配者(调用栈、PID、GFP flags、NUMA 节点等)的核心调试特性,广泛应用于内存泄漏、碎片化和 OOM 问题的诊断。当用户 cat /sys/kernel/debug/page_owner 时,内核会遍历所有在线页帧,并对每一页通过 stack_depot_snprint() 打印完整的调用栈字符串。在大内存(250 GB+)生产环境中,单次转储的文件体积通常在几 GB 到 10 GB 以上。同时,在 NUMA 感知的部署场景(如 DPDK 云环境中 QEMU 进程绑定到指定节点),OOM 通常是节点局部事件,却必须收集全节点数据后再用 tools/mm/page_owner_sort.c 在用户态做合并与过滤。现有接口缺少任何服务端过滤能力,把所有选择工作都推给了离线后处理。
解决的问题
- 生产机上
page_owner 输出动辄 GB 级,占用本地存储并使跨机传输困难 - 调用栈在内核 stackdepot 中早已去重,却在打印时被展开为完整文本,用户态又得重新去重,做了双倍功
- 无法按 NUMA 节点过滤:排查单节点 OOM 时必须拉全部节点的数据
- v1 采用
bool compact 和单节点 int 两种过于粗糙的控制语义,社区反馈后需要更具描述性和扩展性的接口
如何做
patch 1 在 mm/page_owner.c 引入 filter 基础设施:新增 enum page_owner_print_mode(PAGE_OWNER_PRINT_FULL_STACK / PAGE_OWNER_PRINT_STACK_HANDLE),定义 struct page_owner_filter { enum print_mode; nodemask_t nid_mask; } 的静态实例 owner_filter,并在 pageowner_init() 中创建 /sys/kernel/debug/page_owner_filter/ 目录承载各过滤器旋钮。
patch 2 在 print_page_owner() 的打印路径把原本无条件的 stack_depot_snprint(handle, ...) 改为按 READ_ONCE(owner_filter.print_mode) 分支:handle 模式下只写一行 handle: %d,其余字段(order、gfp mask、pid、tgid、comm、PFN、migratetype、flags、memcg 等)保持不变。handle 到真实栈的映射通过已有的 page_owner_stacks/show_stacks_handles 接口查询,实现了"分取数据 + 分取字典"的拆分存储。旋钮通过 DEFINE_SIMPLE_ATTRIBUTE(page_owner_print_mode_fops, ...) 注册为可读写的 debugfs 文件。
patch 3 加入 NUMA 节点过滤器 nid:写路径 nid_filter_write() 使用动态 kmalloc,输入长度上限沿用 cpuset 的 (100 + 6 * MAX_NUMNODES) 模式,支持 "-1" 清空以及 "0"、"0,2,3"、"0-3"、"0,2-4,7" 等 nodelist 格式,核心解析复用内核已有的 nodelist_parse();读路径则用 %*pbl 紧凑打印 nodemask(如 0-2 或 0,2-4,7)。过滤动作下沉到 read_page_owner() 的遍历循环中:对每个候选页 READ_ONCE(owner_filter.nid_mask),若 mask 非空则 node_isset(page_to_nid(page), mask) 不满足就 goto ext_put_continue 跳过。READ_ONCE/WRITE_ONCE 保证用户态修改旋钮和内核态读取之间的无锁并发安全。
收益
作者未给出压测数字,但从实现逻辑可直接推断:开启 print_mode=1 后每条记录由若干行调用栈缩成一行 handle: N,对典型深度 8-16 帧的分配栈意味着输出体积降至原先的 1/5 到 1/10,10 GB 级 dump 可压缩到 1-2 GB 量级;而调用栈信息并未丢失,show_stacks_handles 仍可按需反查。NUMA 过滤则把遍历开销(仍需扫全内存)和 I/O 开销(仅写目标节点)解耦,单节点 OOM 分析时输出和传输成本按节点数近似线性下降。封面信把该基础设施描述为"可扩展的 filter 框架",明确规划了未来的 PID/TGID、时间窗、GFP 标志、迁移类型等过滤维度,本次改动因此不仅是两个新旋钮,更是一个面向生产诊断的长期接口底座。整个系列 mm/page_owner.c 仅 +122/-2 行,侵入面非常有限。
5. mm/swap: use swap_ops to register swap device's methods
系列:[PATCH v4 0/3] mm/swap: use swap_ops to register swap device's methods作者: Baoquan He(Red Hat)版本: v4(3 个 patch)
背景
Linux swap 子系统当前支持三类 swap 后端:基于文件系统的 SWP_FS_OPS(走 ->swap_rw 回调,例如 nfs/tmpfs-on-btrfs 等)、基于块设备的同步 I/O SWP_SYNCHRONOUS_IO(典型场景是 zram、pmem 这类可直接完成的快速设备),以及普通的异步块设备 swap。历史上这三种路径都集中在 mm/page_io.c:__swap_writepage() 与 swap_read_folio() 内部通过一连串 if (sis->flags & SWP_FS_OPS) ... else if (SWP_SYNCHRONOUS_IO) ... else ... 的分支分派到 swap_writepage_fs/bdev_sync/bdev_async 以及对应的 swap_read_folio_fs/bdev_sync/bdev_async。与此同时,mm/page_io.c 这个沿用自 1991 年的文件名容易让人误以为是通用 page I/O,实际上它已经演化为纯粹的 swap I/O 实现。
解决的问题
- 读、写路径各自重复同一套
flags 分支判定,三种设备类型再扩展时需要同时修改读、写两条 if/else 链,维护和扩展成本高。 - 分派逻辑与
data_race() 注释散落在两处,新增 swap 后端类型(例如未来可能的 zswap 直通、swap-over-RDMA、swap_activate 等扩展回调)时没有统一入口可插桩。 - 文件名
mm/page_io.c 与其实际内容(纯 swap I/O)不匹配,MAINTAINERS、include 注释、git 历史都在 swap 上下文里引用它,容易造成混淆。 - 函数命名
swap_writepage_* 仍残留早期 page 语义,而参数和实现已经是 folio,与对端 swap_read_folio_* 风格不对称。
如何做
整套改动分三个递进步骤:
- 文件与命名归位(patch 1/3):将
mm/page_io.c 重命名为 mm/swap_io.c,同步更新 mm/Makefile、mm/swap.h 注释、MAINTAINERS 中对应的 F: 条目;patch 3/3 进一步把 swap_writepage_fs/bdev_sync/bdev_async 改名为 swap_write_folio_fs/bdev_sync/bdev_async,与 swap_read_folio_* 形成对称命名。 - 引入
swap_ops vtable(patch 2/3):在 mm/swap.h 定义抽象操作集:structswap_ops {
void (*read_folio)(struct swap_info_struct *sis,
struct folio *folio, struct swap_iocb **plug);
void (*write_folio)(struct swap_info_struct *sis,
struct folio *folio, struct swap_iocb **plug);
};
在 struct swap_info_struct 新增 const struct swap_ops *ops 字段;把原先的三对分派函数改为实例化三个 const 静态 vtable:bdev_fs_swap_ops、bdev_sync_swap_ops、bdev_async_swap_ops。 - 集中初始化:新增
init_swap_ops(),在 SYSCALL_DEFINE2(swapon, ...) 中 setup_swap_extents() 之前调用,一次性依据 SWP_FS_OPS / SWP_SYNCHRONOUS_IO 标志挑选 vtable,若 sis->ops 或其 read_folio/write_folio 为空则直接让 swapon 失败返回 -EINVAL(此 NULL 校验是 v3 采纳 Chris Li 的建议后加入)。I/O 热路径从此只剩一行 sis->ops->read_folio(sis, folio, plug) 与 sis->ops->write_folio(...),原来的 __swap_writepage() 被彻底删除,zswap 的 writeback 路径 (zswap_writeback_entry) 也改为调用 si->ops->write_folio(),三条 I/O 判定分支一次性归并到 swapon 时刻。
收益
作者未提供性能基准数据(这是一次结构性清理,不涉及热路径行为变化)。从代码逻辑可推断的收益:
- 热路径的
data_race(sis->flags & SWP_FS_OPS) 与 SWP_SYNCHRONOUS_IO 分支被消除,每次 swap-in/swap-out 少两次原子性无保证的标志检查,替换为一次间接调用;由于 vtable 在 swapon 时绑定且 const,分支预测和 I-cache 行为更稳定。 - 后续添加新 swap 后端(例如 Chris Li 在 v3 评审中提到的未来
swap_activate 回调、或第三方快速存储如 CXL.mem、RDMA swap)时只需新增一个 swap_ops 实例并扩展 init_swap_ops() 的分支,无需再改动 I/O 热路径。 - 文件重命名使 MAINTAINERS 路径语义清晰(
mm/swap_io.c 与已有的 mm/swap.c、mm/swap_state.c、mm/swap_table.h 成系列),函数改名消除了 page/folio 术语不一致,降低新贡献者理解门槛。该系列已被 Kairui Song、Chris Li、Nhat Pham、Barry Song、Usama 等核心 swap 维护者 Reviewed-by / Acked-by,v4 主要是修正 v3 中 init_swap_ops() 的一个未实测的 code bug 与拼写。
6. rust: Add helper functions and constants for Tyr driver
系列:[PATCH 0/4] rust: Add helper functions and constants for Tyr driver作者: Alvin Sun版本: v1(4 个 patch)
背景
Tyr 是正在上游化的 Arm Mali CSF(Command Stream Frontend)系列 GPU 的 Rust 版 DRM 驱动(对标现有 C 实现 Panthor),目标是作为 Rust-for-Linux 在大型 DRM 驱动上的首批落地之一。Tyr 的 VM/BO(virtual memory / buffer object)ioctl 在上游 merge request(gitlab.freedesktop.org/panfrost/linux MR 64)中进入实现阶段后,发现 Rust kernel crate 里缺若干基础砖:没有 SZ_4G 常量、Mm 抽象没有暴露 task_size、XArray 的 Guard 缺少「找到第一个已占用索引」的辅助接口,同时 StoreError 的文档没有同步即将引入的 alloc() 语义。这些缺失都是阻塞 Tyr 驱动继续合入的「前置依赖」。
解决的问题
- GPU 用户 VA 空间布局计算(
VmLayout)需要以 4 GiB 为粒度做对齐,而 rust/kernel/sizes.rs 目前只停在 SZ_2G。 - 当 Tyr 驱动接受
VmUserSize::Auto 时需要查询当前进程的 task size(即 mm_struct.task_size)以决定用户 VA 范围的上界,但 Rust 的 Mm 抽象未开放此字段。 - Tyr 在释放 VM 对象时需要遍历 XArray 清理遗留条目,而
Guard 当前只有 store/remove 类基本操作,没有「首个 present 索引」查询,C 侧对应的是 xa_find(..., XA_PRESENT)。 - Onur Özkan 的
xa_alloc 前置系列会让 StoreError 同时用于 store 与 alloc 两条路径,原 doc 只提 store,容易误导。
如何做
四个 patch 都是小而精确的 Rust 绑定补齐,无 behavior change:
SZ_4G 常量(rust/kernel/sizes.rs):直接 pub const SZ_4G: usize = SZ_2G * 2;(沿用本文件既有风格:SZ_1G/SZ_2G 来自 bindings::SZ_*,但 C 侧没导出 SZ_4G,故以 SZ_2G * 2 实现,注释值 0x100000000)。- **
Mm::task_size()**(rust/kernel/mm.rs):#[inline]
pubfntask_size(&self) -> u64 {
// SAFETY: self.as_raw() is a valid pointer to an mm_struct.
unsafe { (*self.as_raw()).__bindgen_anon_1.task_size asu64 }
}
注意读取的是 mm_struct 内匿名 union(__bindgen_anon_1)里的 task_size 字段;SAFETY 注释沿袭 Mm 类型不变式。 StoreError 文档同步(rust/kernel/xarray.rs):把 rustdoc 中 store 的单点引用改为 [Guard::store] 与 [Guard::alloc] 并列,并在两个字段注释里补上 "or allocated" 语义,为 Onur 的 alloc() 合入做铺垫。- **
Guard::find()**(rust/kernel/xarray.rs):新增返回 Option<usize> 的 helper,内部调用 bindings::xa_find(self.xa.xa.get(), &mut index, usize::MAX, bindings::XA_PRESENT),index 初值 0,命中返回 Some(index)、空数组返回 None。因为方法接收 &mut self,与 Guard 的锁契约一致,SAFETY 注释强调「类型不变式保证 xa 有效且持锁」。
收益
作者未提供性能数据,本系列是典型的「enablement」型补丁,收益以解除下游阻塞为主:
- 解除 Tyr 驱动合入阻塞:四项前置砖齐备后,Tyr 的 VM/BO ioctl 补丁(MR 64)就可以直接基于
Mm::task_size()、SZ_4G、Guard::find() 编写 safe-by-default 的 Rust 实现,而不必再在驱动里手写 bindgen 字段访问或临时 helper。 - 扩大 Rust 通用 crate 的覆盖:新增的三项 API(
SZ_4G、Mm::task_size、Guard::find)对后续其他 Rust-for-Linux 驱动同样可复用,尤其 Guard::find 填补了 XArray 遍历语义的缺口,使 Rust 侧的 XArray 用法更接近 C 侧 xa_find(..., XA_PRESENT) 的能力。 - 文档一致性:
StoreError 注释的前瞻性更新避免了 Onur 的 xa_alloc 系列合入后出现文档与 API 不一致的过渡窗口,降低评审 churn。 - 风险面小:所有改动要么是
const、要么是薄 wrapper,SAFETY 注释清晰;改动行数极少(rust/kernel/mm.rs +7、rust/kernel/sizes.rs +2、rust/kernel/xarray.rs +25/-3),易于 review 与后续维护。
7. make MM selftests more CI friendly
系列:[PATCH v2 00/53] make MM selftests more CI friendly作者: Mike Rapoport(Microsoft)版本: v2(53 个 patch)
背景
tools/testing/selftests/mm/ 下的 HugeTLB 相关测试多年来分别由不同作者提交,彼此约定不一:有的要求"若干默认大页",有的硬编码"至少 256 MB 大页",有的在缺失任一大小的大页时直接跳过一整组用例。驱动层的 run_vmtests.sh 因此积累了近 130 行用于扫描 /proc/meminfo、调整 /proc/sys/vm/nr_hugepages、保存恢复 shmmax/shmall、遍历 /sys/kernel/mm/hugepages/**/nr_hugepages 的脚本代码,逻辑割裂且难以维护。与此同时,大量 HugeTLB 与 THP 测试没有使用 kselftest 框架,输出不是 TAP13 格式,对 CI 系统不友好:结果解析困难、失败定位粗糙、无法与其他 kselftest 汇总。
解决的问题
run_vmtests.sh 承担了过多"大页管理员"职责,与具体测试的需求高度耦合但又不够精确——例如 uffd-wp-mremap 需要每种 hugepage size 至少 1 个,脚本里用一段 bash 循环逐个 /sys/kernel/mm/hugepages/*-kB/nr_hugepages 写 1 并在 trap 里恢复- 多个测试硬编码
TWOMEG 作为 THP 和 HugeTLB 页大小,在 ARM64 64 KB PAGE_SIZE(PMD 512 MB)或 ppc64le 16 MB 巨页的机器上直接错误 - HugeTLB 的
hugetlb-shm、hugetlb-vmemmap、hugetlb-madvise、hugetlb_madv_vs_map、hugetlb-read-hwpoison 以及 khugepaged、ksm_tests、protection_keys、uffd-stress、uffd-unit-tests、va_high_addr_switch 等都没接 kselftest harness hugetlb-read-hwpoison 读到中毒页触发 SIGBUS 后直接被信号终止,CI 中表现为崩溃而非受控 failrun_vmtests.sh 里 THP 和 KSM 测试被错误地用 HAVE_HUGEPAGES 门控hugepage-* 命名与 hugetlb-* 命名混用,map_hugetlb 与 hugepage-mmap 功能重复
如何做
系列按 6 段有序推进:
- Patch 1-4(小修):为
hugetlb-read-hwpoison 装上 dummy SIGBUS handler(signal(SIGBUS, sigbus_handler),handler 只打印 "received SIGBUS\n")避免 CI 被信号杀死;让 khugepaged 的 collapse_single_pte_entry_compound 对 tmpfs 改用 MADV_REMOVE(原本因 MADV_DONTNEED 不能驱逐 tmpfs 页而 skip);migration.c 用 read_pmd_pagesize() / default_huge_page_size() 动态检测 pmd 大小替换 TWOMEG;解绑 THP/KSM 与 HAVE_HUGEPAGES。 - Patch 5-19(机械改造):合并
map_hugetlb.c 进 hugepage-mmap.c 并统一改名 hugetlb-*;将 10+ 个 HugeTLB/THP/KSM/pkey/uffd/va_high 测试改写为 kselftest 框架(TAP13 输出、ksft_test_result_pass/fail、exit code 规范化)。 - Patch 20-26(API 扩展):把
thp_settings.[ch] 重命名为 hugepage_settings.[ch];在其中并入 HugeTLB 帮助函数——detect_hugetlb_page_size() 返回 unsigned long、新增 get_nr_hugepages() / set_nr_hugepages()、get_free_hugepages() 改名、hugetlb_setup() 和 hugetlb_teardown() 一对 API 封装"按需申请并在退出时还原"的完整流程;另外在 thp_settings 基础上加入 atexit() + 信号 handler,保证测试异常退出时 THP/HugeTLB 旋钮被还原。 - Patch 27-28(基础设施):把
read_file() / read_num() / write_num() 从 thp_settings 挪到通用 vm_util;加入 shm_save() / shm_restore() 帮助函数供 hugetlb-shm 和 thuge-gen 调用。 - Patch 29-51(整合 API):让每个用到 HugeTLB 的测试(
compaction_test、cow、gup_longterm、gup_test、hmm-tests、hugepage_dio、hugetlb_fault_after_madv、hugetlb-madvise、hugetlb_madv_vs_map、hugetlb-mmap、hugetlb-mremap、hugetlb-shm、hugetlb-soft-online、hugetlb-vmemmap、migration、pagemap_ioctl、protection_keys、thuge-gen、uffd-stress、uffd-unit-tests、uffd-wp-mremap、va_high_addr_switch 等)在 main() 或 setup() 里自行 hugetlb_setup(),退出前 hugetlb_teardown()。 - Patch 52-53(脚本瘦身):从
run_vmtests.sh 里彻底删除近 130 行 /proc/meminfo 扫描、uffd_min_KB 计算、nr_hugepages 试错、shmmax/shmall 保存、/sys/kernel/mm/hugepages/** 循环等代码;uffd-stress hugetlb 的参数由"动态计算的 half-free"改成固定 128 MB(测试自行申请);run_vmtests.sh 最终大幅瘦身,回归"调度器"本分。
收益
作者未提供性能数据,但从架构改动可以直接列出收益:
- 驱动脚本
run_vmtests.sh 一次删除 128 行纯环境管理代码(patch 53 diff 为 +9/-119),维护复杂度显著下降 - 每个 HugeTLB 测试现在都是自洽的:无论直接
./hugetlb-mmap 还是在 CI 中被调度,setup/teardown 行为一致,不再依赖 run_vmtests.sh 预先准备环境——这对 CI 调度器(可并行分发单个测试)是关键先决条件 - 全系列总 diff 为
+1376 / -1475 行,净减约 100 行,外加 42 个文件触及并完成 kselftest TAP13 化,CI 解析首次具备一致性 - 消除了
TWOMEG 硬编码、MADV_DONTNEED 对 tmpfs 失效的错误 skip、SIGBUS 杀测试、HAVE_HUGEPAGES 误门控 THP/KSM 等四类长期问题
封面信把目标概括为"使测试自行处理 HugeTLB 的 setup 和 teardown",v2 相较 v1 的变更记录(修复 hugetlb mmap 测试实际覆盖文件映射、提高 khugepaged 最大测试数量、加入审阅 tag)显示系列已进入收敛阶段,作者在封面信中表态"可进入 mm-new 等待更多 review 反馈",预示近期合入。
8. mm/mglru: Use folio_mark_accessed to replace folio_set_active in PF
系列:[PATCH] mm/mglru: Use folio_mark_accessed to replace folio_set_active in PF作者: Barry Song(Xiaomi)版本: v1(1 个 patch)
背景
MGLRU(Multi-Generational LRU)通过多代 LRU 替代传统 active/inactive 双队列,为页表映射页提供了更精细的老化模型。在缺页路径上,folio_add_lru() 对所有带 lru_gen_in_fault() 标记的新 folio 调用 folio_set_active(),从而把它们直接放进最年轻的代以获得最高保护等级。该设计假设只要页在缺页时被建立 PTE 映射,就是真正的工作集。commit 4d5d14a01e2c "mm/mglru: rework workingset protection" 之后,PG_active folio 改放到次年轻代,稍稍降低了过度保护,但在有 readahead 的场景下仍然过于乐观。
解决的问题
- Readahead(尤其是
mmap 文件的 fault-around 和文件顺序预读)会把大量并未被实际访问的页加入 LRU;MGLRU 把这些"预取但未用"的页当成热页,推挤出真正的工作集 folio_set_active() 无条件置 PG_active,相比经典 LRU 的"二次机会"更激进,与 readahead 的存在性冲突- 之前 Lei Liu 尝试引入专用 readahead LRU,但该方案被视为过度工程化
- arm64 在 commit 315d09bf30c2 之后需要依赖
arch_wants_old_prefaulted_pte() 让 fault-around PTE 初始为 old,以便硬件 AF 驱动 active 迁移;x86 因 10 年前的 UnixBench ~6% 回退将该机制关闭,但这只影响 x86 本次优化的收益面
如何做
改动极简——mm/swap.c:folio_add_lru() 中 MGLRU 缺页分支把 folio_set_active(folio) 替换为 folio_mark_accessed(folio),仅 1 行:
if (lru_gen_enabled() && !folio_test_unevictable(folio) &&
lru_gen_in_fault() && !(current->flags & PF_MEMALLOC))
- folio_set_active(folio);
+ folio_mark_accessed(folio);
语义差别在于:folio_set_active() 无条件给最高保护;folio_mark_accessed() 只在 folio 之前已被引用过时才提升代,否则只设 PG_referenced。这样 readahead 进来的、且在随后回收扫描 folio_check_references() 前从未被 PTE 真正触碰的 folio 不会被无谓保护;而真正被 CPU 访问的 folio 会通过硬件 access flag(arm64)把 PTE 从 old 翻成 young,folio_check_references() 在扫描时识别 young PTE 并把 folio 提升代数。作者坦率说明:这条路径的收益目前仅对 arm64 显著,因为 arch_wants_old_prefaulted_pte() 在 arm64 打开,x86 如果要复用需要在 arch/x86/include/asm/pgtable.h 打开同名 hook,但 10 年前报告的 x86 HW AF 成本是否还成立值得重新评估。
收益
作者提供了完整、可复现的 fio 压测脚本:400 MB memcg 限额内,用 fio 3.42 以 --zonemode=strided --zonesize=4K --zonerange=64K 对 600 MB 文件做"每 64 KB 读 4 KB"的稀疏访问,模拟大量被 fault-around 带入但从不访问的 PTE。
| | |
|---|
| File refault delta(60 秒窗口) | | |
| | |
refault 降到 0、带宽约 87 倍提升,直接验证了预期的因果链:不再把预取未访问页固定在年轻代 → 真实工作集不被错误驱逐 → 工作集命中率回到正常水平。作者明确致谢 Lance Yang 和 Xueyuan Chen 提供了测试,并借机呼吁 x86 社区基于现代微架构重新评估 HW AF 的成本——这条"顺带讨论"本身也让该 1 行 patch 成为 mm/mglru 的一个策略性改动。
9. mm/damon/modules: detect and use fresh status
系列:[PATCH v3 0/3] mm/damon/modules: detect and use fresh status作者: SeongJae Park版本: v3(3 个 patch)
背景
DAMON 的三个模块化前端——DAMON_RECLAIM(主动回收冷页)、DAMON_LRU_SORT(LRU 链表排序)、DAMON_STAT(数据访问统计)——都通过 module_param 的 enabled 和 kdamond_pid 参数对外暴露自己的 kdamond 工作线程运行状态。长期以来,这些参数值都是"缓存式"维护:只有当用户显式向 enabled 写入 Y/N 触发 damon_{reclaim,lru_sort}_turn() 或 damon_stat_{start,stop}() 成功时才更新缓存。问题是 kdamond 还会因其他事件被"隐式"停止,这种状态转换路径无法在写入者侧被捕获。
解决的问题
- 状态参数返回陈旧值:kdamond 实际已退出,但
cat enabled 仍显示 Y、cat kdamond_pid 仍显示已不存在的 PID,用户被误导。 - DAMON_RECLAIM / DAMON_LRU_SORT 无法在系统重启前重新启动 kdamond:因为两者都根据缓存的
enabled 值做幂等判定——既然缓存认为已启用,再次写 Y 就被提前 return 0,真正的 damon_start() 永远得不到执行。Cover letter 给出了可复现路径:echo Y > enabled(启动)→ echo 3 > addr_unit; echo Y > commit_inputs(用非法输入触发 damon_commit_ctx() 失败,kdamond 自行退出)→ 之后即便修正参数、再次 echo Y > enabled 也无法拉起 kdamond。 - 触发隐式停止的三类事件:①
ctx->regions_score_histogram 在 kdamond 初始化阶段分配失败;②damon_commit_ctx() 因用户输入非法而失败;③damon_commit_ctx() 自身的内部分配失败。DAMON_STAT 只受第①类影响,但因为它对 enabled 是"强制更新"语义,尚可重启,仅有显示陈旧的问题。 - 对应的 Fixes 标签与稳定版回溯:分别回溯到
e035c280f6df(5.19.x,DAMON_RECLAIM 支持在线输入更新时引入)、40e983cca927(6.0.x,DAMON_LRU_SORT 首次引入)、369c415e6073(6.17.x,DAMON_STAT 模块引入)。
如何做
该系列摒弃了 v2 之前"在每个停止路径上同步更新缓存变量"的思路——作者自评为"跟踪所有事件是困难的"——改为"按需探测真实状态"(detect and use fresh status on demand)。核心实现是把 module_param 的 .get 回调从通用的 param_get_bool / 直接读变量替换成自定义的 load 回调,在回调里直接查询 DAMON 核心 API:
- 新增静态 helper
damon_reclaim_enabled() / damon_lru_sort_enabled() / damon_stat_enabled(),内部判断 ctx 非空后调用 damon_is_running(ctx)(声明于 include/linux/damon.h:989)。 - 新增
*_enabled_load() 以 damon_is_running() 的实时结果打印 Y 或 N;在 DAMON_RECLAIM/LRU_SORT 里还新增 *_kdamond_pid_load(),调用 damon_kdamond_pid(ctx) 实时返回 PID,取值 <0 归一为 -1。 - store 侧相应简化:
enabled_store 把 damon_reclaim_turn()/damon_lru_sort_turn() 中 kdamond_pid = damon_kdamond_pid(ctx) 与 kdamond_pid = -1 的手工同步全部删除;damon_{reclaim,lru_sort}_enabled_store() 的幂等判定也从 is_enabled == enable 改为 damon_{reclaim,lru_sort}_enabled() == enabled,这样一旦 kdamond 已隐式退出,写入 Y 就能真正进入 turn(true) 路径并拉起新 kdamond。 - 由于
kdamond_pid 原先是 0400(只读)的纯变量参数,改为 module_param_cb 后需要提供 .set 回调——作者增加了空实现 *_kdamond_pid_store() 以容忍内核命令行可能的写入,避免 boot 阶段报错。 - DAMON_STAT 因为原本就"强制更新",简化幅度较小:仅需补一个
damon_stat_enabled_load(),并把 enabled_store 里 err 后回滚 enabled = false 的分支去掉。
v3 相对 v2 的关键演进如封面 changelog 所述:v2 仍是尝试在所有失败路径上补齐状态同步,包括 damon_{lru_sort,reclaim}_turn() 的 N 路径 fallback、apply_parameters() 失败时重置 enabled/kdamond_pid 等;v3 彻底改为"按需探测"的思路,代码净增量从 v2 的 +104/−52 扩大到 +130/−70,但消除了补丁作者和复审者都认为脆弱的"事件追赶"逻辑。
收益
作者未提供 benchmark 数据(这是功能正确性修复,不涉及性能),但给出明确的行为收益:
cat enabled 与 cat kdamond_pid 永远反映 kdamond 真实运行状态,不再因为三类隐式停止事件而陈旧- DAMON_RECLAIM/LRU_SORT 在隐式停止后可通过
echo Y > enabled 正常重启,无需系统重启 - 将"多处事件同步一个缓存"重构为"单点按需查询核心 API",大幅降低后续维护成本——未来再有新的 kdamond 退出路径(如新的 commit 校验失败场景)也不需要每个模块重复打补丁
- 三个 patch 均带
Cc: stable@vger.kernel.org 分别回溯至 5.19.x / 6.0.x / 6.17.x 全部受影响的长期支持分支
10. mm: prepare anon_vma before swapin rmap
系列:[PATCH] mm: prepare anon_vma before swapin rmap作者: ZhengYuan Huang版本: v1(单 patch)
背景
自 commit a373baed5a9d("mm: delay the check for a NULL anon_vma")将 anon_vma 的按需准备从通用 fault 路径下沉到各个具体的 fault handler 之后,所有会向 VMA 安装匿名 rmap 状态的 handler 都必须自行调用 vmf_anon_prepare()。mm/memory.c 中 do_anonymous_page()(line 3854)和 do_cow_fault()(line 5372)都已正确调用,wp_page_copy() 路径(line 5913)也做了处理。唯独 do_swap_page() 被遗漏——虽然它最终会调用 folio_add_new_anon_rmap() 或 folio_add_anon_rmap_ptes() 把换入的页重新建立匿名映射,却从未先调用 vmf_anon_prepare() 确保 vma->anon_vma 非空。
解决的问题
- VMA-lock 快路径下的内核 BUG:当缺页以
FAULT_FLAG_VMA_LOCK 方式进入 do_swap_page(),而此 VMA 从未挂过匿名页(vma->anon_vma == NULL),一路调到 __folio_set_anon() 就会命中 mm/rmap.c:1468 的 BUG_ON(!anon_vma)(以及 line 1445 的 VM_BUG_ON_VMA(!anon_vma, vma)),触发 invalid opcode 并让内核宕机。 - 可触发场景:
madvise(MADV_HWPOISON) 经 madvise_inject_error() → get_user_pages_fast() → gup_fast_fallback() → __get_user_pages() → faultin_page() → handle_mm_fault() 把一个交换条目再 fault 回来,在 6.18 上可以确定性复现(作者原话:"I can reproduce this issue deterministically on v6.18")。关键调用栈: do_swap_page+0x14c5 → handle_pte_fault/__handle_mm_faultgup_fast_fallback+0x8a3 → get_user_pages_fastmadvise_inject_error → madvise_do_behavior → __x64_sys_madvise
- 回归根因:
Fixes: a373baed5a9d ("mm: delay the check for a NULL anon_vma") 漏补 swap 路径。作者坦言在 next-20260415 上无法复现,但代码审查表明"do_swap_page() 仍然可以不经 vmf_anon_prepare() 就到达 folio_add_new_anon_rmap()/folio_add_anon_rmap_ptes(),而 __folio_set_anon() 仍会在 vma->anon_vma 为 NULL 时 BUG_ON",属于潜在仍存在的 race。
如何做
补丁在 mm/memory.c 的 do_swap_page() 中,确认 PTE 指向一个真正的 swap entry、且处理完 device-private migrate/hwpoison/marker 等非 swap 分支之后(即 line 4850 附近的 goto out 之后),在获取 swap_device 之前插入 5 行:
/* Swapin installs anonymous rmap state into the faulting VMA. */
ret = vmf_anon_prepare(vmf);
if (ret)
goto out;
vmf_anon_prepare() 已经内置了 VMA-lock 下的重试规则(__vmf_anon_prepare(),mm/memory.c:3802):若 vma->anon_vma 已存在直接 likely 返回 0;若当前仅持 per-VMA lock,它会尝试 mmap_read_trylock(),失败则返回 VM_FAULT_RETRY 让上层降级到持 mmap_lock 重放,从而既不破坏 VMA-lock 快路径,又能在需要时安全访问相邻 VMA 以合并 anon_vma。作者强调 "when anon_vma is already present this stays a single likely branch in the swap fault hot path"——对 swapin 热路径只多一次 branch 判断。位置选择在 hwpoison/device-private/marker 等特殊 entry 的 goto out 之后,避免给这些非匿名映射路径做无意义的准备工作。
收益
作者未提供量化性能数据(这是一个崩溃修复),但给出具体的正确性收益:
- 消除
madvise(MADV_HWPOISON) 与 gup_fast_fallback 组合下确定性触发的内核 BUG,保护内核免于 invalid opcode 宕机 - 补齐
a373baed5a9d 引入的类型不变式缺口——让所有安装匿名 rmap 状态的 fault handler 统一遵守"先 vmf_anon_prepare() 后建 rmap"的契约,与 do_anonymous_page() / do_cow_fault() / wp_page_copy() 的处理保持一致 - 对已持有
anon_vma 的常见 swap 命中路径仅新增一次 likely(vma->anon_vma) 快速返回,对 swapin 吞吐影响可以忽略 - 带
Fixes: 标签方便 stable 维护者回溯至所有继承 delay-NULL-anon_vma-check 行为的内核版本
总结
新机制 / 新接口
| | |
|---|
| khugepaged mTHP support | khugepaged 首次支持后台折叠 mTHP,bitmap+栈的 O(N log N) 搜索,新增 per-order stats/tracepoint |
| Remove RO-THP for non-large-folio FSes | 删除 CONFIG_READ_ONLY_THP_FOR_FS,所有 large folio FS 获得 khugepaged/MADV_COLLAPSE 能力,address_space 缩 8 字节 |
| shmem large folio for internal mount | internal shmem mount 一律 mapping_set_large_folios(),order 完全交给 sysfs 动态裁剪 |
| page_owner filter infrastructure | debugfs 新增 print_mode(stack handle 替代完整栈)与 NUMA nid 过滤,可扩展 filter 框架 |
| swap_ops vtable | 引入 struct swap_ops 替代 SWP_FS_OPS/SWP_SYNCHRONOUS_IO if/else,page_io.c 改名 swap_io.c |
| rust helpers for Tyr driver | 补齐 SZ_4G、Mm::task_size、Guard::find,解除 Rust DRM 驱动 Tyr 合入前置依赖 |
| MM selftests CI friendly | 所有 HugeTLB 测试自行 setup/teardown,kselftest TAP13 化 42 个文件,run_vmtests.sh 删 128 行 |
性能优化
| | |
|---|
| mglru folio_mark_accessed in PF | fio 稀疏访问(400MB memcg):refault 12,883,855 → 0;读带宽 58.5 → 5,078 MiB/s(约 87x) |
Bug Fix
| | |
|---|
| damon/modules fresh status | DAMON_RECLAIM/LRU_SORT 隐式停止后可正常重启,enabled/kdamond_pid 永远反映实际状态。Fixes: e035c280f6df(5.19)、40e983cca927(6.0)、369c415e6073(6.17),Cc stable |
| anon_vma before swapin rmap | 修复 MADV_HWPOISON + gup_fast_fallback 在 VMA-lock 快路径下确定性触发 rmap.c:1468 BUG_ON(!anon_vma) 内核宕机。Fixes: a373baed5a9d |