系列:[PATCH 0/6] psi: slightly improve performance of psi作者:Luka Bai版本:v1(6 个 patch)
PSI(Pressure Stall Information)是 Linux 内核中用于监控系统资源压力的子系统,它跟踪 CPU、内存和 IO 的"失速"(stall)时间。PSI 的回调函数分布在所有常见的调度热路径中——psi_task_switch 和 psi_task_change 作为 task_switch、enqueue、dequeue 的一部分被频繁调用,其中 psi_group_change 是最热的回调。在 8 核 16GB 内存的双 NUMA 节点机器上,通过 hackbench -s 512 -P -g 10 -f 30 -l 1000 --pipe 测试并通过 perf 观测,PSI 消耗了 4.3% 的 CPU,对实际工作负载造成可观测的影响。
psi_group_change 中,对每个 parent cgroup 都重复访问 cpu_curr(cpu)->in_memstall,造成不必要的 cacheline 停顿task->pid 与 in_memstall 等 PSI 变量不在同一 cacheline,导致额外 cacheline stallstate_mask 未变化时仍无条件调用 record_times,浪费 CPU 周期psi_bug 变量冗余本系列从三个维度系统性地削减 PSI 热路径开销:减少热路径内存访问、优化 cacheline 布局、精简控制流。
减少热路径内存访问:将 curr_in_memstall 的计算从 psi_group_change 内部(每次遍历 parent cgroup 都通过 rq->curr 间接访问)提升到外层 psi_task_switch 中计算一次后作为参数传入。在 psi_task_switch 上下文中,next 就是即将在 CPU 上运行的任务,直接用 next->in_memstall 替代 cpu_curr(cpu)->in_memstall,省略访问 runqueue 的开销。同时,当 state_mask 未实际变化时跳过 record_times 调用——作者说明这不会影响最终结果,因为 get_recent_times() 在获取 PSI 时间时总会计算剩余时间。
优化 cacheline 布局:将 psi_flags(仅 5 bit)从独立的 unsigned int 移入 task_struct 的 bitfield 区域,与 in_memstall 和新增的 need_psi bit 放在同一 cacheline。作者解释这不会增大 task_struct,反而因为消除了原本独立存放的 psi_flags(unsigned int)而缩小了结构体。引入 prefetch_and_get_groupc() 辅助函数,在访问当前 cgroup 时对 parent cgroup 的 pcpu 数据执行 prefetchw(),补偿链表遍历时硬件预取器的不足。
精简控制流:为默认 hierarchy 的 root cgroup 关联 psi_system psi group(原本 cgroup_psi() 需通过 cgroup_ino(cgrp) == 1 特殊判断),消除了条件分支。用 printk_deferred_once 替代 psi_bug 变量。将 test_states() 中最可能触发的 NR_RUNNING 检查提前,并添加 likely/unlikely 标注。
在相同 8 核 16GB 双 NUMA 节点机器上,hackbench 测试中 PSI 的 CPU 占用:
| -20.9% |
系列:[RFC PATCH v2 00/28] mm/damon: introduce data attributes monitoring作者:SeongJae Park (SJ)版本:RFC v2(28 个 patch)
DAMON 最初作为"数据访问监测器"(Data Access MONitor)引入,后来扩展了基于访问模式的自动系统操作(DAMOS),但监测部分始终局限于数据访问(access)本身。用户需要更全面的信息:开发人员想知道 DAMON 发现的热/冷区域中有多少是大页(huge page)支撑的;运行多租户负载的用户想知道热/冷区域分别属于哪些 cgroup。6.14 内核中合入了一个基于 DAMOS 的 page-level 属性监测方案,能提供精确的 folio 级别统计,但其开销与内存大小成正比,无法在生产环境始终开启。
核心思路是复用 DAMON 已有的访问采样基础设施来实现数据属性采样,在访问检查的同一次采样中以极低成本附加属性判断。
引入 struct damon_probe 表示数据属性探测器,可安装到 damon_ctx 的 ctx->probes 链表上。每个 probe 内包含一个 struct damon_filter 链表,filter 结构与 damos_filter 相似但专用于监测目的,支持 DAMON_FILTER_TYPE_ANON 和 DAMON_FILTER_TYPE_MEMCG 两种类型。当前限制最多 4 个 data probe。
采样统计方面,每个 damon_region 增加 probe_hits[] 数组记录每个 probe 的阳性采样次数。在 kdamond_fn() 中紧接 check_accesses() 之后调用新增的 damon_ops->apply_probes() 回调。对 MEMCG filter,在 RCU read lock 保护下调用 folio_memcg_check() + mem_cgroup_id() 进行比对。区间聚合时 kdamond_reset_aggregated() 在重置 nr_accesses 的同时将 probe_hits[] 归零。
用户接口方面,sysfs 提供 /sys/kernel/mm/damon/admin/kdamonds/<N>/contexts/<C>/monitoring_attrs/probes/ 层级用于配置,tried_regions/<R>/probes/<P>/hits 文件返回 PCR(probe-positive count)。tracefs 新增 damon_aggregated_v2 tracepoint,通过 __dynamic_array 编码 probe_hits。
作者未提供性能数据。从代码逻辑推断:
系列:[PATCH v2] mm/damon/vaddr: attempt per-vma lock during page table walk作者:Kefeng Wang版本:v2(单 patch)
DAMON 的虚拟地址空间(vaddr)操作集在对目标进程进行采样时需要遍历页表,当前实现每次采样都获取 mmap_read_lock(mm->mmap_lock 的读锁)。由于 DAMON 以固定频率执行采样,且 mmap_read_lock 是进程级的大锁,在高并发场景下(如多线程进程频繁进行 mmap/munmap 操作),DAMON 的频繁获取会引发不必要的锁竞争。
damon_va_young() 和 damon_va_mkold() 高频调用时,每次获取进程级 mmap_lock 与用户空间的 mmap/munmap 操作产生竞争damos_va_migrate 和 damos_va_stat 同样受益于 per-vma lock新增 damon_va_walk_page_range() 函数作为统一入口,实现"先尝试 per-vma lock,失败则回退到 mmap_read_lock"的策略:
使用 lock_vma_under_rcu() 在 RCU 读锁保护下尝试获取目标地址所在 VMA 的 per-vma lock。成功获取后检查采样范围是否完全落在该 VMA 内(DAMON 的 young/mkold 采样范围通常为单页,几乎总能命中 per-vma lock 路径)。对 VM_PFNMAP VMA(如 GPU MMIO 映射,没有 struct page 支撑)则回退到 mmap_read_lock 路径。统一了四个调用点(damon_va_mkold、damon_va_young、damos_va_migrate、damos_va_stat)的页表遍历逻辑,消除了重复的锁样板代码。
作者未提供性能数据。从代码逻辑推断:
mmap_lockmmap_read_lock系列:[PATCH 0/2] Insert instead of copy pages into shmem when shrinking作者:Thomas Hellstrom版本:v1(2 个 patch)
TTM(Translation Table Manager)是 DRM 子系统中管理 GPU 显存的缓冲对象管理器。为了支持 uncached 或 write-combined 映射的页面池,TTM 不直接使用 shmem 作为缓冲对象内存,而是在 shrink(收缩回收)时将页面内容备份到 shmem 对象中。当前流程是"分配 + 拷贝":先通过 shmem_read_folio_gfp() 分配新的 shmem folio,再用 copy_highpage() 拷贝。对高阶页面(如 order-2 4-page 合页),这需要在释放目标页面之前额外分配一整块同等大小的高阶内存——在内存紧张(恰是触发 shrink 的时机)时极不合理。
CONFIG_THP_SWAP 未被利用:即使内核允许透明大页整体换出,TTM 的 per-page 拷贝也使其无法生效核心变更是提供新的 shmem 接口 shmem_insert_folio(),实现零拷贝插入——直接将调用者拥有的 isolated folio 转移给 shmem 的 page cache。
该接口要求 folio 不在 LRU 上、已解锁、引用计数恰好为 1、无页表映射、folio->mapping == NULL。内部执行流程:可选的 compound promotion(prep_compound_page())→ 标记 swapbacked + uptodate → memcg 记账(mem_cgroup_charge())→ 插入 page cache(shmem_add_to_page_cache(),遇冲突先 shmem_truncate_range())→ inode 块记账 → LRU 或 writeback(若 writeback=true 则立即 shmem_writeout() 换出,否则加入 LRU 等待常规回收)。
新增 undo_compound_page() 用于失败回滚时撤销 prep_compound_page() 的效果。TTM 侧将 ttm_backup_backup_page() 替换为 ttm_backup_insert_folio(),shrink 循环以 compound page 为粒度遍历,替代了原有的逐页 alloc+copy 流程。
作者未提供性能数据。从代码逻辑推断:
系列:[PATCH RFC 0/5] memcg: dma-buf per-cgroup accounting via pid_fd作者:Albert Esteve,T.J. Mercier版本:RFC v1(5 个 patch)
dma-buf 是 Linux 图形和媒体子系统各组件间共享缓冲区的核心机制。目前只能通过 DMABUF_SYSFS_STATS 查看占用,该接口在创建/移除 sysfs 节点时会竞争 kernfs_rwsem 锁。在嵌入式平台上,中央守护进程为多个客户端分配 dma-buf 时,内存开销都记在分配器的 cgroup 上,客户端自身的内存限制形同虚设。T.J. Mercier 早期的方案试图通过 binder 感知的 charge transfer 解决,但这将记账与 binder IPC 紧密耦合。
DMABUF_SYSFS_STATS 的 kernfs_rwsem 锁竞争__GFP_ACCOUNT 与 MEMCG_DMABUF 的双重记账方案分层叠加在 memcg、dma-buf 和 LSM 三个子系统之上。
memcg 基础设施:新增 MEMCG_DMABUF 统计项,以 dmabuf 作为 memory.stat 中的键对外暴露。在 struct dma_buf 中新增 struct mem_cgroup *memcg 字段。提供 __mem_cgroup_charge_dmabuf()/__mem_cgroup_uncharge_dmabuf()。
通过 pidfd 显式归属:在 DMA_HEAP_IOCTL_ALLOC 中检查新增的 charge_pid_fd 字段(UAPI 新增字段,位于现有 heap_flags 之后)。若 charge_pid_fd 非零,通过 pidfd_get_task() 获取目标任务,再通过 get_mem_cgroup_from_mm(task->mm) 解析出其 memcg。dma_heap_buffer_alloc() 调用 mem_cgroup_charge_dmabuf() 将 dmabuf->memcg 设为目标 cgroup。若 charge_pid_fd 为 0,回退到向调用者 cgroup 记账。同时移除 system_heap.c 中的 __GFP_ACCOUNT 消除双重记账。
安全控制:新增 security_dma_heap_alloc(from, to) LSM 钩子,接收分配进程和目标进程的凭据。SELinux 实现利用 avc_has_perm() 使用新的 dma_heap 对象类和 charge_to 权限,使策略编写者可表达精准的跨 cgroup 记账授权规则。selftest 覆盖 pidfd 自引用、目标指向和端到端跨 cgroup 记账验证。
作者未提供性能数据。从代码逻辑推断:
MEMCG_DMABUF 计数器避免了 DMABUF_SYSFS_STATS 的 kernfs_rwsem 竞争,天然集成到 cgroup 内存层级MEMCG_DMABUF 单一路径系列:[PATCH v5] dma-contiguous: add kconfig option to setup numa cma area if not configured explicitly作者:Feng Tang版本:v5(单个 patch)
在多 NUMA 节点系统上,CONFIG_DMA_NUMA_CMA 允许为每个节点配置独立的 CMA(Contiguous Memory Allocator)区域,使设备可通过 dma_alloc_coherent() 就近获取节点本地内存。然而来自一台多 NUMA arm64 服务器的报告指出:当 IOMMU 禁用时,dma_alloc_coherent() 总是从节点 0 返回内存,即便设备挂载在其他节点上;而当 IOMMU 开启时,同样的 API 却能获取本地 DMA 内存。根本原因在于系统仅有一块默认 64MB CMA 保留区域(位于节点 0),且缺乏内核经验的用户不知道需要显式配置 per-numa CMA。
新增布尔型 kconfig 选项 CONFIG_CMA_SIZE_PERNUMA(默认 default y),在 dma_numa_cma_reserve() 中当四个条件同时满足时自动为每个节点分配与默认 CMA 相同大小的区域:(1)编译期 CONFIG_CMA_SIZE_PERNUMA=y;(2)用户未显式配置(!numa_cma_configured);(3)默认 CMA 区域存在;(4)nr_online_nodes > 1。
关键改动是将 dma_numa_cma_reserve() 的调用时机从 dma_contiguous_reserve() 开头移到默认 CMA 创建完成之后,使 cma_get_size(dma_contiguous_default_area) 能正确获取大小。单 NUMA 节点的嵌入式设备不受影响(nr_online_nodes > 1 条件),用户仍可通过内核命令行 numa_cma=0:0 覆盖。
作者未提供性能数据。从代码逻辑推断:
系列:[PATCH] mm/memory: avoid unnecessary #PF on mTHP allocation race作者:Wandun Chen版本:v1(1 个 patch)
在 do_anonymous_page() 中,当发生匿名页缺页异常时,内核尝试分配一个 mTHP(multi-size Transparent Huge Page)folio。分配后通过 pte_range_none() 检查目标 PTE 范围是否完全为空,如果不是则释放 folio 并返回。这个逻辑假定如果范围内有任何 PTE 已被填充,则该地址的缺页也已经被处理完毕。
当两个线程同时访问同一个 mTHP 范围的不同地址时,存在竞态条件。CPU 1 先在偏移 Y 处安装了 zero_pfn PTE,CPU 0 随后获取 PTE 锁并调用 pte_range_none(),因 Y 处已被填充而释放 folio 返回。但实际上 CPU 0 负责的 X 地址的 PTE 仍然是 pte_none(),缺页实际未被处理。作者提供的 reproducer 显示 writer 产生了 1951 次 minor fault(预期 1024 次),多出 927 次(约 90%)即为不必要的重复缺页。
在 do_anonymous_page() 中新增重试逻辑。pte_range_none() 返回 false 后,计算 fault_offset = (vmf->address - addr) >> PAGE_SHIFT,检查本次缺页地址对应的 PTE 是否确实已被填充。如果已被填充(说明已被其他线程处理),正常释放 folio 返回;否则设置 should_retry = true,释放 PTE 锁、put folio,通过 goto retry 回到 alloc_anon_folio() 重试。重试时 alloc_anon_folio() 会重新检查 pte_range_none() 并可能回退到更小的 order,不会产生无限循环。
系列:[PATCH v3] zram: fix use-after-free in zram_writeback_endio作者:Richard Chang版本:v3(1 个 patch)
zram 的 writeback 功能通过 bio 提交写回请求,bio 的完成回调 zram_writeback_endio() 负责将完成的请求加入 wb_ctl->done_reqs 链表并唤醒等待中的 writeback 任务。wb_ctl 在 writeback 任务完成后通过 release_wb_ctl() 释放。
zram_writeback_endio() 中 wake_up(&wb_ctl->done_wait) 在释放 wb_ctl->done_lock 自旋锁之后调用,创造了一个竞态窗口:writeback 任务被唤醒后可观察到 num_inflight 变为 0,然后调用 release_wb_ctl(wb_ctl) 释放 wb_ctl,而 CPU 0 上的 zram_writeback_endio() 尚未完成 wake_up() 调用,导致在已释放的内存上访问 wb_ctl->done_wait,触发 NULL 指针解引用的 UAF 崩溃。
采用 RCU 机制保护 wb_ctl。在 struct zram_wb_ctl 中新增 struct rcu_head rcu 成员,release_wb_ctl() 中用 kfree_rcu(wb_ctl, rcu) 延迟释放直到 RCU grace period 结束。zram_writeback_endio() 中用 rcu_read_lock()/rcu_read_unlock() 包裹对 wb_ctl 的访问,确保 wake_up() 访问时内存仍然有效。
系列:[PATCH] mm/damon/sysfs-schemes: fix double increment of nr_regions作者:Vineet Agarwal版本:v1(1 个 patch)
damos_sysfs_populate_region_dir() 函数负责为 DAMON sysfs 中的每个 scheme region 创建 sysfs 目录项,使用 sysfs_regions->nr_regions 作为 kobject 的名称(如 "0", "1", "2" 等)。
该函数对 nr_regions 进行了两次递增:一次是显式的 sysfs_regions->nr_regions++,另一次是 kobject_init_and_add() 格式化参数中的后置递增 sysfs_regions->nr_regions++。结果每添加一个 region,nr_regions 增加 2 而非 1,导致目录名称跳过奇数("1"、"3"、"5"...)且 nr_regions 与实际 region 数量不匹配。
将 kobject_init_and_add() 调用中的后置递增改为直接使用已递增后的值(一行修改)。
系列:[PATCH v3] mm/slub: hold cpus_read_lock around flush_rcu_sheaves_on_cache()作者:Qing Wang版本:v3(1 个 patch)
flush_rcu_sheaves_on_cache() 内部使用 for_each_online_cpu() 遍历所有在线 CPU 并对其调用 queue_work_on()。调用路径 flush_all_rcu_sheaves() 持有 cpus_read_lock() 所以安全,但 kvfree_rcu_barrier_on_cache() 在 kmem_cache 销毁路径上调用时未持有该锁。
kvfree_rcu_barrier_on_cache() → flush_rcu_sheaves_on_cache() 期间若某 CPU 下线,queue_work_on() 可能被调度到已下线 CPU。自然的修复思路是将 cpus_read_lock() 移到函数内部,但这会引入新的锁顺序 slab_mutex → cpu_hotplug_lock,与已有反向顺序 cpu_hotplug_lock → slab_mutex 形成 AB-BA 死锁。
在调用者侧修复:在 kvfree_rcu_barrier_on_cache() 中的 flush_rcu_sheaves_on_cache(s) 前后分别添加 cpus_read_lock()/cpus_read_unlock()。同时在 flush_rcu_sheaves_on_cache() 开头添加 lockdep_assert_cpus_held() 断言,通过 lockdep 捕捉类似遗漏。
系列:[PATCH v2] mm/page_alloc: fix zone reserve update serialization作者:Muchun Song版本:v2(1 个 patch)
Commit 9726891fe753 将 setup_per_zone_lowmem_reserve() 的调用移入了 adjust_managed_page_count() 中,使得 zone reserve 重新计算可从多个 CPU 上并发触发(memory hotplug、CMA 释放、页面初始化等),但未提供序列化保护。
setup_per_zone_lowmem_reserve() 更新三类数据:zone->lowmem_reserve[]、pgdat->totalreserve_pages 和全局 totalreserve_pages。并发调用者交错执行这些更新会导致部分 zone 使用旧值、部分使用新值的不一致状态,可能引发错误的 OOM 判断或不当的水印(watermark)计算。通过代码审查发现。
引入静态自旋锁序列化整个 reserve 更新操作。新增 static DEFINE_SPINLOCK(zone_reserve_lock),在 setup_per_zone_lowmem_reserve() 中使用 guard(spinlock_irqsave)(&zone_reserve_lock) 保护。同时复用该锁替代 setup_per_zone_wmarks() 中原有的独立静态锁,统一了两个相关函数的序列化范围。
系列:[PATCH v2] mm/filemap: fix page_cache_prev_miss() when no hole is found作者:Tal Zussman版本:v2(1 个 patch)
page_cache_prev_miss() 和 page_cache_next_miss() 是 page cache 的 XArray 空洞查找辅助函数,用于估算顺序读的缓存连续页面数量以决定预读窗口(readahead window)大小。这两个函数的修复历史曲折——commit 9425c591e06a 曾一起修复两者的 off-by-one 错误但被 revert,后来 next_miss 在 commit 901a269ff3d5 和 bbcaee20e03e 中被重新修复,但 prev_miss 的修复被遗漏。
当 page_cache_prev_miss() 在搜索范围内未找到空洞时,错误返回 xas.xa_index(搜索范围的第一个索引)。调用者 page_cache_sync_ra() 使用该返回值估算连续缓存页面数量时,可能将连续缓存范围少算一个页面,导致预读窗口被低估,甚至错误地进入"小随机读"分支。该 bug 由 Claude Opus 4.7 在审查 mm/filemap.c 时发现。
当未找到空洞时返回 xas.xa_index - 1(搜索范围起始索引减 1),确保返回值位于搜索范围之外。同时对 page_cache_next_miss() 做一致性更新,将函数末尾改为更直观的 return xas.xa_index + 1。v2 根据 review 意见简化了变量使用。
系列:[PATCH] Revert "mm/hugetlbfs: update hugetlbfs to use mmap_prepare"作者:Lorenzo Stoakes版本:v1(1 个 patch)
Commit ea52cb24cd3f 将 hugetlbfs 的 mmap 实现迁移到了新的 .mmap_prepare 框架。VMA lock 的分配被拆分到 success hook 中执行(hugetlb_file_mmap_prepare_success),而非在 mmap_prepare 阶段直接分配。
如果 mmap_prepare 成功返回但在 success hook 调用之前发生了分配失败(如 vm_area_struct 分配失败),success hook 永远不被调用。同时,若 hugetlb_vma_lock_alloc() 已在 mmap_prepare 阶段执行但其后有其他分配失败,已分配的 lock 将无法被释放,因为回滚路径不知道 lock 已被分配。该问题由 Mingyu Wang 报告。还存在 VMA_DONTEXPAND_BIT 未正确设置等连带问题。
直接 revert ea52cb24cd3f,将 hugetlbfs 回退到传统的 .mmap 回调模式。关键改动是将 VMA lock 分配移入 hugetlb_reserve_pages() 内部,在预留成功路径上直接调用 hugetlb_vma_lock_alloc(vma),在错误回滚路径 out_err 标签处新增 hugetlb_vma_lock_free(vma) 调用,确保 lock 分配与释放配对。revert 被视为第一步,后续可能需要重新设计 mmap_prepare 与 hugetlb 的集成。
| DAMON 数据属性监测 | ||
| shmem 零拷贝插入 | shmem_insert_folio() 接口,TTM shrink 时零拷贝插入高阶 isolated pages,可利用 CONFIG_THP_SWAP 整体换出 | |
| dma-buf per-cgroup 记账 | ||
| CMA per-NUMA 自动配置 |
| PSI 热路径优化 | ||
| DAMON per-vma lock |
| mTHP 分配竞态 | ||
| zram writeback UAF | ||
| DAMON nr_regions 双递增 | ||
| SLUB cpus_read_lock | ||
| zone reserve 序列化 | ||
| page_cache_prev_miss off-by-one | ||
| hugetlbfs mmap_prepare revert |