引言
Linux内核的内存管理子系统是操作系统中最复杂、最关键的组件之一。在系统内存紧张时,内核必须做出两个关键决策:哪些页面应该被回收(reclaim),以及当内存耗尽时应该终止哪个进程(OOM Killer)。本文将深入剖析这些机制的实现原理。
1. 页面组织:LRU链表体系
Linux内核使用LRU(Least Recently Used)算法来决定哪些页面可以被回收。自2.6内核以来,内核维护活跃的LRU链表和不活跃的LRU链表,形成双时钟(approximated clock)算法。
每种内存 zone 都维护以下五种LRU链表:
- anon_active - 活跃匿名页
- anon_inactive - 不活跃匿名页
- file_active - 活跃文件页
- file_inactive - 不活跃文件页
- unevictable - 不可回收页
页面在active和inactive之间移动的核心逻辑是页面访问位(referenced bit)的检测。当scan_balance参数配置为SCAN_ANON时,内核优先扫描匿名页;当为SCAN_FILE时优先扫描文件缓存。
2. LRU页面回收:shrink_lruvec()
页面回收的入口函数是shrink_node(),它会遍历所有内存cgoup的LRU链表,调用shrink_lruvec()来执行实际回收。
关键参数get_scan_count()决定了对inactive list和active list的扫描比例:
- 当swappiness=0时,只扫描文件页
- 当swappiness=100时,匿名页和文件页平等对待
- 默认swappiness=60,文件页享有轻微优势
3. 反向映射(Reverse Mapping)
页面回收时需要更新所有引用该页的页表项,这就需要反向映射机制。Linux内核实现了两种RMAPI:
- Anonymous pages (page_vma_mapped_walk):通过anon_vma链表追踪一个物理页被哪些VMA映射
- File pages (try_to_unmap_file):通过interval tree(基于prio_tree)快速定位引用者
反向映射的本质目标是建立一个从物理页面到所有引用它的虚拟地址映射的映射表。当一个页面被回收时,内核可以快速找到所有引用该页的PTE,清除Present位,设置swap entry或设置文件offset。
4. Swap机制与回写策略
匿名页被回收时,会被写入swap空间。内核使用frontswap和zswap等压缩技术来优化swap性能。zswap在写入磁盘前对页面进行压缩,减少I/O延迟。
5. OOM Killer:评分与选择
当直接回收仍然无法满足内存需求时,内核触发OOM Killer。其核心逻辑是遍历所有进程,计算oom_score分数:
- oom_score = 占用内存比例 × 10 × 1000 + oom_score_adj 的修正
- 占用比例:RSS / 总物理内存(zone中需要考虑huge page)
- oom_score_adj的取值范围是-1000到1000,设置为-1000表示永远不会被OOM选中
- oom_kill_allocating_task参数可让内核直接杀死触发OOM的进程而跳过评分
6. Memory Cgroup与PSI
现代容器环境中,内存限制通过cgroup实现。当cgroup达到limit时,触发cgroup级别的OOM。PSI(Pressure Stall Information)是Linux 4.20引入的新机制,通过/proc/pressure/memory暴露内存压力信息。
7. 性能调优实践
- vm.swappiness - 控制匿名页与文件页回收的平衡(数据库场景推荐设为1-10)
- vm.min_free_kbytes - 保留的最小空闲内存
- vm.vfs_cache_pressure - 控制dentry/inode缓存回收倾向
- memory.high/memory.max - cgroup层面的软/硬内存限制
总结
Linux内核的内存回收机制是一个多层的复杂体系,从页面访问跟踪(LRU)、反向映射(RMAP)、异步后台回收(kswapd)到终极手段(OOM Killer),每一层都有精密的权衡。

发表评论 取消回复