v2 2026-11-04

页缓存无引用读取 · v2

RFC v2 引入 PG_rcu_free 延迟释放 folio 至 RCU 宽限期后,并在 splice/fuse/compaction/内存失败路径处理等待页。

原始补丁链接

子 Patch(18)

1/18
mm: free PG_rcu_free folios only after an RCU grace period
新增 PG_rcu_free 与 per-CPU 延迟释放队列,带标志 folio 需等 RCU 宽限期后才归还;使无引用 page cache 读者在 rcu_read_lock 内可安全拷贝,batch work 避免每 folio call_rcu。
2/18
splice: don't steal page cache folios
移除 page_cache_pipe_buf_ops 的 ->try_steal,禁止 splice 把 page cache folio 偷给 fuse/virtio console 后继续改写;否则 PG_rcu_free 读者仍可能读旧 folio,相关路径改为拷贝。
3/18
fuse: don't move a folio with PG_rcu_free into a fuse file
fuse_try_move_folio() 遇到 PG_rcu_free folio 时改为拷贝而非移入 fuse 文件,避免截断后仍被无引用 page cache 读者读取的 folio 被 fuse 写入,同时消除 fuse_check_folio() 的 dump_page。
4/18
mm: count the pages waiting for a grace period
新增每 zone 的 NR_FREE_RCU_PAGES 统计,把 refcount 为 0 且等待 RCU 宽限期的 deferred folio 计入 nr_free_rcu,并在 /proc/vmstat 和 show_free_areas() 展示,供提前回收判断。
5/18
mm: expedite the grace period when a CPU has many deferred frees
当单 CPU 延迟队列超过 4MiB 且 zone 低于 high watermark 或待释放页超过空闲页 1/8 时,用 expedited RCU 宽限期批量释放,避免 truncate/fadvise 后大量 folio 迟迟不能回 buddy。
6/18
mm: free the pages waiting for a grace period before OOM
在 should_reclaim_retry() 放弃前,若等待 RCU 的页加上可回收页能把合格 zone 抬到 min watermark,就 drain 延迟页到 buddy 并重试,避免过早触发 OOM;重试按无进展次数受限。
7/18
mm: free the pages waiting for a grace period before isolation fails
test_pages_isolated() 失败且 zone 有等待 RCU 的页时先 drain,避免 PG_rcu_free 迁移页让 alloc_contig_range() 返回 -EBUSY 或 offline_pages() 反复重试;offline 仅在迁移轮次后执行。
8/18
mm/compaction: free the migrated folios that wait for a grace period
在 compaction 迁移过某 block 后、检查是否完成前,若 zone 有 PG_rcu_free 等待页则 drain,避免迁移页延迟回 buddy 使 block 仍显占用、defrag_mode 过度迁移和停顿;异步压缩跳过。
9/18
mm/memory-failure: handle folios with PG_rcu_free
让 memory_failure()/soft_offline_page() 在 get_any_page() 重试前 drain PG_rcu_free 页,并在成功前等待宽限期,避免 refcount 0 的延迟页无法离线或大 folio 带毒页重新分配。
10/18
mm: have folio_mark_accessed() return whether the folio is hot
修改 folio_mark_accessed() 返回 folio 是否已处于 LRU/MGLRU 最热状态(active+referenced 非 idle 或 refs/workingset 满),供后续无引用读取热 page cache folio 时判断是否安全。
11/18
mm/filemap: mark a folio accessed in one place in filemap_read()
把 filemap_read() 两处 folio_mark_accessed() 合并为每个 folio 调用的 filemap_read_accessed(),保持首尾 folio 标记行为不变,为后续按 hot 状态选择无引用读取做准备。
12/18
mm/filemap: set PG_rcu_free on the folios that reads keep coming back to
From: Linus Torvalds torvalds@linux-foundation.org A folio with PG_rcu_free can be read without a reference, but its memory is held back for a grace period after it is freed. That trade is worth m
13/18
mm/filemap: read from the page cache without folio references
From: Linus Torvalds torvalds@linux-foundation.org Small reads of one file from many CPUs all take and drop a reference on the same folio, and its refcount bounces between the CPUs. Before taking
14/18
mm/filemap: read large folios without folio references
A large folio is in all the slots of its range or in none. Once the slot of the last page to copy holds it too, the pages in between are this folio’s: copy them after one lookup. The folio’s size is
15/18
mm/filemap: don't store f_ra.prev_pos for FMODE_RANDOM files
filemap_read() writes the end of every read to file->f_ra.prev_pos, in the cache line that fdget() and fdput() write when threads share the file table. Applications that read a file at random offsets
16/18
mm/shmem: count accesses to a folio the way filemap_read() does
shmem_file_read_iter() marks a folio accessed only when a read starts at the beginning of the folio. Small reads that start anywhere else never count, so a folio that is read over and over at one off
17/18
mm/shmem: read without folio references
shmem_file_read_iter() takes a reference on every folio it reads, so small reads of one tmpfs file from many CPUs bounce the folio’s refcount. Copy with filemap_read_rcu() first, and continue with th
18/18
mm/filemap: add a debugfs switch for lockless page cache reads
Reading hot folios without a reference defers the free of their memory by an RCU grace period. A workload that loses on that trade, or a bug in the lockless read, has no way to turn it off: CONFIG_PA