should_try_to_free_swap是交换缺页处理中的一个决策辅助函数,用于判断在成功将交换页读回内存后,是否可以安全地释放该页面对应的交换槽位。释放交换槽位意味着页面不再需要被换出,从而节省交换空间并减少后续 I/O。
核心实现
static inline bool should_try_to_free_swap(struct folio *folio, struct vm_area_struct *vma, unsigned int fault_flags) { /* * 如果页面是独占的,且我们即将使其可写,则释放交换槽位。 * 但如果页面可能被其他进程共享(例如 fork 后), * 则保留交换槽位,因为其他进程可能仍需要它。 */ if (!folio_test_swapcache(folio)) return false; if (mem_cgroup_swap_full(folio)) return true; if (!folio_test_anon(folio) || !(fault_flags & FAULT_FLAG_WRITE)) return false; if (folio_maybe_dma_pinned(folio)) return false; return true; }逐项判定逻辑
1. 页面必须在交换缓存中
!folio_test_swapcache(folio)为真时直接返回false。如果页面已经不在交换缓存中(例如已被其他路径释放),自然无需再释放。
2. 交换空间紧张时优先释放
mem_cgroup_swap_full(folio)检查内存 cgroup 的交换空间使用情况。当交换空间接近满时,函数返回true,即使条件不完美也尝试释放。注释指出:此时释放交换槽位比保留它更有价值。
3. 必须是匿名页 + 写缺页
这是核心条件:
!folio_test_anon(folio):只对匿名页释放交换槽位。文件页的交换缓存管理逻辑不同。!(fault_flags & FAULT_FLAG_WRITE):必须是写缺页。读缺页时页面仍可能被其他进程共享,释放交换槽位不安全。
4. DMA 引脚检查
folio_maybe_dma_pinned(folio)为真时返回false。如果页面可能被 DMA 长期引脚(如get_user_pages长期锁定),释放交换槽位可能导致引脚的内存被意外释放或状态不一致。
释放交换槽位的并发安全
该函数的判定结果并非绝对安全保证。调用者在实际释放交换槽位前,还需要:
持有页面锁:确保在判断和释放之间页面不会被并发修改。
重新检查 mapcount:如果
folio_mapcount(folio) != 1,说明页面已被多个进程映射,此时释放交换槽位会破坏其他进程的映射。使用
folio_free_swap():该函数内部会再次检查 mapcount 和交换计数,确保释放操作不会导致数据丢失。
设计演进
该函数在 2023 年的 folio 化补丁中从should_try_to_free_swap(struct page *page, ...)转换为接收struct folio *。转换过程中保持了逻辑完全一致,只是将PageSwapCache、PageAnon等 page 级宏替换为 folio 级接口,节省了compound_head()调用。
调用场景
该函数由do_swap_page()在成功从交换空间读回页面后调用。如果返回true,调用者会尝试通过folio_free_swap()释放交换槽位,从而避免页面再次被换出,减少不必要的 I/O。这是交换子系统“读回即优化”策略的核心决策点。