《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存
关键词:页缓存、address_space、radix tree、page、writeback、dirty page、mmap、文件系统缓存、文件 I/O 性能优化、direct I/O
一、页缓存是什么?为什么重要?
1.1 定义
页缓存(page cache)是 Linux 内核用于缓存文件内容的内存区域,避免每次文件读写都访问磁盘。
1.2 页缓存的核心目的
- 降低 I/O 延迟;
- 提高磁盘访问效率;
- 减少重复读取;
- 支持延迟写回(writeback);
- 提供内存映射(mmap)机制。
1.3 页缓存的表现形式
- 文件通过
read()
、write()
、mmap()
访问; - 文件数据页在页缓存中存在;
- 数据在页缓存中被标记为 clean(干净)或 dirty(脏页);
- 脏页会被后台线程回写到磁盘。
二、页缓存的关键数据结构
页缓存建立在以下三个核心结构之上:
2.1 struct page
表示系统中的一页物理内存,页缓存的基本单位:
c
struct page {
unsigned long flags;
atomic_t _count;
void *mapping;
pgoff_t index;
...
};
flags
:标志位(如 PG_dirty、PG_locked);mapping
:指向所属的 address_space;index
:页在文件中的偏移页号(单位为页)。
2.2 struct address_space
每个文件都有一个 address_space
结构,管理它的所有缓存页:
c
struct address_space {
struct inode *host;
struct radix_tree_root page_tree;
unsigned long nrpages;
const struct address_space_operations *a_ops;
...
};
page_tree
:以页号为 key 的 radix tree(基数树),实现高效查找;a_ops
:操作方法集(如 readpage、writepage)。
2.3 struct address_space_operations
为页缓存提供访问操作的接口:
c
struct address_space_operations {
int (*readpage)(struct file *, struct page *);
int (*writepage)(struct page *, struct writeback_control *);
...
};
- 由具体文件系统(如 ext4)实现;
- 页缓存管理不直接读写磁盘,而是调用这些函数。
三、页缓存生命周期与文件访问流程
3.1 读流程(read)
c
sys_read()
└── vfs_read()
└── generic_file_read_iter()
└── filemap_read()
└── pagecache_get_page() // 查找或分配页
└── ->a_ops->readpage() // 页面未命中时触发
流程说明:
- 读取页缓存(radix tree 查询);
- 若不存在,则通过 readpage 填充;
- 将页内容复制到用户空间缓冲区。
3.2 写流程(write)
c
sys_write()
└── vfs_write()
└── generic_file_write_iter()
└── filemap_write()
└── pagecache_get_page() // 查找页
└── copy_from_user() // 写入页缓存
└── set_page_dirty()
流程说明:
- 查找目标页;
- 若页不存在则分配;
- 将用户数据拷贝入页;
- 将页标记为 dirty。
四、脏页管理与回写机制(Writeback)
4.1 脏页定义
修改后的页被标记为 dirty(PG_dirty),需要回写到磁盘以保证数据一致性。
4.2 回写路径
由后台线程或显式触发:
c
flush_dirty_pages()
└── writeback_inodes()
└── ->a_ops->writepage()
常见触发方式:
- 系统空闲时;
- 调用
sync()
、fsync()
; - 达到内存压力阈值;
- 定期写回机制(dirty_expire_centisecs)。
4.3 脏页控制参数
/proc/sys/vm/dirty_ratio
:最大脏页占比;/proc/sys/vm/dirty_background_ratio
:达到该比例时触发后台写回;/proc/sys/vm/dirty_writeback_centisecs
:后台写回周期;/proc/sys/vm/dirty_expire_centisecs
:判断页是否过期。
五、内存映射 I/O(mmap)与页缓存
5.1 mmap 简介
将文件内容直接映射到用户地址空间,实现高效访问:
c
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
5.2 mmap 与页缓存的关联
- mmap 会将页缓存页直接映射到用户地址空间;
- 读写数据不再经过
read()
/write()
,而是直接访问页; - 修改后页也会被标记为 dirty。
5.3 写回机制
msync()
:手动同步映射数据到磁盘;munmap()
:取消映射可能触发写回;madvise()
:优化页面使用行为(如提前释放)。
六、页缓存与块设备/文件系统协作
6.1 页缓存在文件系统中的地位
- 页缓存维护每个 inode 的缓存;
- 使用
address_space
统一访问接口; - 文件系统提供底层磁盘操作方法。
6.2 文件系统对接方式
- 负责实现
readpage()
/writepage()
等; - 例如 ext4 在
fs/ext4/inode.c
中实现对应方法; - 页缓存自动调度,文件系统实现数据同步。
七、直接 I/O(Direct I/O)绕过页缓存
7.1 定义
某些场景(数据库、日志)不希望缓存数据,直接对磁盘读写,称为 Direct I/O。
7.2 使用方式
c
open("file", O_DIRECT);
7.3 特性
- 绕过页缓存;
- 用户缓冲区需对齐;
- 对性能敏感程序更有用;
- 但不再享受缓存带来的性能提升。
八、页缓存回收与清理
8.1 页回收机制
- Linux 使用 LRU(最近最少使用)算法维护页状态;
- 分为 active/inactive 两级列表;
- 页面在使用中被提升,空闲时被回收。
8.2 清理命令与技巧
sync
:触发全局写回;echo 3 > /proc/sys/vm/drop_caches
:丢弃页缓存、目录项、inode 缓存;vmstat
/top
/free
:查看内存使用情况;perf record -e
跟踪 writeback 情况。
九、写时复制(COW)机制与共享页
- 多个进程通过 mmap 映射同一文件时可共享页;
- 若进程写入,触发 COW 分离复制;
- 提高效率、减少内存占用。
十、页缓存与性能优化建议
- 利用缓存延迟写,减少同步阻塞;
- 使用
O_DIRECT
提高高吞吐任务性能; - 合理配置 dirty_* 参数减少卡顿;
- 定期回写可避免突然 IO 峰值。
十一、源码路径参考
路径 | 说明 |
---|---|
mm/filemap.c |
页缓存读取与写入核心逻辑 |
mm/page-writeback.c |
回写机制实现 |
fs/buffer.c |
文件系统缓存页管理 |
include/linux/page-flags.h |
页状态标志位 |
include/linux/address_space.h |
address_space 定义 |
十二、小结
- 页缓存是提升 Linux 文件访问性能的核心机制;
- 通过 address_space 管理每个 inode 的页面;
- 读写系统调用和 mmap 都利用页缓存;
- 写回机制由后台线程或显式触发;
- 文件系统负责底层 IO,页缓存提供统一访问层;
- 参数调优可显著提升 IO 表现。