【Note】《深入理解Linux内核》 Chapter 15 :深入理解 Linux 页缓存

《深入理解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()  // 页面未命中时触发

流程说明:

  1. 读取页缓存(radix tree 查询);
  2. 若不存在,则通过 readpage 填充;
  3. 将页内容复制到用户空间缓冲区。

3.2 写流程(write)

c 复制代码
sys_write()
 └── vfs_write()
     └── generic_file_write_iter()
         └── filemap_write()
             └── pagecache_get_page()  // 查找页
             └── copy_from_user()      // 写入页缓存
             └── set_page_dirty()

流程说明:

  1. 查找目标页;
  2. 若页不存在则分配;
  3. 将用户数据拷贝入页;
  4. 将页标记为 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 表现。
相关推荐
峰顶听歌的鲸鱼1 小时前
30.Linux DHCP 服务器
linux·运维·服务器·笔记·学习方法
Lzc7741 小时前
Linux的网络基础
linux·linux的网络基础
violet-lz1 小时前
Linux文件系统调用:文件调用函数与exec系统函数详解与应用
linux·运维·服务器
一人の梅雨2 小时前
亚马逊 MWS 关键字 API 实战:关键字搜索商品列表接口深度解析与优化方案
python·spring
期待のcode2 小时前
MyBatis框架—延迟加载与多级缓存
java·数据库·后端·缓存·mybatis
袁泽斌的学习记录2 小时前
ubuntu22.04安装cuda11.4版本
linux·运维·服务器
华仔啊2 小时前
Spring 配置混乱?搞懂这两个核心组件,问题真能少一半
java·后端·spring
喂完待续3 小时前
【序列晋升】45 Spring Data Elasticsearch 实战:3 个核心方案破解索引管理与复杂查询痛点,告别低效开发
java·后端·spring·big data·spring data·序列晋升
用户31187945592183 小时前
CentOS 7 安装 net-tools.rpm 包步骤详解(附 rpm 命令和 yum 方法)附安装包
linux
我叫黑大帅3 小时前
什么是 mmap?
linux·c++·操作系统