详细介绍Linux 内存管理 匿名页面和page cache页面有什么区别?
Linux 内存管理:匿名页面 vs Page Cache 页面深度解析
在 Linux 内存管理中,匿名页面 (Anonymous Pages) 和 页面缓存 (Page Cache) 是两种核心内存类型,它们在物理内存中存储不同类型的数据,并由不同的内核机制管理。下面从 5 个维度进行详细对比:
1. 核心概念对比
特性 匿名页面 页面缓存
数据来源 用户进程动态内存分配 文件系统内容缓存
典型内容 堆、栈、共享内存、mmap(MAP_ANON) 文件内容、目录、块设备数据
存储位置 RAM RAM (文件数据的缓存副本)
生命周期 与进程共存 可长期存在(直到缓存淘汰)
后备存储 交换空间 (Swap) 磁盘文件
管理对象 struct anon_vma struct address_space
页表项标志 VM_SHARED 和 VM_PRIVATE 无特定标志
2. 使用场景对比
匿名页面场景
// 动态内存分配
void *heap_mem = malloc(1024); // glibc底层使用brk/sbrk
// 栈内存
int stack_var = 42;
// mmap匿名映射
void *anon_mem = mmap(NULL, 8192,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-1, 0);
页面缓存场景
// 文件读写
int fd = open("data.txt", O_RDWR);
char buf[4096];
read(fd, buf, 4096); // 数据加载到page cache
// mmap文件映射
void *file_mem = mmap(NULL, 8192,
PROT_READ|PROT_WRITE,
MAP_SHARED,
fd, 0);
3. 内核数据结构对比
匿名页面反向映射
struct anon_vma {
struct rw_semaphore rwsem; // 读写锁
atomic_t refcount; // 引用计数
struct anon_vma_chain *root;
};
页面缓存组织
struct address_space {
struct inode *host; // 所属inode
struct xarray i_pages; // 页缓存树
struct rw_semaphore i_mmap_rwsem;
unsigned long nrpages; // 缓存页数
};
4. 缺页异常处理流程对比
匿名页面缺页处理 (do_anonymous_page)
触发缺页
是否为写访问?
-->分配新物理页-->初始化页面内容为0-->建立页表映射
-->映射零页
页面缓存缺页处理 (filemap_fault)
触发缺页
页面是否在缓存中?
-->映射缓存页
-->预读策略-->从磁盘读取数据-->创建新缓存页-->建立页表映射
5. 回收机制对比
匿名页面回收
通过 kswapd 扫描 LRU_INACTIVE_ANON 列表
使用交换子系统的反向映射 (try_to_unmap)
写入交换空间后释放物理页
页面缓存回收
通过 kswapd 扫描 LRU_INACTIVE_FILE 列表
干净页面直接释放
脏页触发回写机制 (writepages)
实战代码分析:两种页面行为对比
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#define SIZE (2 * 1024 * 1024) // 2MB
int main() {
// 场景1: 匿名页面操作
char *anon_mem = mmap(NULL, SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
// 写入数据(触发物理页分配)
for (int i = 0; i < SIZE; i += 4096)
anon_mem[i] = 'A';
printf("匿名页已分配\n");
// 场景2: Page Cache操作
int fd = open("test.dat", O_RDWR | O_CREAT, 0666);
ftruncate(fd, SIZE); // 创建2MB文件
char *file_mem = mmap(NULL, SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, 0);
// 写入数据(进入page cache)
for (int i = 0; i < SIZE; i += 4096)
file_mem[i] = 'B';
printf("Page Cache已填充\n");
msync(file_mem, SIZE, MS_SYNC); // 强制回写磁盘
// 查看内存统计
system("grep -E 'AnonPages|PageCache' /proc/meminfo");
// 释放资源
munmap(anon_mem, SIZE);
munmap(file_mem, SIZE);
close(fd);
// 查看回收后状态
system("grep -E 'AnonPages|PageCache' /proc/meminfo");
return 0;
}
输出分析:
匿名页已分配
Page Cache已填充
AnonPages: 2048 kB # 匿名页占用
PageCache: 2048 kB # 页面缓存占用
# 释放后
AnonPages: 0 kB # 立即归零
PageCache: 16 kB # 元数据残留
6. 高级行为对比
行为 匿名页面 页面缓存
预读机制 无 文件系统级预读 (readahead)
透明大页(THP) 支持 (MADV_HUGEPAGE) 部分支持(依赖文件系统)
内存回收优先级 高 (vm.swappiness控制) 低 (vm.vfs_cache_pressure控制)
脏页处理 必须换出到swap 可延迟回写到文件系统
共享机制 写时复制 (COW) 直接共享
7. 性能优化技巧
匿名页面优化
// 1. 使用大页
madvise(anon_mem, SIZE, MADV_HUGEPAGE);
// 2. 禁用交换
mlock(anon_mem, SIZE); // 防止被换出
页面缓存优化
// 1. 预读设置
readahead(fd, 0, 4 * 4096); // 预读4个页面
// 2. 绕过缓存
int fd = open("file.dat", O_DIRECT); // 直接IO
总结:核心差异表
维度 匿名页面 页面缓存
数据来源 进程动态创建 存储设备文件
内核管理 anon_vma链 + swap address_space树 + 回写机制
缺页成本 低(零页或新分配) 高(可能触发磁盘IO)
生命周期 绑定进程生命周期 与文件缓存策略相关
共享开销 高(COW复制) 低(直接共享)
回收影响 强制swap-out(性能损失大) 可能仅丢失缓存(性能损失小)
理解这些差异对于开发高性能应用和诊断内存问题至关重要。通过合理选择内存类型,可以显著优化程序性能并减少系统开销。