Linux 内核技术实战课 · Page Cache 管理模块让文件读写真正为业务提速一、引言在 Linux 服务器上你是否遇到过以下场景磁盘 IO 明明没满业务接口却突然变慢好几倍服务器 load average 毫无征兆地飙升到十几查进程却找不到 CPU 繁忙的凶手一句echo 3 /proc/sys/vm/drop_caches下去业务反而踩了雷。这些问题的幕后黑手多半是Page Cache页缓存。——它是 Linux 内核最核心的内存管理机制之一也是应用层最容易感知、却最容易误解的模块。本文是《Linux 内核技术实战课》的Page Cache 管理模块全部数据来自真实服务器 ecs-fddb-0001Ubuntu 24.04 / 内核 6.8 / 8C16G的真实实验没有理论推导只有实测取证。二、基础篇 · 用数据观测 Page Cache2.1 /proc/meminfo一眼看穿内存去哪了很多人习惯用free -h看内存但free输出的buff/cache是一个黑盒我们得拆开它。# 在 ecs-fddb-0001 上执行cat/proc/meminfo真实的输出节选以下数据均为该机器采集MemTotal: 15495016 kB MemFree: 13916460 kB MemAvailable: 14786828 kB Buffers: 37964 kB Cached: 1061188 kB Active(file): 198752 kB Inactive(file): 889028 kB AnonPages: 121020 kB Shmem: 2628 kB Dirty: 186952 kB Writeback: 0 kB Slab: 151484 kB SReclaimable: 67616 kB SUnreclaim: 83868 kB拿这张「内存全家福」逐一拆解字段本机值含义能不能回收MemFree~13.9 GiB完全空闲的内存—MemAvailable~14.8 GiB内核估算可立即分配的内存量—Cached~1.06 GiBPage Cache 主体— 普通文件的数据缓存可回收echo 1Buffers~37 MiB块设备元数据缓存可回收echo 2连带Active(file)~198 MiB近期访问过的文件页可回收优先级低Inactive(file)~889 MiB近期未访问的文件页可回收优先级高AnonPages~118 MiB匿名页堆、栈、mmap不可回收只能 swapShmem~2.6 MiB共享内存/tmpfs计入 Cached 但不可回收SReclaimable~66 MiBslab 中可回收部分dentry/inode可回收echo 2SUnreclaim~82 MiBslab 中不可回收部分不可回收Dirty~183 MiB已修改、待写回磁盘的脏页写回后释放一句话穿透表象free -h里的buff/cache不是浪费而是内核替你缓存的文件数据。判断内存是否紧张要看MemAvailable和SReclaimable而不是MemFree。AnonPages不属于 Page Cachedrop_caches清不掉它SUnreclaim是内核长期占用的 slab 对象也清不掉。所以有时候你echo 3之后发现free显示的buff/cache还有几百 MiB别慌——那是共享内存和不可回收 slab。2.2 写文件 → Cached 增长读/写都会产生缓存# 基线free-h;grep-E^(Cached|Buffers)/proc/meminfo基线输出Buffers: 38072 kB Cached: 1062920 kB此时Cached约 1.06 GiB是系统刚启动后各类文件留下的缓存。生成一个 2GB 文件ddif/dev/zeroof/tmp/pc_testbs1Mcount2048convfdatasync写完后观测Buffers: 38116 kB Cached: 3160184 kB # 增长约 2 GiB Dirty: 280 kBCached从1.06 GiB → 3.16 GiB正好多出约2 GiB。Dirty几乎归零——说明convfdatasync让数据同步落盘脏页已刷干净。再把文件完整读一遍cat/tmp/pc_test/dev/null vmtouch /tmp/pc_testvmtouch 输出Files: 1 Directories: 0 Resident Pages: 524288/524288 2G/2G 100% Elapsed: 0.04358 seconds524288/524288 页 100%驻留内存。整个 2GB 文件已被完整装进 Page Cache。结论Page Cache 不是手动开启的功能而是读/写文件的必然副产品。内核把你经手的文件数据自动缓存起来下次读就直接走内存不再碰磁盘。vmtouch是把内存占用这个抽象数字变成文件到底在不在内存里的直观利器。2.3 释放 Page Cachedrop_caches 的 1/2/3 到底有什么区别# 先确认文件已缓存cat/tmp/pc_test/dev/null vmtouch /tmp/pc_test释放前Buffers: 38124 kB Cached: 3160200 kB Files: 1 Resident Pages: 524288/524288 2G/2G 100%执行echo 3 /proc/sys/vm/drop_caches之后Buffers: 1520 kB Cached: 115944 kB Files: 1 Resident Pages: 0/524288 0/2G 0%Cached 从3.16 GiB → 0.11 GiBvmtouch直接从 100% 掉到0%。文件缓存被彻底丢弃。再分别实验echo 1和echo 2的区别操作Cached 变化Buffers 变化效果释放前基线2240808 kB3748 kB—echo 1仅 pagecache131980 kB✔ 大幅下降396 kB文件数据页被清slab 不动再次读入后2246060 kB4400 kB—echo 2仅 slab2246060 kB ✘ 几乎不变4400 kBdentry/inode 被清文件页不动三者语义总结echo 1 仅释放 page cache文件数据页echo 2 仅释放可回收 slabdentry、inode 等元数据缓存echo 3 1 2最彻底重要提醒drop_caches只能丢弃干净的、可丢弃的文件缓存。它不会回收匿名页AnonPages也不会让脏页凭空消失——脏页会先被内核写回再释放。生产中千万不要把它当释放内存的常规手段它会让你热点的文件缓存全部失效这正是下一节讲到的性能抖动根源。三、基础篇 · Page Cache 的自动回收机制Page Cache 不只是产生与手动释放内核还有一套自动回收机制。当内存压力上升低于watermark水位线时内核的kswapd后台线程开始扫描 LRU 链表优先回收Inactive(file)中的页。如果回收速度跟不上分配速度分配路径就会触发直接回收direct reclaim——这是同步阻塞操作进程必须等内核腾出空闲页才能继续执行。控制回收行为的核心参数来自sysctl# 在 ecs-fddb-0001 上采集sysctlvm.min_free_kbytes vm.dirty_ratio vm.dirty_background_ratio vm.swappiness vm.vfs_cache_pressurevm.min_free_kbytes 67584 vm.dirty_ratio 20 vm.dirty_background_ratio 10 vm.swappiness 0 vm.vfs_cache_pressure 100参数本机值作用与影响min_free_kbytes~66 MB保留的最低空闲内存。逼近此值触发 direct reclaim——阻塞操作是 load 飙高的根因之一dirty_ratio20%脏页占可用内存超过此比例写进程被阻塞强制同步回写dirty_background_ratio10%脏页超过此比例后台线程开始回写不阻塞业务swappiness0倾向保护匿名内存内存紧张时优先回收文件缓存——Page Cache 更易失vfs_cache_pressure100越大越积极回收 dentry/inode 等元数据缓存本机swappiness0是云镜像常见默认值。这意味着内存紧张时内核优先回收 Page Cache 而非 swap 匿名页——Page Cache 的易失性被进一步放大这也是实验 5 中抖动问题的内因之一。四、案例篇 · 难以回收导致 load 飙高4.1 实验设计我们模拟一个极端场景Page Cache 被不断冲刷留不住。准备一个4GB 的大文件/bigfile启动16 个后台读者while true; do cat /bigfile /dev/null; done持续读文件同时启动一个每秒执行echo 1 drop_caches的循环——强制回收刚装进去的读缓存后台采集vmstat 1、iostat -x 1、sar -B 1并周期性打印 loadavg 与 D 状态进程数。4.2 真实数据load 从 0.8 飙到 5.12基线压测前loadavg: 0.81 0.37 0.16 2/268 9377 iostat: vda %util 7.49%磁盘基本空闲load 时间线时间点loadavg (1min)D 状态进程数说明t0s0.810基线t5s2.021616 个 cat 全部卡在 D 状态t10s3.1416持续攀升t15s4.179偶尔有读者跑完一轮t20s5.1212仍在上升远未到上限vmstat 1 节选核心证据procs r b swpd free buff cache bi bo us sy id wa st 18 0 0 8598328 5264 6597956 4155 6553 0 0 97 2 0 17 0 0 10004784 1032 5206040 996 0 1 99 0 0 0 8 7 0 13170804 1032 2052440 131120 0 1 85 0 14 0 1 15 0 14828600 1476 404332 122760 88 0 15 0 85 0 0 16 0 15036492 140 191152 120836 0 0 12 0 89 0稳态下bblocked列长期10~16waiowait≈87~89%ididle接近 0。iostat -x 1 稳态样本avg-cpu: %user %nice %system %iowait %steal %idle 0.15 0.00 11.11 88.27 0.00 0.46 Device r/s rkB/s r_await aqu-sz %util vda 4510.0 120564.0 1.70 7.66 90.90sar -B 1pgpgin/s fault/s majflt/s 136500.00 3165.00 7.00 121096.00 7138.00 109.00 ... Average: 116817.47 798.26 13.584.3 因果链为什么 load 会飙高整个链条可以浓缩为 4 步每秒 drop_caches → 缓存被清空 → 16 个读者全去读盘 → 读盘走 D 状态不可中断睡眠 → D 状态进程计入 load average → load 从 0.8 飙到 5.12根因一句话Page Cache 本该替你挡住磁盘 IO当它留不住时读请求全部落盘进程在不可中断睡眠D 状态中排队load average 随之被抬高。D 状态进程数直接计入 load average——这是很多运维误以为CPU 繁忙但实际是 IO 瓶颈的根源。sar -B 的pgpgin/s约 117 MB/s持续从磁盘读页、majflt/s约 14每秒 14 次必须读盘的主要缺页这些都是缓存不断失效、命不中的铁证。4.4 生产建议drop_caches不要进 cron不要当常规操作。它会让热点缓存失效制造人为的冷启动监控vmstat的 b 列。b 0持续出现说明有进程在 D 状态阻塞要么是 IO 瓶颈要么是 direct reclaim监控/proc/loadavg与vmstat的 r/b 列联合看load 高但rrunning低 → 大概率是 D 状态堆积而非 CPU 不够如果是因为内存紧张导致 Page Cache 被自动回收而触发 D 状态阻塞就要调整vm.min_free_kbytes或vm.vfs_cache_pressure让回收更平滑。五、案例篇 · 容易回收引起业务读延迟抖动5.1 实验设计对比热点文件有缓存与缓存被回收无缓存两种情况下读同一文件的耗时与磁盘压力差异。场景 A有缓存先 warm 一遍 →time cat /tmp/pc_testiostat/sar -b场景 B无缓存echo 3 drop_caches→time cat /tmp/pc_testiostat/sar -b5.2 真实数据99 倍的延迟差距场景 A有缓存vmtouch 确认 100% 驻留[有缓存] 读取耗时 .166154837 秒有缓存期间 iostatvda rkB/s7255 ... %util 9.25 接近空闲对比 sar -b 有缓存tps rtps wtps bread/s bwrtn/s 2.00 0.00 2.00 0.00 32.00 ← rtps0几乎不读盘场景 B无缓存echo 3 清空后[无缓存] 读取耗时 16.475804879 秒无缓存期间 iostatvda r/s480 rkB/s122628 r_await4.09 ... %util96.40 vda ... %util96.00 / 92.60 / 96.80 / 59.90 / 96.30 磁盘打满sar -b 无缓存tps rtps wtps bread/s bwrtn/s 1090.00 1090.00 0.00 481544.00 0.00 607.00 607.00 0.00 245856.00 0.00 ... Average: 664.40 654.40 10.00 292796.80 89.605.3 数据对比指标有缓存场景 A无缓存场景 B差距读 2GB 耗时0.166 秒16.48 秒≈ 99 倍磁盘 %util~9%~96%10 倍rtps读请求/s0~654∞bread/s读流量0~287 MB/s∞5.4 根因分析一次缓存未命中就把读延迟从亚秒级拉到十几秒级。99 倍的差距在业务侧就是接口突然从 1ms 变成 1s的典型抖动。而vmtouch收尾验证发现无缓存读完之后该文件又被重新装回了 Page Cache100% Resident。这说明抖动发生在缓存刚被清掉的那个瞬间——一旦缓存回温性能又恢复正常。但回温的过程需要完整读一遍磁盘这段时间的延迟就是灾难。根因一句话Page Cache 命中是纳秒级的内存访问一旦被回收下一次访问必须触发磁盘 IO毫秒级业务出现明显抖动。这正是线上明明磁盘没满某个接口却突然变慢的典型内核根因。六、分析篇 · 如何判断问题是否由 Page Cache 产生6.1 排查清单当怀疑业务抖动与 Page Cache 有关时按以下步骤排查第一步看整体内存水位free-hgrep-E^(Cached|Buffers|MemAvailable|AnonPages)/proc/meminfoMemAvailable如果远低于总内存——说明系统内存紧张Page Cache 可能正在被大量回收。第二步看缓存是否命中sar-B1关注pgpgin/s每秒从磁盘读入页的量和majflt/s每秒主要缺页次数。如果pgpgin/s持续高位几十 MB/s 以上说明大量读请求走盘缓存未命中。第三步看进程是否在 IO 阻塞vmstat1重点关注bblocked列。b 0持续出现说明有进程在 D 状态等待 IO 或内存回收。联合iostat -x 1看%util和aqu-sz确认磁盘是否为瓶颈。第四步vmtouch 定点取证vmtouch /path/to/suspected/file直接看业务热点文件到底有多少页在内存里。如果Resident Pages比例很低说明缓存不住。第五步检查/proc/vmstat中的关键指标grep-Enr_file_pages|pgpgin|pgpgout|pgsteal|pgscan/proc/vmstatpgscan_kswapd/pgscan_direct后台回收 vs 直接回收的次数。pgscan_direct高说明频繁发生 direct reclaim——这是最严重的阻塞场景。pgsteal_*回收成功页数与 pgscan 对比看回收效率。6.2 调优方向场景推荐调优原理内存紧张 频繁 direct reclaim适当调大vm.min_free_kbytes提前启动 kswapd 后台回收避免分配路径阻塞小文件密集 元数据缓存频繁被回收调低vm.vfs_cache_pressure如 50保护 dentry/inode 缓存减少 major fault写多读少 脏页突发回写 IO 风暴调低vm.dirty_ratio如 10%vm.dirty_background_ratio如 5%更早、更平滑地启动后台回写避免瞬间大 IO读多写少 读缓存易失调高vm.swappiness如 10~20如果系统有 swap让内核在内存紧张时优先 swap 匿名页保护文件缓存热点文件被意外回收检查是否误用drop_caches用mlock()或vmtouch -t锁定关键文件强制驻留不让内核回收七、本模块要点速记Page Cache 是文件读写的必然产物不是可选的功能。内核用它避免重复读盘提升 IO 性能。/proc/meminfo的 Cached ≠ buff/cache。Cached是文件数据缓存可回收Buffers是块设备元数据缓存Shmem计入 Cached 但不可回收。drop_caches的 1/2/3 语义1文件页2slab 元数据3两者。千万别把它当常规运维手段——它会清掉热点缓存制造人为抖动。D 状态进程直接计入 load average。load 高不一定是 CPU 忙很可能是 IO 阻塞或 direct reclaim。联合vmstat b列和iostat %util判断。缓存命中 vs 缺失99 倍延迟差距。有缓存读是亚秒级无缓存是十几秒级。一次缓存回收就能让业务接口从 1ms 变成 1s。排查三板斧free -h看水位 →sar -B 1看 pgpgin/majflt →vmstat 1看 b 列。定点用vmtouch确认文件驻留比例。调优是系统工程min_free_kbytes、dirty_ratio、swappiness、vfs_cache_pressure四个参数决定了 Page Cache 的产生→回收→再平衡节奏需结合业务模型读多写少 / 写多读少 / 小文件密集来调整。八、下篇预告Page Cache 的管理是内存怎么给文件缓存用的问题。但更常见的线上事故是——内存泄漏进程分配了内存却不释放或者内核 slab 持续增长不回缩最终 OOM 杀进程。下一讲《Linux 内核技术实战课 · 内存泄漏模块从 slab 泄漏到进程 OOM 的完整追溯》我们将继续用真实实验数据演示用/proc/slabinfo和slabtop追踪 slab 泄漏用kmemleak扫描内核态内存泄漏用memcgperf定位用户态内存泄漏真实 OOM 案例的 dmesg 日志解读。敬请期待。本文全部实验数据均来自ecs-fddb-0001Ubuntu 24.04 / Kernel 6.8 / 8C16G脚本与完整日志见scripts/目录。欢迎在评论区交流实操中的疑问与踩坑。[exit0]
Linux 内核技术实战课 · Page Cache 管理模块:让文件读写真正为业务提速
Linux 内核技术实战课 · Page Cache 管理模块让文件读写真正为业务提速一、引言在 Linux 服务器上你是否遇到过以下场景磁盘 IO 明明没满业务接口却突然变慢好几倍服务器 load average 毫无征兆地飙升到十几查进程却找不到 CPU 繁忙的凶手一句echo 3 /proc/sys/vm/drop_caches下去业务反而踩了雷。这些问题的幕后黑手多半是Page Cache页缓存。——它是 Linux 内核最核心的内存管理机制之一也是应用层最容易感知、却最容易误解的模块。本文是《Linux 内核技术实战课》的Page Cache 管理模块全部数据来自真实服务器 ecs-fddb-0001Ubuntu 24.04 / 内核 6.8 / 8C16G的真实实验没有理论推导只有实测取证。二、基础篇 · 用数据观测 Page Cache2.1 /proc/meminfo一眼看穿内存去哪了很多人习惯用free -h看内存但free输出的buff/cache是一个黑盒我们得拆开它。# 在 ecs-fddb-0001 上执行cat/proc/meminfo真实的输出节选以下数据均为该机器采集MemTotal: 15495016 kB MemFree: 13916460 kB MemAvailable: 14786828 kB Buffers: 37964 kB Cached: 1061188 kB Active(file): 198752 kB Inactive(file): 889028 kB AnonPages: 121020 kB Shmem: 2628 kB Dirty: 186952 kB Writeback: 0 kB Slab: 151484 kB SReclaimable: 67616 kB SUnreclaim: 83868 kB拿这张「内存全家福」逐一拆解字段本机值含义能不能回收MemFree~13.9 GiB完全空闲的内存—MemAvailable~14.8 GiB内核估算可立即分配的内存量—Cached~1.06 GiBPage Cache 主体— 普通文件的数据缓存可回收echo 1Buffers~37 MiB块设备元数据缓存可回收echo 2连带Active(file)~198 MiB近期访问过的文件页可回收优先级低Inactive(file)~889 MiB近期未访问的文件页可回收优先级高AnonPages~118 MiB匿名页堆、栈、mmap不可回收只能 swapShmem~2.6 MiB共享内存/tmpfs计入 Cached 但不可回收SReclaimable~66 MiBslab 中可回收部分dentry/inode可回收echo 2SUnreclaim~82 MiBslab 中不可回收部分不可回收Dirty~183 MiB已修改、待写回磁盘的脏页写回后释放一句话穿透表象free -h里的buff/cache不是浪费而是内核替你缓存的文件数据。判断内存是否紧张要看MemAvailable和SReclaimable而不是MemFree。AnonPages不属于 Page Cachedrop_caches清不掉它SUnreclaim是内核长期占用的 slab 对象也清不掉。所以有时候你echo 3之后发现free显示的buff/cache还有几百 MiB别慌——那是共享内存和不可回收 slab。2.2 写文件 → Cached 增长读/写都会产生缓存# 基线free-h;grep-E^(Cached|Buffers)/proc/meminfo基线输出Buffers: 38072 kB Cached: 1062920 kB此时Cached约 1.06 GiB是系统刚启动后各类文件留下的缓存。生成一个 2GB 文件ddif/dev/zeroof/tmp/pc_testbs1Mcount2048convfdatasync写完后观测Buffers: 38116 kB Cached: 3160184 kB # 增长约 2 GiB Dirty: 280 kBCached从1.06 GiB → 3.16 GiB正好多出约2 GiB。Dirty几乎归零——说明convfdatasync让数据同步落盘脏页已刷干净。再把文件完整读一遍cat/tmp/pc_test/dev/null vmtouch /tmp/pc_testvmtouch 输出Files: 1 Directories: 0 Resident Pages: 524288/524288 2G/2G 100% Elapsed: 0.04358 seconds524288/524288 页 100%驻留内存。整个 2GB 文件已被完整装进 Page Cache。结论Page Cache 不是手动开启的功能而是读/写文件的必然副产品。内核把你经手的文件数据自动缓存起来下次读就直接走内存不再碰磁盘。vmtouch是把内存占用这个抽象数字变成文件到底在不在内存里的直观利器。2.3 释放 Page Cachedrop_caches 的 1/2/3 到底有什么区别# 先确认文件已缓存cat/tmp/pc_test/dev/null vmtouch /tmp/pc_test释放前Buffers: 38124 kB Cached: 3160200 kB Files: 1 Resident Pages: 524288/524288 2G/2G 100%执行echo 3 /proc/sys/vm/drop_caches之后Buffers: 1520 kB Cached: 115944 kB Files: 1 Resident Pages: 0/524288 0/2G 0%Cached 从3.16 GiB → 0.11 GiBvmtouch直接从 100% 掉到0%。文件缓存被彻底丢弃。再分别实验echo 1和echo 2的区别操作Cached 变化Buffers 变化效果释放前基线2240808 kB3748 kB—echo 1仅 pagecache131980 kB✔ 大幅下降396 kB文件数据页被清slab 不动再次读入后2246060 kB4400 kB—echo 2仅 slab2246060 kB ✘ 几乎不变4400 kBdentry/inode 被清文件页不动三者语义总结echo 1 仅释放 page cache文件数据页echo 2 仅释放可回收 slabdentry、inode 等元数据缓存echo 3 1 2最彻底重要提醒drop_caches只能丢弃干净的、可丢弃的文件缓存。它不会回收匿名页AnonPages也不会让脏页凭空消失——脏页会先被内核写回再释放。生产中千万不要把它当释放内存的常规手段它会让你热点的文件缓存全部失效这正是下一节讲到的性能抖动根源。三、基础篇 · Page Cache 的自动回收机制Page Cache 不只是产生与手动释放内核还有一套自动回收机制。当内存压力上升低于watermark水位线时内核的kswapd后台线程开始扫描 LRU 链表优先回收Inactive(file)中的页。如果回收速度跟不上分配速度分配路径就会触发直接回收direct reclaim——这是同步阻塞操作进程必须等内核腾出空闲页才能继续执行。控制回收行为的核心参数来自sysctl# 在 ecs-fddb-0001 上采集sysctlvm.min_free_kbytes vm.dirty_ratio vm.dirty_background_ratio vm.swappiness vm.vfs_cache_pressurevm.min_free_kbytes 67584 vm.dirty_ratio 20 vm.dirty_background_ratio 10 vm.swappiness 0 vm.vfs_cache_pressure 100参数本机值作用与影响min_free_kbytes~66 MB保留的最低空闲内存。逼近此值触发 direct reclaim——阻塞操作是 load 飙高的根因之一dirty_ratio20%脏页占可用内存超过此比例写进程被阻塞强制同步回写dirty_background_ratio10%脏页超过此比例后台线程开始回写不阻塞业务swappiness0倾向保护匿名内存内存紧张时优先回收文件缓存——Page Cache 更易失vfs_cache_pressure100越大越积极回收 dentry/inode 等元数据缓存本机swappiness0是云镜像常见默认值。这意味着内存紧张时内核优先回收 Page Cache 而非 swap 匿名页——Page Cache 的易失性被进一步放大这也是实验 5 中抖动问题的内因之一。四、案例篇 · 难以回收导致 load 飙高4.1 实验设计我们模拟一个极端场景Page Cache 被不断冲刷留不住。准备一个4GB 的大文件/bigfile启动16 个后台读者while true; do cat /bigfile /dev/null; done持续读文件同时启动一个每秒执行echo 1 drop_caches的循环——强制回收刚装进去的读缓存后台采集vmstat 1、iostat -x 1、sar -B 1并周期性打印 loadavg 与 D 状态进程数。4.2 真实数据load 从 0.8 飙到 5.12基线压测前loadavg: 0.81 0.37 0.16 2/268 9377 iostat: vda %util 7.49%磁盘基本空闲load 时间线时间点loadavg (1min)D 状态进程数说明t0s0.810基线t5s2.021616 个 cat 全部卡在 D 状态t10s3.1416持续攀升t15s4.179偶尔有读者跑完一轮t20s5.1212仍在上升远未到上限vmstat 1 节选核心证据procs r b swpd free buff cache bi bo us sy id wa st 18 0 0 8598328 5264 6597956 4155 6553 0 0 97 2 0 17 0 0 10004784 1032 5206040 996 0 1 99 0 0 0 8 7 0 13170804 1032 2052440 131120 0 1 85 0 14 0 1 15 0 14828600 1476 404332 122760 88 0 15 0 85 0 0 16 0 15036492 140 191152 120836 0 0 12 0 89 0稳态下bblocked列长期10~16waiowait≈87~89%ididle接近 0。iostat -x 1 稳态样本avg-cpu: %user %nice %system %iowait %steal %idle 0.15 0.00 11.11 88.27 0.00 0.46 Device r/s rkB/s r_await aqu-sz %util vda 4510.0 120564.0 1.70 7.66 90.90sar -B 1pgpgin/s fault/s majflt/s 136500.00 3165.00 7.00 121096.00 7138.00 109.00 ... Average: 116817.47 798.26 13.584.3 因果链为什么 load 会飙高整个链条可以浓缩为 4 步每秒 drop_caches → 缓存被清空 → 16 个读者全去读盘 → 读盘走 D 状态不可中断睡眠 → D 状态进程计入 load average → load 从 0.8 飙到 5.12根因一句话Page Cache 本该替你挡住磁盘 IO当它留不住时读请求全部落盘进程在不可中断睡眠D 状态中排队load average 随之被抬高。D 状态进程数直接计入 load average——这是很多运维误以为CPU 繁忙但实际是 IO 瓶颈的根源。sar -B 的pgpgin/s约 117 MB/s持续从磁盘读页、majflt/s约 14每秒 14 次必须读盘的主要缺页这些都是缓存不断失效、命不中的铁证。4.4 生产建议drop_caches不要进 cron不要当常规操作。它会让热点缓存失效制造人为的冷启动监控vmstat的 b 列。b 0持续出现说明有进程在 D 状态阻塞要么是 IO 瓶颈要么是 direct reclaim监控/proc/loadavg与vmstat的 r/b 列联合看load 高但rrunning低 → 大概率是 D 状态堆积而非 CPU 不够如果是因为内存紧张导致 Page Cache 被自动回收而触发 D 状态阻塞就要调整vm.min_free_kbytes或vm.vfs_cache_pressure让回收更平滑。五、案例篇 · 容易回收引起业务读延迟抖动5.1 实验设计对比热点文件有缓存与缓存被回收无缓存两种情况下读同一文件的耗时与磁盘压力差异。场景 A有缓存先 warm 一遍 →time cat /tmp/pc_testiostat/sar -b场景 B无缓存echo 3 drop_caches→time cat /tmp/pc_testiostat/sar -b5.2 真实数据99 倍的延迟差距场景 A有缓存vmtouch 确认 100% 驻留[有缓存] 读取耗时 .166154837 秒有缓存期间 iostatvda rkB/s7255 ... %util 9.25 接近空闲对比 sar -b 有缓存tps rtps wtps bread/s bwrtn/s 2.00 0.00 2.00 0.00 32.00 ← rtps0几乎不读盘场景 B无缓存echo 3 清空后[无缓存] 读取耗时 16.475804879 秒无缓存期间 iostatvda r/s480 rkB/s122628 r_await4.09 ... %util96.40 vda ... %util96.00 / 92.60 / 96.80 / 59.90 / 96.30 磁盘打满sar -b 无缓存tps rtps wtps bread/s bwrtn/s 1090.00 1090.00 0.00 481544.00 0.00 607.00 607.00 0.00 245856.00 0.00 ... Average: 664.40 654.40 10.00 292796.80 89.605.3 数据对比指标有缓存场景 A无缓存场景 B差距读 2GB 耗时0.166 秒16.48 秒≈ 99 倍磁盘 %util~9%~96%10 倍rtps读请求/s0~654∞bread/s读流量0~287 MB/s∞5.4 根因分析一次缓存未命中就把读延迟从亚秒级拉到十几秒级。99 倍的差距在业务侧就是接口突然从 1ms 变成 1s的典型抖动。而vmtouch收尾验证发现无缓存读完之后该文件又被重新装回了 Page Cache100% Resident。这说明抖动发生在缓存刚被清掉的那个瞬间——一旦缓存回温性能又恢复正常。但回温的过程需要完整读一遍磁盘这段时间的延迟就是灾难。根因一句话Page Cache 命中是纳秒级的内存访问一旦被回收下一次访问必须触发磁盘 IO毫秒级业务出现明显抖动。这正是线上明明磁盘没满某个接口却突然变慢的典型内核根因。六、分析篇 · 如何判断问题是否由 Page Cache 产生6.1 排查清单当怀疑业务抖动与 Page Cache 有关时按以下步骤排查第一步看整体内存水位free-hgrep-E^(Cached|Buffers|MemAvailable|AnonPages)/proc/meminfoMemAvailable如果远低于总内存——说明系统内存紧张Page Cache 可能正在被大量回收。第二步看缓存是否命中sar-B1关注pgpgin/s每秒从磁盘读入页的量和majflt/s每秒主要缺页次数。如果pgpgin/s持续高位几十 MB/s 以上说明大量读请求走盘缓存未命中。第三步看进程是否在 IO 阻塞vmstat1重点关注bblocked列。b 0持续出现说明有进程在 D 状态等待 IO 或内存回收。联合iostat -x 1看%util和aqu-sz确认磁盘是否为瓶颈。第四步vmtouch 定点取证vmtouch /path/to/suspected/file直接看业务热点文件到底有多少页在内存里。如果Resident Pages比例很低说明缓存不住。第五步检查/proc/vmstat中的关键指标grep-Enr_file_pages|pgpgin|pgpgout|pgsteal|pgscan/proc/vmstatpgscan_kswapd/pgscan_direct后台回收 vs 直接回收的次数。pgscan_direct高说明频繁发生 direct reclaim——这是最严重的阻塞场景。pgsteal_*回收成功页数与 pgscan 对比看回收效率。6.2 调优方向场景推荐调优原理内存紧张 频繁 direct reclaim适当调大vm.min_free_kbytes提前启动 kswapd 后台回收避免分配路径阻塞小文件密集 元数据缓存频繁被回收调低vm.vfs_cache_pressure如 50保护 dentry/inode 缓存减少 major fault写多读少 脏页突发回写 IO 风暴调低vm.dirty_ratio如 10%vm.dirty_background_ratio如 5%更早、更平滑地启动后台回写避免瞬间大 IO读多写少 读缓存易失调高vm.swappiness如 10~20如果系统有 swap让内核在内存紧张时优先 swap 匿名页保护文件缓存热点文件被意外回收检查是否误用drop_caches用mlock()或vmtouch -t锁定关键文件强制驻留不让内核回收七、本模块要点速记Page Cache 是文件读写的必然产物不是可选的功能。内核用它避免重复读盘提升 IO 性能。/proc/meminfo的 Cached ≠ buff/cache。Cached是文件数据缓存可回收Buffers是块设备元数据缓存Shmem计入 Cached 但不可回收。drop_caches的 1/2/3 语义1文件页2slab 元数据3两者。千万别把它当常规运维手段——它会清掉热点缓存制造人为抖动。D 状态进程直接计入 load average。load 高不一定是 CPU 忙很可能是 IO 阻塞或 direct reclaim。联合vmstat b列和iostat %util判断。缓存命中 vs 缺失99 倍延迟差距。有缓存读是亚秒级无缓存是十几秒级。一次缓存回收就能让业务接口从 1ms 变成 1s。排查三板斧free -h看水位 →sar -B 1看 pgpgin/majflt →vmstat 1看 b 列。定点用vmtouch确认文件驻留比例。调优是系统工程min_free_kbytes、dirty_ratio、swappiness、vfs_cache_pressure四个参数决定了 Page Cache 的产生→回收→再平衡节奏需结合业务模型读多写少 / 写多读少 / 小文件密集来调整。八、下篇预告Page Cache 的管理是内存怎么给文件缓存用的问题。但更常见的线上事故是——内存泄漏进程分配了内存却不释放或者内核 slab 持续增长不回缩最终 OOM 杀进程。下一讲《Linux 内核技术实战课 · 内存泄漏模块从 slab 泄漏到进程 OOM 的完整追溯》我们将继续用真实实验数据演示用/proc/slabinfo和slabtop追踪 slab 泄漏用kmemleak扫描内核态内存泄漏用memcgperf定位用户态内存泄漏真实 OOM 案例的 dmesg 日志解读。敬请期待。本文全部实验数据均来自ecs-fddb-0001Ubuntu 24.04 / Kernel 6.8 / 8C16G脚本与完整日志见scripts/目录。欢迎在评论区交流实操中的疑问与踩坑。[exit0]