3. 页面类型与LRU分类:匿名页与文件页
Linux内核将物理页面划分为两种基本类型:匿名页(Anonymous Page)和文件页(File-backed Page)。这两种页面在生命周期、回写方式、回收代价以及LRU链表管理策略上存在本质差异。理解它们的区别是掌握内存回收机制的核心前提。
3.1 匿名页(Anonymous Page)
匿名页是指没有关联任何磁盘文件的物理页面。它们通常用于进程的堆(heap)、栈(stack)、写时复制(COW)产生的页面以及共享内存(SHM)区域。由于没有后备存储,匿名页在回收时必须先写入交换分区(swap)或交换文件,否则数据会丢失。
- 典型来源:malloc()/mmap(MAP_ANONYMOUS) 分配的堆内存、线程栈、共享内存段。
- 回写路径:必须经过 swap-out 写入交换设备,代价较高。
- 回收条件:页面内容被换出后,物理页框可被重用;再次访问时触发缺页中断换入。
- 脏页状态:匿名页始终被视为“脏页”,因为其内容与磁盘无同步关系。
3.2 文件页(File-backed Page)
文件页是映射了磁盘文件内容的物理页面,包括通过 mmap() 映射的文件区域、可执行程序的代码段(text segment)以及内核文件缓存(page cache)。文件页的内容与磁盘文件保持同步,回收时只需将脏页写回原文件,干净页则可直接丢弃。
- 典型来源:mmap(MAP_SHARED) 文件映射、可执行文件加载、文件读写产生的 page cache。
- 回写路径:脏页通过 flusher 线程写回原磁盘文件;干净页直接释放,无需I/O。
- 回收条件:干净页可立即回收;脏页需先写回,再回收。
- 脏页状态:文件页有明确的“干净/脏”区分,由硬件脏位(dirty bit)或软件脏位标记。
3.3 匿名页与文件页的核心对比
| 对比维度 | 匿名页(Anonymous Page) | 文件页(File-backed Page) |
|---|---|---|
| 后备存储 | 交换分区/交换文件 | 原始磁盘文件 |
| 脏页定义 | 始终为脏(无磁盘同步) | 仅修改后为脏 |
| 回收路径 | 必须换出到 swap | 脏页写回文件,干净页直接丢弃 |
| 回收代价 | 高(需I/O换出,可能需I/O换入) | 低(干净页零代价,脏页需写回) |
| 典型用途 | 堆、栈、共享内存 | 文件缓存、可执行代码、mmap文件 |
| LRU链表归属 | 匿名页LRU链表 | 文件页LRU链表 |
3.4 LRU链表中的分类管理
Linux内核从2.6.28开始引入双链LRU(Per-zone Two-List LRU)管理机制,将匿名页和文件页分别维护在不同的LRU链表中。每个内存管理区(zone)维护两组LRU链表:一组用于匿名页,一组用于文件页。每组又分为活跃(active)和非活跃(inactive)两个链表。
- 匿名页LRU链表:管理所有匿名页面,包括进程堆栈、共享内存等。
- 文件页LRU链表:管理所有文件映射页面和page cache页面。
这种分离设计使得内核可以针对不同类型的页面采用不同的回收策略。例如,在内存压力下,内核可以优先从文件页的非活跃链表回收页面,而保留匿名页以降低swap抖动。
3.5 页面类型与LRU链表映射关系
每个物理页面通过其 page 结构体中的标志位确定类型,并挂载到对应的LRU链表。内核通过 page_lru() 宏和 lru_cache_add() 函数实现页面分类入链。
/* 内核源码:include/linux/page-flags.h 中页面类型判断 */
#define PG_swapbacked 14 /* 匿名页标志:PageSwapBacked */
#define PG_swapcache 15 /* 交换缓存页标志 */
/* 判断是否为匿名页 */
static inline int PageAnon(struct page *page)
{
return PageSwapBacked(page) && !PageSwapCache(page);
}
/* 判断是否为文件页 */
static inline int PageFileCache(struct page *page)
{
return !PageSwapBacked(page);
}
当页面被加入LRU时,内核根据页面类型选择对应的链表组:
/* 内核源码:mm/swap.c 中 lru_cache_add 简化逻辑 */
void lru_cache_add(struct page *page)
{
int lru = LRU_INACTIVE_ANON; /* 默认匿名非活跃 */
if (PageSwapBacked(page))
lru = PageActive(page) ? LRU_ACTIVE_ANON : LRU_INACTIVE_ANON;
else
lru = PageActive(page) ? LRU_ACTIVE_FILE : LRU_INACTIVE_FILE;
__lru_cache_add(page, lru);
}
3.6 双链LRU结构总览
下图展示了每个内存管理区(zone)内部的LRU链表组织结构,清晰呈现匿名页与文件页的分离管理:
3.7 页面类型转换与特殊页面
某些页面在生命周期中会发生类型转换,内核需要动态调整其LRU归属:
- 共享内存页(SHM):使用 tmpfs 或 shmfs 的共享内存页被标记为
PG_swapbacked,归入匿名页LRU链表,回收时需换出到swap。 - 交换缓存页(SwapCache):当匿名页被换出到swap后,其物理页框可能仍保留在交换缓存中,此时页面同时具有匿名页和文件页特征,但内核将其归入匿名页LRU。
- KSM合并页:Kernel Same-page Merging 合并后的页面被标记为匿名页,但多个进程共享同一物理页框,回收时需特殊处理。
3.8 回收优先级与水位线影响
内核在回收页面时,会根据当前内存水位线(watermark)和页面类型决定回收顺序:
- 正常水位:优先回收非活跃文件页的干净页,避免swap I/O。
- 低压水位:开始回收非活跃文件页的脏页(写回后回收),同时少量回收非活跃匿名页。
- 中压水位:加大匿名页回收力度,触发swap换出。
- 高压水位:全面回收所有类型页面,包括活跃链表页面,可能触发OOM killer。
这种分级回收策略确保了系统在内存压力较轻时保持高性能,在压力严重时仍能通过swap维持基本运行。