Linux系统中如何让已换出的file-backed可执行页保持不被重新加载
MADV_DONTNEED调用后页面非访问触发重映射的场景及解决方案
常见非CPU访问触发重映射的场景
- 其他进程/内核组件主动读取目标进程内存:
process_vm_readv()、ptrace()等系统调用,或者用户态读取/proc/[pid]/mem的操作,都会主动触发目标地址的缺页异常,无需目标进程自身访问内存地址。
内核代码参考:对应逻辑位于mm/process_vm_access.c的process_vm_rw_core()函数,内部通过get_user_pages_remote()获取目标页面,触发缺页加载流程。 - 内核后台线程的页表扫描操作:KSWAPD回收线程的反向映射扫描、KSM(内核同页合并)的重复页扫描、Cgroup内存统计扫描等后台操作,在遍历页表过程中可能触发页面加载,部分场景下还会更新页表访问标记,导致页面被误判为热页放回LRU链表。
内核代码参考:KSM的扫描逻辑位于mm/ksm.c的scan_get_next_rmap_item()函数,缺页时会调用follow_page()触发页面加载。 - 交换预读(Swap readahead)逻辑:当目标页面相邻的其他换出页被访问时,内核默认会触发交换预读,批量加载连续的多个换出页面,即使你没有访问这些额外页面。
内核代码参考:对应逻辑位于mm/swap_state.c的swap_readahead()函数,默认预读窗口大小为交换集群大小的4倍,会连续加载相邻交换槽位的页面。 - 同VMA区域的预读副作用:如果目标页面所在的VMA开启了预读,同区域其他页面触发缺页异常时,内核会顺带加载同区域的连续页面,导致被
MADV_DONTNEED释放的页面被重新加载。
内核代码参考:对应逻辑位于mm/memory.c的filemap_fault()函数,内部调用do_page_cache_ra()执行预读逻辑。
你之前的认知是正确的:MADV_DONTNEED调用确实会将目标匿名页从Active/Inactive LRU链表中移除并释放,对应代码位于mm/madvise.c的madvise_dontneed()函数,通过zap_page_range()清除页表项的同时,会将页面从LRU链表隔离后释放。
保持页面换出状态的可行方案
- 优先调用
madvise(MADV_RANDOM)标记目标内存区域,禁用该区域的文件、交换预读逻辑,避免相邻页面访问触发的批量加载。对应内核逻辑在mm/madvise.c的madvise_behavior()函数中,设置VM_RANDOM_READ标记后会将该区域预读窗口强制设为0。 - Linux 5.4及以上内核可使用
madvise(MADV_PAGEOUT)替代MADV_DONTNEED,该接口会直接将页面写入交换区并解除映射,不会触发后续的主动预读,换出效果比MADV_DONTNEED更稳定。 - 如果是文件映射页,配合
posix_fadvise(POSIX_FADV_DONTNEED)调用回收对应页缓存,避免页缓存命中导致页面被重新映射。
内容的提问来源于stack exchange,提问作者pcp
相关产品推荐
相关产品推荐

