目录
- mm/swap: 使用 swap_ops 方法表重构 swap 设备接口
- slab: 编译器辅助的基于类型的 slab cache 分区
- s390/mm: 在 lazy MMU 模式下批量化 PTE 更新
- HQ-spinlock: NUMA 感知的分层队列自旋锁
- mm/huge_memory: 优化需要拆分 huge PMD 时的迁移路径
- mm/memory-failure: 为不可恢复页面增加 panic 选项
- DAMON_RECLAIM/LRU_SORT 默认监控全部系统物理内存
- DAMON_STAT 新增 kdamond_pid 模块参数
- mm: 修复 vmemmap 优化的统计与初始化问题
- mm/vmalloc: 防止 decay_va_pool_node() 中的 RCU stall
- PM: hibernate: 修复 swap pin 在重设失败时的保持
- mm/memblock: 修复 reserve_mem_release_by_name() 的 off-by-one 页面泄漏
1. mm/swap: 使用 swap_ops 方法表重构 swap 设备接口
系列:[PATCH v3 0/3] mm/swap: use swap_ops to register swap device's methods作者: Baoquan He(Red Hat),Co-developed-by: Barry Song版本: v3(3个patch)
背景
Linux 内核 swap 子系统目前支持三种 swap 后端设备类型:基于文件系统的 swap(bdev_fs)、同步块设备(bdev_sync)和异步块设备(bdev_async)。每种设备类型在执行 read 和 write 操作时需要调用不同的底层函数。在现有实现中,这些分发逻辑分散在 swap_read_folio() 和 __swap_writepage() 等函数内部,通过多层 if/else 条件判断 sis->flags 中的 SWP_FS_OPS 和 SWP_SYNCHRONOUS_IO 标志位来选择对应的处理函数。此外,源文件 mm/page_io.c 的命名也具有误导性——该文件中的代码仅涉及 swap I/O,与通用的 page I/O 无关。
解决的问题
- 读写路径中的条件分发逻辑重复且分散,
swap_read_folio() 和 __swap_writepage() 内部各有一套独立的 if/else 链式判断,增加了代码维护成本 - 源文件名
mm/page_io.c 不能准确反映其内容(纯 swap I/O),容易造成混淆 - 写操作相关函数名(如
swap_writepage_fs)仍使用 "page" 命名,与对应的读操作函数名(如 swap_read_folio_fs)不一致 - 未来添加新的 swap 设备类型时,需要在多处条件判断中插入新分支,扩展性差
如何做
整个方案分三步实现,核心思想是引入 ops 方法表(operations table)模式,将运行时的条件分发转化为初始化时的一次性绑定。
首先(Patch 1),将 mm/page_io.c 重命名为 mm/swap_io.c,并同步更新 MAINTAINERS、mm/Makefile 和 mm/swap.h 中的引用。
核心改动在 Patch 2 中完成。定义了新的 struct swap_ops 方法表结构体,包含两个函数指针:
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 方法表实例:bdev_fs_swap_ops、bdev_sync_swap_ops 和 bdev_async_swap_ops。新增 init_swap_ops() 函数,在 swapon 系统调用中一次性选择并绑定正确的方法表。原来的 __swap_writepage() 函数被完全移除,其调用点直接替换为 sis->ops->write_folio() 调用。读路径 swap_read_folio() 中的三路 if/else 判断也被替换为单一的 sis->ops->read_folio() 调用。
最后(Patch 3),将写操作函数从 swap_writepage_* 重命名为 swap_write_folio_*,使其与读操作对应函数 swap_read_folio_* 命名保持一致。
收益
作者未提供性能数据。从代码逻辑推断,该系列的主要收益在于代码可维护性和扩展性:通过 ops 方法表模式,未来新增 swap 设备类型时只需定义一个新的 struct swap_ops 实例并在 init_swap_ops() 中增加一个分支即可,无需在多个读写路径中逐一添加条件判断。
2. slab: 编译器辅助的基于类型的 slab cache 分区
系列:[PATCH v2] slab: support for compiler-assisted type-based slab cache partitioning作者: Marco Elver(Google),Co-developed-by: Harry Yoo(Oracle)版本: v2(1个patch)
背景
Linux 内核在 6.6 版本引入了 CONFIG_RANDOM_KMALLOC_CACHES 安全加固机制,为通用 kmalloc 分配创建 16 个 slab cache 副本,每次分配时根据调用者地址(_RET_IP_)和 per-boot 随机种子进行哈希,将对象随机分配到不同的 cache 中。然而,这种随机化方案存在根本性的概率局限:cache 分配是基于调用地址而非对象类型的,攻击者可以通过在多台机器上重试攻击来绕过随机化。此外,同一类型的对象可能因来自不同调用站点而被分散到不同 cache 中,而不同类型的对象也可能因调用地址哈希冲突而被放入同一 cache。
解决的问题
RANDOM_KMALLOC_CACHES 的概率性隔离无法保证特定类型之间的确定性分离,攻击者有机会通过重试绕过- 无法区分包含指针的对象(pointer-containing objects)和不包含指针的纯数据对象(pointerless objects),而这种区分对于阻止一类重要的内存腐败利用至关重要
- 现有基础设施命名和接口与"随机化"概念强耦合,缺乏对其他分区策略的抽象
如何做
该 patch 的核心思想是利用 Clang 22 引入的 allocation token 编译器特性(__builtin_infer_alloc_token),将 slab cache 的选择从"基于调用地址的随机哈希"升级为"基于类型的确定性映射"。
在架构层面,将 CONFIG_RANDOM_KMALLOC_CACHES 泛化为 CONFIG_PARTITION_KMALLOC_CACHES 框架,提供两种可选模式:原有的 CONFIG_RANDOM_KMALLOC_CACHES(随机模式,仍为默认)和新增的 CONFIG_TYPED_KMALLOC_CACHES(类型模式,需要 Clang 22+)。
引入抽象类型 kmalloc_token_t,封装分区所需的 token 信息。核心宏 __kmalloc_token(...) 在类型模式下展开为 __builtin_infer_alloc_token(__VA_ARGS__)。编译器能识别 kmalloc(sizeof(T), ...)、(T *)kmalloc(...) 等常见模式,并返回一个基于类型名哈希的 token ID。
Clang 的默认 token ID 计算采用 "typehashpointersplit" 模式:ID 空间的上半部分保留给包含指针的类型,下半部分给不包含指针的类型。编译标志 -falloc-token-max=16 将 token 值限制在 0-15 的范围内。命名体系全面重构:RANDOM_KMALLOC_CACHES_NR 改为 PARTITION_KMALLOC_CACHES_NR,slab cache 名称从 kmalloc-rnd-XX- 改为 kmalloc-part-XX-。整个 kmalloc API 链都通过宏链透传 token 参数。当 CONFIG_PARTITION_KMALLOC_CACHES 未启用时,kmalloc_token_t 定义为空结构体,所有相关参数被完全优化掉。
收益
作者提供了 x86 defconfig 内核启动后的 /proc/slabinfo 分布数据:
编译器类型推断失败的分配站点仅 186 个(相比 RFC 版本的 966 个大幅减少)。安全收益方面,将包含指针的对象和不含指针的纯数据对象隔离到不同 slab cache 中,可以有效阻止攻击者通过溢出原始缓冲区来覆写相邻对象中的指针。
3. s390/mm: 在 lazy MMU 模式下批量化 PTE 更新
系列:[PATCH v2 0/6] s390/mm: Batch PTE updates in lazy MMU mode作者: Alexander Gordeev(IBM)版本: v2(6个patch)
背景
在 s390 架构上,修改一个有效的 PTE 之前通常需要执行 IPTE(Invalidate Page Table Entry)指令。IPTE 的一个显著缺点是"它可能发起一个 machine-wide quiesce state"(机器级静默状态),相当于一个昂贵的全局硬件锁。然而,IPTE 指令本身支持通过 "Additional Entries" 字段一次失效最多 256 个 PTE,这一硬件能力此前在内核中完全未被利用。
解决的问题
- 每次 PTE 更新都触发单独的 IPTE 指令,导致频繁的 CEC 级 quiesce 事件,尤其在多 LPAR 环境下严重影响性能
- 现有的 lazy MMU mode 接口是 context-independent 的,不向架构层传递页表范围信息
- s390 代码中存在大量直接解引用页表指针(如
*ptep)而未使用标准 accessor(如 ptep_get())的情况
如何做
通用层: Patch 1 新增 lazy_mmu_mode_enable_for_pte_range(mm, addr, end, ptep) 函数,替代原有的无参数 lazy_mmu_mode_enable(),向架构层传递地址空间和 PTE 范围信息。所有关键的 page-table walker 调用点均被更新(mm/mprotect.c、mm/madvise.c、mm/memory.c、mm/mremap.c、mm/vmalloc.c、fs/proc/task_mmu.c)。
s390 架构层: Patch 5 是核心实现,新增 arch/s390/mm/ipte_batch.c,引入 per-CPU 的 struct ipte_batch 数据结构。进入 lazy MMU 模式时,所有对 PTE 的修改写入缓存而非硬件页表。离开时,leave_ipte_batch() 扫描缓存中连续的有效 PTE 条目,通过 __ptep_ipte_range() 利用 IPTE 的 "Additional Entries" 字段一次性批量失效。
Patch 3 完成了 s390 全面的 ptep_get() 转换。Patch 6 引入 lazy_mmu 内核启动参数,基于 static_key 允许在启动时禁用 lazy MMU 模式以便调试。
收益
| |
|---|
mprotect() | ~15x |
munmap() | ~3x |
mremap() | ~28x |
在 4 个 LPAR 并行执行 mprotect() 的极端场景下,quiesce 事件减少了约 25 倍。fork() 出现了约 1.5x 的可测量退化。
4. HQ-spinlock: NUMA 感知的分层队列自旋锁
系列:[RFC PATCH v3 0/7] HQ-spinlock作者: Anatoly Stepanov, Nikita Fedorov(Huawei)版本: v3 / RFC(7个patch)
背景
在高竞争场景下,现有 Linux 内核的 qspinlock 实现在多 NUMA 节点系统上表现不佳,根本原因在于频繁且昂贵的跨 NUMA cacheline 传输——发生在 "contender enqueue" 和 "MCS handoff" 两个环节。此前 Oracle 的 CNA-lock 仅优化了 handoff 阶段的跨节点流量,入队阶段的跨节点流量未被解决,且在 x86 平台无明显收益。
解决的问题
- qspinlock 的 MCS handoff 在跨 NUMA 节点时产生大量 cache-line bouncing,严重限制高竞争吞吐量
- CNA-lock 仅优化 handoff 阶段,入队阶段未被解决
如何做
HQ-lock 是 cohort lock 和 queued spinlock 的混合体,采用两层竞争者组织架构。每个 NUMA 节点拥有独立的 FIFO 竞争者队列(struct numa_queue),多个活跃的 NUMA 队列通过单链表维护 FIFO 顺序。
锁传递分为 local handoff(同一 NUMA 队列内)和 remote handoff(不同 NUMA 队列间)。local handoff 优先执行,直到达到公平性阈值(hqlock_fairness_threshold,默认 1000)后执行 remote handoff。
为解决 struct qspinlock 大小约束,引入共享元数据池 meta_pool(struct lock_metadata 数组,大小 4096)。锁首次进入 slowpath 时通过 hash 查找可用槽位并绑定,释放时解绑。
v3 新增竞争检测机制:锁在 LOCK_MODE_QSPINLOCK 模式下跟踪 handoff 统计,只有当 general handoffs 超过 50 且 remote handoffs 超过 2 时才切换为 HQ 模式,确保低竞争场景无退化。
收益
Locktorture(单锁微基准):
| | |
|---|
| AMD EPYC 9654 (192 cores, 2 sockets) | | +186% |
| Kunpeng 920 (96 cores, 4 NUMA nodes) | | +158% |
应用层:
低竞争场景(< 8 threads)下无退化,HQ-spinlock 吞吐与 qspinlock 持平。
5. 在 mmap 同步预读中使用高阶 folio
系列:[RFC PATCH 0/2] Use high-order folios in mmap sync RA作者: Anatoly Stepanov(华为 / Huawei)版本: RFC(2个patch)
背景
当用户通过 mmap() 映射文件并以特定模式访问时,在某些访问模式下——例如应用逐块访问大文件,每块大小恰好等于 RA 窗口——程序永远不会触及被标记为 readahead 的页面,因此异步预读(async RA)无法启动。这导致 page cache 中只会被 0 阶(order-0,即单个 4KB 页面)的 folio 填满。当 fault_around_bytes 启用时,filemap_map_pages() 需要逐个处理大量小 folio,性能显著下降。在 ARM64 架构上,0 阶 folio 无法利用 contiguous PTE(contpte)硬件特性。
解决的问题
- 同步预读路径仅生成 0 阶 folio,导致后续
filemap_map_pages() 映射性能低下 - ARM64 上无法获得 contpte 覆盖,TLB 效率低
如何做
在 struct readahead_control 中新增 sync_mmap_order 字段。在 do_sync_mmap_readahead() 的 mmap read-around 分支中,通过 __ffs(fault_around_pages) 计算出 fault_around_bytes 对应的 folio 阶数。在 page_cache_ra_order() 中,new_order 的初始值变为 max(ra->order, ractl->sync_mmap_order),同时为不影响后续异步预读,仅当 ra->order >= ractl->sync_mmap_order 时才将 new_order 写回 ra->order。
Patch 1 还在 /proc/<pid>/smaps 中增加了 ContPTE(Rss) 字段,用于展示 ARM64 contpte 覆盖情况。
收益
测试场景:ARM64 上 mmap 一个 100MB 文件,以 4MB 步长逐块访问:
吞吐量提升约 3 倍,filemap_map_pages() 耗时降低约 **63%**。
6. mm/huge_memory: 优化需要拆分 huge PMD 时的迁移路径
系列:[PATCH 0/2] mm/huge_memory: optimize migration when huge PMD needs split作者: Wei Yang版本: v1(2个patch)
背景
在页面迁移流程中,当遇到 THP 的 PMD 映射需要被迁移时,try_to_migrate_one() 会调用 split_huge_pmd_locked() 并传入 freeze=true,将 huge PMD 拆分为多个 PTE 级别的 migration entry。拆分后,代码总是无条件地重启 page table walk,再逐个遍历所有 PTE。但如果 split_huge_pmd_locked() 已经成功将所有 PTE 设置为 migration entry,这次重启遍历完全是冗余的。
解决的问题
split_huge_pmd_locked() 成功将 PMD 拆分为 migration entry 后,try_to_migrate_one() 仍做了一次冗余的 page table walk
如何做
将 split_huge_pmd_locked() 和 __split_huge_pmd_locked() 的返回类型从 void 改为 bool。返回 true 表示 PMD 已成功拆分为 migration entry(freeze 成功),false 表示未产生 migration entry。在 try_to_migrate_one() 中,返回 true 时直接 page_vma_mapped_walk_done() 完成遍历并 break 退出,跳过冗余的重启遍历。Patch 2 新增 split_shared_pmd() 自测函数。
收益
作者未提供性能数据。从代码逻辑推断,对于一个标准的 PMD(覆盖 512 个 4KB 页面),该优化在 freeze 成功路径上省去了 512 次 page_vma_mapped_walk() 的迭代。在页面迁移密集的场景(NUMA balancing、compaction、memory hotplug)中可减少不必要的 CPU 开销。
7. 设备页面的 fault 时迁移
系列:[PATCH v8 0/5] Migrate on fault for device pages作者: Mika Penttila(Red Hat)版本: v8(5个patch)
背景
在 GPU 计算和异构内存场景中,设备驱动需要将系统内存页面迁移到设备私有内存。当前内核提供两个独立的 API:hmm_range_fault() 用于将 not present 的页面 fault in,migrate_vma_*() 用于执行实际的页面迁移。驱动同时完成 fault 和 migration 时,面临 2~3 次 page table walk 的开销。作者指出,"one page table walk could cost over 1000 cpu cycles on X86-64"。
解决的问题
- 设备驱动同时执行 fault 和 migration 时需要多次 page table walk
- 两个独立 API 维护各自的 page table walk 逻辑,代码重复
- 缺少统一的"一次 walk 同时完成 fault + migration"的机制
如何做
核心设计是将 migrate_vma_*() 的 "collecting" 逻辑统一到 HMM 的 page table walk 中。新增 HMM_PFN_REQ_MIGRATE 输入标志和 HMM_PFN_MIGRATE/HMM_PFN_COMPOUND 输出标志,以及 MIGRATE_VMA_FAULT 标志。migrate_hmm_range_setup() 负责将 HMM pfn 数组转换为 migrate_vma 所需的 src[] / dst[] 格式。
对 mm/hmm.c 进行大规模重构,在 PTE/PMD 遍历中就地插入 migration entry(代码从原来 migrate_device.c 中搬移)。migrate_vma_setup() 被改写为调用 hmm_range_fault() + migrate_hmm_range_setup()。原来约 530 行的 migrate_vma_collect_* walker 被完全删除。
驱动新使用模式:一次 hmm_range_fault() 完成 fault + migration entry 设置,再 migrate_hmm_range_setup() + migrate_vma_pages() + migrate_vma_finalize() 完成迁移。
收益
将原来需要 2~3 次 page table walk 的操作合并为 1 次,在真实 GPU 驱动工作负载中,每减少一次 walk 可节省超过 1000 CPU 周期(x86-64)。同时删除了约 530 行重复代码,统一了 HMM 和 migrate_device 的 page table walk 逻辑。
8. mm/memory-failure: 为不可恢复页面增加 panic 选项
系列:[PATCH v4 0/3] mm/memory-failure: add panic option for unrecoverable pages作者: Breno Leitao(Debian/Meta)版本: v4(3个patch)
背景
当硬件报告的内存错误命中内核自身使用的页面(slab 对象、page table、内核栈等)时,memory_failure() 无法进行恢复,只能将该页面标记为 "Ignored" 并继续运行。被损坏的数据仍留在内核地址空间中,后续访问将导致不可预测的崩溃。作者给出了一个真实案例:多比特 ECC 错误命中 dentry cache 的 slab 页面,67 秒后 d_lookup() 访问了中毒的缓存行触发崩溃。
解决的问题
- 不可恢复的内核页面内存错误被静默忽略,系统在未来某个不可预测时刻崩溃,crash dump 无法追溯到原始硬件错误
- reserved page 在
get_hwpoison_page() 失败时被错误归类
如何做
新增 sysctl vm.panic_on_unrecoverable_memory_failure(默认 0)。核心判定函数 panic_on_unrecoverable_mf() 在以下三个条件同时满足时触发 panic:sysctl 已启用、恢复结果为 MF_IGNORED、页面类型属于 MF_MSG_KERNEL / MF_MSG_KERNEL_HIGH_ORDER / MF_MSG_UNKNOWN 之一。MF_MSG_GET_HWPOISON 被刻意排除,避免因瞬态引用计数竞争产生误报。Patch 1 细化了 reserved page 的分类,Patch 3 添加了完整的 sysctl 文档。
收益
作者未提供性能数据。核心收益在于可诊断性:启用后,不可恢复的内核内存错误在第一时间触发 panic,将延迟数十秒的"无关路径崩溃"转变为即时的、可溯源的崩溃,显著降低大规模集群的故障排查成本。
9. DAMON_RECLAIM/LRU_SORT 默认监控全部系统物理内存
系列:[RFC PATCH 0/7] mm/damon/reclaim,lru_sort: monitor all system rams by default作者: SeongJae Park(DAMON maintainer)版本: RFC(7个patch)
背景
DAMON_RECLAIM 和 DAMON_LRU_SORT 当前的默认策略是选择系统中最大的单个 System RAM 资源作为监控区域。在 NUMA 系统上这会产生严重的盲区问题——例如双 NUMA node 各 500 GiB 的服务器,只有一个 node 被监控。
解决的问题
- 多 NUMA node 系统上默认仅监控最大单段 RAM,大量内存不在监控范围内
- DAMON_STAT 模块内部存在与核心代码重复的地址范围计算逻辑
如何做
新增核心函数 damon_set_region_system_rams_default(),通过 walk_system_ram_res(0, -1, ...) 遍历所有 System RAM 资源,记录第一个资源的起始地址和最后一个资源的结束地址,得到覆盖所有 System RAM 的地址范围。DAMON_RECLAIM、DAMON_LRU_SORT 和 DAMON_STAT 统一使用此函数。旧函数 damon_set_region_biggest_system_ram_default() 及 DAMON_STAT 中的重复代码被移除(净减少约 38 行)。
收益
作者未提供性能数据。预期收益:NUMA 系统不再遗漏任何 System RAM 区域;消除手动配置需求;由于 DAMON 的采样机制不会对非 RAM 地址产生实际探测,扩大地址范围的监控开销可以忽略。
10. DAMON_STAT 新增 kdamond_pid 模块参数
系列:[RFC PATCH 0/2] mm/damon/stat: add kdamond_pid parameter作者: SeongJae Park(DAMON maintainer)版本: RFC(2个patch)
背景
DAMON_RECLAIM 和 DAMON_LRU_SORT 都已提供 kdamond_pid 只读模块参数,但 DAMON_STAT 缺少此参数,导致接口不一致。
解决的问题
- DAMON_STAT 缺少
kdamond_pid,用户无法便捷获取其 kdamond 线程 PID
如何做
在 mm/damon/stat.c 中新增 static int kdamond_pid __read_mostly = -1; module_param(kdamond_pid, int, 0400);。在 damon_stat_start() 中获取 PID 并写入,在 damon_stat_stop() 中重置为 -1。Patch 2 更新文档。
收益
纯功能性改进,仅增加 20 行代码。用户可通过 /sys/module/damon_stat/parameters/kdamond_pid 直接读取 PID。
11. DAMON_RECLAIM 支持监控间隔自动调优
系列:[RFC PATCH 0/2] mm/damon/reclaim: support monitoring intervals auto-tuning作者: SeongJae Park(DAMON maintainer)版本: RFC(2个patch)
背景
DAMON 核心层已引入监控间隔自动调优机制,但 DAMON_RECLAIM 的模块参数接口尚不支持。该机制通过设定期望的访问事件观测比例(access_bp),自动调整采样和聚合间隔。
解决的问题
- DAMON_RECLAIM 不支持自动调优,用户必须手动选择固定间隔
如何做
新增布尔模块参数 autotune_monitoring_intervals(默认 false,可读写)。启用时覆盖用户设置的监控属性,填入一组精选的调优参数:5ms 初始采样间隔、100ms 初始聚合间隔、目标观测比例 0.4%、每 3 次聚合评估一次、采样间隔范围 5ms~10s。
收益
作者未提供性能数据。预期收益:访问密集期自动缩短间隔提高精度,稀疏期拉长间隔减少开销;降低配置门槛;运行时可通过 sysfs 动态切换。
12. userfaultfd: 匿名内存工作集跟踪
系列:[RFC 00/12] userfaultfd: working set tracking for VM guest memory作者: Kiryl Shutsemau(Meta)版本: RFC(12个patch)
背景
VMM 需要管理 guest 的内存生命周期:识别 working set、安全驱逐冷页、按需取回。对于匿名内存后端的 guest(KSM 跨 VM 去重所需),根本没有任何机制——清除一个匿名页面的 PTE 就意味着丢失该页面。对于 shmem 后端,冷页驱逐存在检测与驱逐之间的竞态窗口。
解决的问题
- NUMA balancing 和 userfaultfd 都使用 protnone PTE,两者在同一 VMA 上会冲突
如何做
在 userfaultfd 框架上构建完整的工作集跟踪方案,引入 5 个新的 UAPI 接口/特性:
**UFFD_FEATURE_MINOR_ANON**:扩展 minor fault 注册到匿名私有内存。
**UFFDIO_DEACTIVATE**:新增 ioctl,对匿名 VMA 通过 change_protection(MM_CP_UFFD_DEACTIVATE) 将 PTE 设为 protnone(保留 PFN 和页面内容),对 shmem/hugetlbfs 则 zap PTE(页面留在 page cache)。
UFFDIO_CONTINUE 匿名内存支持:PTE 级别新增 mfill_atomic_pte_continue_anon(),恢复 protnone PTE 的正常权限。PMD/THP 级别直接原地恢复。
protnone fault 拦截:在 protnone 分支中检查 userfaultfd_minor(vma),分发到 uffd minor fault 而非 NUMA balancing 路径。async 模式下 inline 恢复;sync 模式下通过 handle_userfault() 传递给 handler。
**UFFDIO_SET_MODE**:运行时切换 async/sync 模式。通过 mmap_write_lock 确保与 in-flight fault 串行化。
**PAGE_IS_UFFD_DEACTIVATED**:在 PAGEMAP_SCAN 中新增标志,供 VMM 批量检测冷页。
VMM 完整工作流:
UFFDIO_DEACTIVATE(all) -- async, no vCPU stalls
sleep(interval)
PAGEMAP_SCAN -- find cold pages
UFFDIO_SET_MODE(sync) -- block faults for eviction
pwrite + MADV_DONTNEED cold pages -- safe, faults block
UFFDIO_SET_MODE(async) -- resume tracking
收益
作者未提供性能 benchmark。预期收益:为匿名 guest 内存提供此前完全缺失的工作集跟踪能力(KSM 跨 VM 去重的前置条件);async 模式对 vCPU 零停顿;统一接口同时支持匿名和 shmem 后端;全部 73 个 uffd 单元测试通过。
13. mm: 修复 vmemmap 优化的统计与初始化问题
系列:[PATCH v2 0/6] mm: Fix vmemmap optimization accounting and initialization作者: Muchun Song(ByteDance)版本: v2(6个patch)
背景
vmemmap 优化用于减少复合页面的 struct page 元数据开销。该优化涉及 vmemmap_pages 计数器追踪、架构相关的页表同步、以及 ZONE_DEVICE 复合页面的 pageblock migratetype 初始化,在当前代码中这些层面存在多个 bug。
解决的问题
- vmemmap 统计下溢:错误路径中加减操作不对称
- DAX vmemmap 统计不准确:memory deactivation 路径缺少
pgmap 参数 ZONE_DEVICE 复合页面的 pageblock migratetype 初始化不完整
如何做
Patch 1-3 修复统计,将增减操作移至对称位置并传递 pgmap 参数。Patch 4-5 将 pgmap 传递到架构层 vmemmap_populate() 实现中(影响 arm64/loongarch/powerpc/riscv/s390/sparc/x86)。Patch 6 抽取 pageblock_migratetype_init_range() 函数,对整个 PFN 范围按 pageblock_nr_pages 步长遍历初始化。修改跨越 18 个文件。
收益
作者未提供性能数据。主要收益在于正确性:消除统计下溢、stale mapping 访问风险和 pageblock 初始化缺失。对 DAX/PMEM 和大页(尤其 1GB hugepage)场景尤为重要。
14. mm/vmalloc: 防止 decay_va_pool_node() 中的 RCU stall
系列:[PATCH] mm/vmalloc: Prevent RCU stall in decay_va_pool_node()作者: Sechang Lim版本: v1(1个patch)
背景
decay_va_pool_node() 在回收大量 vmap area 时长时间不让出 CPU,触发 RCU 自检测 stall。由 Syzkaller 发现。
解决的问题
- 大量 vmap area 回收时长时间独占 CPU,触发 RCU stall 警告(stall 持续约 6344 ticks)
如何做
在外层 for 循环底部添加 cond_resched()。此时 per-pool spinlock 已释放,外层 vmap_purge_lock 是 mutex(可睡眠锁),因此调度让出安全。标记 Fixes: 72210662c5a2。
收益
消除大量 vmap area 回收场景下的 RCU stall 风险。正常路径下 cond_resched() 开销可忽略(仅一次标志位检查)。
15. PM: hibernate: 修复 swap pin 在重设失败时的保持
系列:[PATCH] PM: hibernate: preserve uswsusp swap pin across SNAPSHOT_SET_SWAP_AREA re-set failures作者: DaeMyung Kang版本: v1(1个patch)
背景
snapshot_set_swap_area() 在重设路径上先无条件 unpin 旧设备再 pin 新设备,若新 pin 失败则 session 将没有任何 swap 设备被 pin,重新打开了原始 commit 要修复的 swapoff 竞态。
解决的问题
- 失败路径上 swap pin 被丢弃,重新打开 swapoff 竞态条件
- 先 unpin 后 pin 的两步序列存在观察窗口
如何做
引入 repin_hibernation_swap_type(old_type, device, offset),在单次 swap_lock 获取内原子完成 pin 转移。任何失败路径都不修改任何 si 的标志,旧的 pin 被保留。调用侧使用局部变量暂存返回值,仅在成功时 commit 到 data->swap。
收益
作者提供了完整的测试方案:修复前 bogus 重设后 swapoff 成功(BUG),修复后返回 EBUSY(FIXED)。
16. mm/memblock: 修复 reserve_mem_release_by_name() 的 off-by-one 页面泄漏
系列:[PATCH v2] mm/memblock: fix off-by-one page leak in reserve_mem_release_by_name()作者: DaeMyung Kang版本: v2(1个patch)
背景
free_reserved_area() 的 end 参数是排他边界(exclusive end)。reserve_mem_release_by_name() 使用 start + map->size - 1(inclusive end),导致最后一个页面被 end & PAGE_MASK 向下对齐时丢弃。
解决的问题
- 每次释放保留内存都泄漏最后一个页面(通常 4KB)
如何做
一行修复:end = start + map->size - 1 改为 end = start + map->size。标记 Fixes: 74e2498ccf7b 并抄送 stable。
收益
消除每次调用泄漏一个物理页面的 bug。已推荐 backport 到 stable 内核。
总结
新机制 / 新接口
| | |
|---|
| mm/swap: swap_ops | ops 方法表重构 swap 设备接口,提升扩展性 |
| slab: typed cache partitioning | 基于 Clang 22 的类型推断实现确定性 slab cache 隔离,加固堆安全 |
| memory-failure: panic option | 新增 sysctl 在不可恢复内存错误时立即 panic 获取 crash dump |
| DAMON: monitor all system rams | DAMON_RECLAIM/LRU_SORT 默认监控全部 System RAM,消除 NUMA 盲区 |
| DAMON_STAT: kdamond_pid | 统一 DAMON 模块家族的 kdamond PID 暴露接口 |
| DAMON_RECLAIM: auto-tuning | |
| userfaultfd: working set tracking | 为匿名 guest 内存提供完整的工作集跟踪能力,5 个新 UAPI |
性能优化
| | |
|---|
| s390/mm: Batch PTE | mprotect ~15x, mremap ~28x, quiesce 事件减少 ~25x |
| HQ-spinlock | locktorture +186%, memcached +10%, nginx +78% |
| high-order folios in sync RA | mmap 吞吐 ~3x, filemap_map_pages -63%, contpte 96% |
| huge_memory: optimize migration | freeze 成功时跳过 512 次 PTE 冗余遍历 |
| Migrate on fault for device pages | 2~3 次 page table walk 合并为 1 次,每次节省 >1000 cycles |
Bug Fix
| | |
|---|
| vmemmap optimization fixes | 统计下溢 + 7 架构页表同步 + pageblock 初始化修复 |
| vmalloc: RCU stall | Fixes: 72210662c5a2,Syzkaller 发现的 RCU stall |
| hibernate: swap pin | Fixes: 5b2b0c6e4577,修复 swapoff 竞态重新打开 |
| memblock: off-by-one | Fixes: 74e2498ccf7b,每次释放泄漏一页,Cc: stable |