linux内核 页缓存的脏页管理

页缓存是 Linux 内核基于物理内存 实现的文件数据缓存层 ,以4KB 物理页 为单位缓存磁盘文件的内容,是内核对所有文件 IO 的统一抽象

普通文件 IO (read/write) 和 mmap 文件映射的底层都通过页缓存交互,主缺页异常仅在文件页未缓存时触发 ,页缓存的核心作用是将磁盘 IO 转化为内存 IO,减少主缺页次数和磁盘访问,提升系统整体性能

脏页是页缓存中被修改但尚未同步到磁盘的物理页 ,内核通过PG_dirty标志位标记;

脏页管理的核心是双目标平衡

  • 一是延迟刷盘让文件 IO(write/mmap)无需等待磁盘,最大化性能;
  • 二是及时刷盘 / 回收保证数据一致性、避免物理内存耗尽;

内核通过异步刷盘内核线程可配置的阈值策略实现该平衡,所有脏页操作(标记 / 刷盘 / 清除)均基于页缓存的核心数据结构完成。

页缓存的脏页是一把「双刃剑」:一方面,异步脏页让 write ()/mmap 写操作无需等待磁盘 IO,提升性能;另一方面,若不及时刷盘,系统崩溃会导致数据丢失,物理内存不足时也需要回收页缓存。

Linux 内核通过脏页刷盘页缓存回收 两大机制,平衡性能数据一致性内存复用,这是页缓存管理的核心。

脏页的内核定义

当进程通过write () 普通文件 IOmmap (MAP_SHARED) 文件映射 修改页缓存中的数据后,该物理页的内容与底层磁盘文件 的数据不一致,内核将这类页缓存物理页定义为脏页(Dirty Page) ;与之相对的是干净页(Clean Page):页缓存数据与磁盘完全一致,无未同步修改,是页缓存的默认状态。

脏页的内核标记与核心关联结构

核心标记:PG_dirty 页状态位

Linux 内核通过 **struct page(物理页描述符)中的flags字段的 PG_dirty** 标志位,标记该物理页是否为脏页:

  • 置 1(SetPageDirty(page)):页为脏页,数据未同步到磁盘;
  • 置 0(ClearPageDirty(page)):页为干净页,数据与磁盘一致;
  • 判读(PageDirty(page)):内核通用宏,用于检查页的脏状态。

关键:仅页缓存的物理页会被标记为脏页,匿名页(malloc/mmap 匿名映射)无磁盘关联,不会被标记为脏页(其修改仅涉及内存,无需持久化)。

核心关联结构:struct address_space

每个文件的inode关联唯一的struct address_space,该结构是文件级脏页管理的核心载体,核心脏页相关字段:

  • dirty_pages脏页链表 ,存储该文件的所有脏页(struct page),实现文件级脏页的快速遍历与刷盘;
  • nr_dirty:该文件的脏页数量,用于快速统计;
  • ops:脏页操作函数集(writepage/sync_page),定义该文件脏页的刷盘逻辑(不同文件系统 / 块设备有专属实现)。

全局脏页统计:内核内存控制结构

内核通过全局内存管理结构(struct zone/struct vm_stat)统计系统级脏页总量

  • nr_dirty:整个系统的脏页总数;
  • nr_writeback:正在刷盘的脏页数量;
  • 这些统计值是内核触发异步刷盘页回收的核心依据。

脏页的核心特性

  1. 仅属于页缓存 :脏页是页缓存的状态属性,非独立内存,匿名页、内核态内存无脏页概念;
  2. 修改即标记 :对页缓存的任何写操作(write ()/mmap (MAP_SHARED) 写)都会触发脏页标记,无延迟;
  3. 延迟持久化 :脏页标记后,数据仅存于物理内存,不立即同步到磁盘,这是文件 IO 高性能的核心原因;
  4. 全局可见:脏页的修改对所有共享该页缓存的进程可见(如多进程 mmap 同一文件),刷盘后磁盘数据也全局更新;
  5. 可被锁定 :若脏页正在刷盘 / 被进程访问,内核会通过PG_locked标志锁定,避免并发修改导致数据错乱;
  6. 刷盘后清零 :脏页成功同步到磁盘后,内核会立即清除PG_dirty标志,将其转为干净页。

脏页刷盘机制

内核将页缓存的脏页异步 / 同步 写入磁盘的过程称为刷盘(flush) ,核心目标是保证页缓存与磁盘的数据一致性 ,避免系统崩溃导致数据丢失,分为4 种刷盘方式,覆盖所有场景;

刷盘核心知识

  1. 刷盘单位 :以物理页(4KB) 为单位,与页缓存的缓存单位一致;
  2. 刷盘对象 :分为文件级刷盘 (仅刷指定文件的脏页)和系统级刷盘(刷所有文件的脏页);
  3. 刷盘内容 :分为数据刷盘 (仅同步文件数据)和元数据刷盘(同步文件数据 + inode 元数据,如文件大小、修改时间、权限);
  4. 核心内核函数
    • writepage():单个脏页的刷盘核心函数,不同文件系统(ext4/xfs)有专属实现;
    • sync_inode_pages():文件级脏页刷盘,遍历文件address_space的脏页链表,调用writepage()刷盘;
    • kswapd/pdflush/flushers:内核异步刷盘线程,是后台刷盘的核心载体。

pdflush 是 2.4/2.6 早期的脏页刷盘线程,flushers 是 2.6 中后期对 pdflush 的升级(per-BDI 架构),kswapd 是全程存在的页回收核心线程,3.10 + 内核中 flushers 的刷盘逻辑被整合入 kswapd ,三者是迭代升级 + 功能融合的关系,而非独立并行;脏页刷盘的核心逻辑从「通用单线程」演进为「块设备专属线程」,最终与页回收线程融合,提升了高并发场景下的效率。

Linux 内核对脏页刷盘和内存回收的线程设计,随内核版本迭代持续优化,核心演进方向是从「全局通用线程」到「按设备隔离线程」从「刷盘 / 回收分离」到「刷盘 / 回收融合」,解决单线程瓶颈、跨设备 IO 阻塞等问题。

Linux 2.4 → Linux 2.6.10 → Linux 2.6.32 → Linux 3.10+ pdflush + kswapd → flushers(per-BDI)+ kswapd → kswapd(整合flushers)

演进的核心动因

  1. pdflush 的核心问题 :采用全局固定数量线程池 (默认 2~8 个),所有块设备共享同一批 pdflush 线程,若某块设备出现 IO 阻塞,会导致所有设备的脏页刷盘被阻塞 ,出现全局 IO 瓶颈;存在的核心缺陷:
    • 全局线程池,跨设备 IO 阻塞 :所有块设备共享 pdflush 线程,若某块磁盘(如机械盘)出现高 IO 延迟 / 阻塞,会导致 pdflush 线程被挂起,其他磁盘的脏页刷盘任务无法执行,引发全局脏页累积;
    • 线程数量固定,无法动态扩缩容:面对突发脏页写入(如大文件批量写入),固定线程数无法应对,导致脏页堆积,最终触发紧急刷盘;
    • 与 kswapd 协同低效 :pdflush 和 kswapd 是独立线程,kswapd 回收脏页时需通过内核信号通知 pdflush 刷盘,存在线程间通信开销,且易出现竞态。
  2. flushers 的改进与局限 :引入per-BDI(Block Device Interface) 架构,每个块设备对应一个独立的 flushers 线程,实现设备级刷盘隔离,解决跨设备阻塞,但与 kswapd 分离,刷盘和回收的协同效率低;
  3. 3.10 + 融合的核心优势 :将 flushers 的 per-BDI 刷盘逻辑整合入 kswapd,让内存回收脏页刷盘 由同一线程执行,减少线程间通信开销,实现「内存不足时优先刷盘回收脏页」的精准协同,提升内核效率。flushers被整合入 kswapd 的原因:
    • 与 kswapd 分离,协同效率低 :flushers 负责刷盘,kswapd 负责回收,二者仍为独立线程,kswapd 回收某设备的脏页时,需通知对应 flushers 线程刷盘,存在上下文切换和通信开销
    • 线程数量过多,资源消耗大:若系统挂载大量块设备(如云服务器的多块云盘),会创建大量 flushers 线程,占用内核内存和 CPU 资源,增加调度器负担;
    • 功能单一:仅负责脏页刷盘,无内存回收能力,无法实现「刷盘 - 回收」的一体化处理。

当前 Linux 主流版本(CentOS7/8、Ubuntu18.04+)均为 3.10 + 内核,仅存在 kswapd 线程,pdflush/flushers 均为历史概念;

方式 1:异步刷盘(内核后台自动执行,默认)

这是最主要的刷盘方式,由内核的pdflush/kswapd/flushers内核线程后台执行,无阻塞用户态进程,核心触发条件:

  • 脏页的驻留时间超过阈值 (默认 30s,可通过/proc/sys/vm/dirty_expire_centisecs修改,单位:1/100 秒);
  • 页缓存中的脏页比例超过阈值 (默认总物理内存的 20%,可通过/proc/sys/vm/dirty_ratio修改)。

内核通过两个可配置的全局阈值 触发异步刷盘,满足任一条件即启动刷盘线程,刷盘直到脏页数量 / 驻留时间低于阈值,是内核调优的核心参数:

  1. 时间阈值 :脏页的驻留时间超过阈值

    • 内核为每个脏页记录首次被标记为脏的时间
    • 当时间超过dirty_expire_centisecs(默认 3000,单位:1/100 秒,即 30 秒),触发刷盘;
    • 作用:保证任何脏页都不会在内存中驻留超过 30 秒,避免系统崩溃导致大量数据丢失。
  2. 比例阈值 :系统脏页占总物理内存的比例超过阈值

    • 当脏页总量占系统总物理内存的比例超过dirty_ratio(默认 20%),触发紧急刷盘
    • 补充阈值dirty_background_ratio(默认 10%):触发后台刷盘 ,是更温和的比例阈值,优先于dirty_ratio触发;
    • 作用:防止脏页无限制累积,耗尽物理内存。

刷盘特性

  • 异步执行:刷盘线程在后台运行,与用户态进程无交互,不阻塞任何 IO 操作;
  • 增量刷盘:仅刷盘超过阈值的脏页,而非所有脏页,减少磁盘 IO 压力;
  • 优先级低:刷盘线程的 CPU 优先级较低,避免占用过多 CPU 资源影响业务进程。

方式 2:同步刷盘(用户态主动触发,阻塞)

用户态进程可通过系统调用,主动触发脏页的同步刷盘,阻塞进程直到刷盘完成,保证数据已写入磁盘,核心系统调用:

  • fsync(fd):刷盘指定文件的所有脏页(数据 + 元数据),直到磁盘 IO 完成才返回;
  • fdatasync(fd):刷盘指定文件的数据脏页,不刷盘元数据(如文件大小、修改时间),性能比 fsync 高;
  • sync():刷盘整个系统的所有脏页(所有文件 + 块设备),阻塞直到所有刷盘完成,性能极低,不推荐频繁使用。

方式 3:文件关闭时刷盘(自动,部分场景)

当进程调用close(fd)关闭文件描述符时,内核会异步刷盘 该文件的脏页,不阻塞 close () 调用

close () 不保证脏页已写入磁盘,若需要保证,需在 close () 前调用 fsync ()/fdatasync ()。

方式 4:系统关机 / 重启时刷盘(强制,保证数据不丢失)

系统关机或重启时,内核会执行强制同步刷盘,刷盘所有脏页到磁盘,直到所有 IO 完成后,才执行后续的关机流程,避免数据丢失。

四大刷盘机制核心对比

刷盘机制 触发方式 刷盘粒度 刷盘内容 阻塞特性 核心内核线程 / 函数 适用场景
内核异步刷盘 内核阈值触发 增量(超阈值) 数据 + 元数据 不阻塞用户态 flushers/kswapd 日常默认场景,平衡性能与一致性
用户态主动刷盘 fsync(fd) 文件级全量 数据 + 元数据 同步阻塞 sync_inode_pages 强一致性场景(数据库 / 支付)
fdatasync(fd) 文件级增量 仅数据 同步阻塞(轻) sync_inode_pages 数据一致、元数据无关场景
sync() 系统级全量 所有脏页 同步阻塞(重) sync_all 系统关机 / 重大操作前
文件关闭刷盘 close(fd) 文件级增量 数据 + 元数据 不阻塞 close flushers 文件描述符释放时的被动刷盘
被动强制刷盘 页缓存回收 页级单页 数据 + 元数据 阻塞回收流程 writepage 物理内存不足,回收脏页前
系统关机 系统级全量 所有脏页 阻塞关机流程 sync_all 系统正常关机 / 重启

脏页与页缓存回收的协同逻辑

脏页管理与页缓存回收 是强耦合的,二者共同实现页缓存的物理内存复用,核心遵循 **「先刷盘,后回收」** 的铁律;

  • 回收优先级 :内核优先回收长时间未访问的干净页(LRU 的 inactive 链表),尽量避免触发刷盘;
  • 刷盘粒度 :按单个脏页刷盘,而非批量刷盘,减少单次 IO 压力;
  • 性能影响 :若待回收的页缓存中脏页占比极高,会导致大量同步刷盘,磁盘 IO 飙升,CPU 等待 IO(% iowait 升高),系统出现卡顿;
  • 锁定机制 :刷盘 / 回收过程中,内核会通过PG_locked标记锁定该页,避免进程同时修改,导致数据错乱。

脏页管理调优策略

脏页调优的核心是调整内核阈值参数 ,并结合业务场景选择刷盘方式 ,分为性能优先一致性优先两大场景,无通用最优配置,需根据业务需求定制。

场景 1:性能优先(日志写入、大文件批量写入、视频存储)

业务特点:对 IO 性能要求极高,数据一致性要求低,允许少量数据丢失(如日志可重放、大文件可重新生成);

调优策略放宽脏页阈值,减少刷盘频率,最大化延迟刷盘的性能收益

临时配置(性能优先)

echo 6000 > /proc/sys/vm/dirty_expire_centisecs # 脏页驻留60秒刷盘

echo 20 > /proc/sys/vm/dirty_background_ratio # 脏页占比20%触发后台刷盘

echo 40 > /proc/sys/vm/dirty_ratio # 脏页占比40%触发紧急刷盘

程序优化:不调用fsync/fdatasync,依赖内核异步刷盘

场景 2:一致性优先(数据库、支付系统、金融交易、文件存储)

业务特点:对数据一致性要求极高,不允许任何数据丢失,对 IO 性能要求适中;

调优策略收紧脏页阈值,增加刷盘频率,结合主动刷盘保证数据持久化

临时配置(一致性优先)

echo 1000 > /proc/sys/vm/dirty_expire_centisecs # 脏页驻留10秒刷盘

echo 5 > /proc/sys/vm/dirty_background_ratio # 脏页占比5%触发后台刷盘

echo 10 > /proc/sys/vm/dirty_ratio # 脏页占比10%触发紧急刷盘

程序优化:写操作后调用fdatasync(而非fsync),平衡性能与一致性

通用调优原则(所有场景)

  1. 避免dirty_ratio设置过高(如 > 50%),防止脏页过多导致内存耗尽回收时大量同步刷盘
  2. 避免dirty_expire_centisecs设置过长(如 > 120 秒),防止系统崩溃时丢失大量数据
  3. 优先使用fdatasync而非fsync,减少元数据刷盘的 IO 开销;
  4. 严禁频繁调用sync(),会导致磁盘 IO 飙升,阻塞所有进程;
  5. 大文件写入时,采用分块写入 + 间隔 fdatasync,避免单次刷盘量过大。
相关推荐
范纹杉想快点毕业2 小时前
嵌入式工程师一年制深度进阶学习计划(纯技术深耕版)
linux·运维·服务器·c语言·数据库·算法
物理与数学2 小时前
Linux 内核 vm_area_struct与vm_struct
linux·linux内核
txinyu的博客2 小时前
sprintf & snprintf
linux·运维·算法
EverydayJoy^v^3 小时前
RH124简单知识点——第2章——调度未来任务
linux·运维
顶点多余3 小时前
静态链接 vs 动态链接,静态库 vs 动态库
linux·c++·算法
有梦想有行动3 小时前
ClickHouse的Partition和Part概念
linux·数据库·clickhouse
物理与数学3 小时前
linux内核 Multi-Gen LRU 算法
linux·linux内核
强风7944 小时前
Linux-线程的同步与互斥
linux·服务器
提伯斯6464 小时前
Orangepi R1内置了哪些网卡驱动?(全志H3的板子)
linux·网络·wifi·全志h3