Linux 面试经典:buff 和 cache 到底有什么区别?用 vmstat 看懂 Page Cache 与 Buffer Cache

Linux 面试经典:buff 和 cache 到底有什么区别?用 vmstat 看懂 Page Cache 与 Buffer Cache

buffcache 都会使用内存临时存放数据,它们到底有什么区别?“我的 free 内存怎么只剩一点了,系统是不是快要出问题了?”

本文将简要介绍 Linux 的内存机制,说明 buffcache 各自缓存什么、为什么在现代内核中 buff 通常很小,并通过一个 vmstat 小实验,让两个指标分别“动起来”。

Buffer 与 Cache

教科书式的定义是:

  • Buffer(缓冲区):暂存“正在传输中”的数据。当数据在速度不匹配的组件之间传输时,例如应用程序写入磁盘,数据不会立刻到达目的地,而是先进入 buffer,让内核可以把零散写入合并为更大的批次,再批量写入磁盘。这对顺序 I/O尤其有效。
  • Cache(缓存):暂存“已经读取、之后可能再次使用”的数据。把常用数据保留在速度更快的存储层(RAM)中,下次使用时便不必再从较慢的磁盘读取。CPU 缓存、磁盘缓存和文件系统缓存都遵循同样的原则。

一句话总结:buffer 更注重协调数据传输,cache 更注重保留数据以便复用1 不过,这只是一般概念,不能直接理解成 Linux 中“buffer 只负责写、cache 只负责读”。普通文件的缓冲读取(buffered read)、缓冲写入(buffered write)和 mmap() 都会经过 Page Cache;写入会先把缓存页标记为 dirty page,之后再由内核写回存储设备。2 3

Linux 中的实际含义:Page Cache 与 Buffer Cache

上面的定义是通用概念,但在 Linux 内存管理中,BuffersCached——也就是 /proc/meminfo 中的两个字段——有更加具体的含义。4

字段 缓存对象 典型场景
Cached(Page Cache) 普通文件的内容,也包括 tmpfs / shmem;不包括 SwapCached catgrep、普通文件的 buffered read / write
Buffers(Buffer Cache) 块设备地址空间中的原始磁盘块,以及部分文件系统通过 buffer head 管理的磁盘元数据块 /dev/sdX 执行 buffered I/O(缓冲 I/O)、部分文件系统的日志或元数据 I/O

💡 内存统计中有两个容易忽略的细节

Cached 不全是磁盘缓存。 tmpfs / shmem 也包括在 Cached 中;如果只想粗略估算基于磁盘的文件缓存,可以查看 Cached - Shmem。在未配置 swap、且这些页面尚未释放时,这部分内存不能像干净的文件缓存页一样直接丢弃。5

并非所有文件系统元数据都计入 Buffers 只有部分文件系统的磁盘元数据块会使用 buffer head。VFS 的 inode 对象与 dentry 对象是独立的内存缓存,通常计入 Slab / SReclaimable6 4

这里还有一段值得了解的历史:早期的 Page Cache 和 Buffer Cache 是两套独立缓存;两者后来完成合并,并在 Linux 2.4 时期消除了文件数据写回所需的额外副本。现代内核中的 Buffer Cache 仍然存在,但 buffer head 会指向 Page Cache 中的页面。7 8 因此,现在的 Buffers 主要反映原始磁盘块,而不是普通文件内容;在多数工作负载中,它通常远小于 Cached

如果在面试或排障时遇到这个问题,可以给出更准确的回答:

Cached 主要是 Page Cache,也包括 tmpfs / shmem;Buffers 反映块设备的原始磁盘块,以及部分文件系统使用的元数据块;inode / dentry 对象则大多位于可回收的 slab 内存中。从 procps-ng 3.3.10 开始,free 默认使用 buff/cache 汇总显示,并加入 available;当前版本的 buff/cache 实际为 Buffers + Cached + SReclaimable9 10

从 I/O 栈看 Page Cache 与 Buffer Cache 的位置

展开整个 Linux I/O 路径后,两者的分工会更加直观。11

Linux I/O 路径:Page Cache 与 Buffer Cache 的位置

Linux I/O 路径:Page Cache 与 Buffer Cache 的位置

图中的文件系统元数据指磁盘上的元数据块;VFS 的 inode / dentry 对象则属于前面提到的 slab 缓存。

  • 文件系统路径:应用程序对普通文件执行 read() / write() → VFS → Page Cache。读取命中缓存时,数据直接从 RAM 返回;缓存未命中或需要写回 dirty page 时,I/O 才通过文件系统进入 Block Layer 与磁盘。2
  • 原始块设备路径:对 /dev/sdX 之类的块设备执行普通 buffered I/O 时,不经过已挂载文件系统的文件映射逻辑,但仍会使用该块设备自己的页缓存映射(page-cache mapping);这些页面计入 Buffers12
  • 部分文件系统也使用 buffer head 管理日志或磁盘元数据块,但 VFS inode / dentry 对象使用的是另一套 slab 缓存。
  • 两条路径最终都会汇入 Block Layer → Device Driver → 物理设备。数据库等自行管理缓存的应用程序可以使用 O_DIRECT 绕过 Page Cache;I/O 仍然会经过文件系统与 Block Layer,而不是直接调用硬件。2

使用 vmstat 观察 buff 与 cache

vmstat 是观察系统内存、I/O 和 CPU 的基础工具。vmstat 1 5 表示每秒采样一次,共输出 5 组结果。第一行数据是系统启动以来的平均值,之后各行才是每个采样区间的数值。13

$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 123456  7890  56789    0    0   100   200  300  400 10  5 80  5  0
 0  1      0 120000  7895  56800    0    0   150   250  320  410 12  4 78  6  0
 2  0      0 118500  7898  56810    0    0   180   220  330  420 9   6 82  3  0
 1  0      0 116800  7902  56825    0    0   120   210  310  430 11  3 81  5  0
 0  0      0 115000  7905  56835    0    0   140   230  300  390 13  4 77  6  0

与本文相关的字段包括:

  • free:空闲内存
  • buff:用作 buffer 的内存,对应 /proc/meminfo 中的 Buffers
  • cache:由 procps-ng 汇总计算的缓存值,当前为 Cached + SReclaimable
  • bi / bo:每秒从块设备读取或写入的 KiB;不受 vmstat -S 显示单位选项的影响13 14

上面的数值是示意数据。在实际观察中,如果 bi / bo 持续有读写量,而 free 下降、buffcache 上升,通常表示内核正在把块设备数据或文件数据保留在内存中。仅凭这一现象并不能说明内存异常。15

实验:让 buff 和 cache 分别动起来

只看定义仍然比较抽象,直接实验最容易理解。先清空缓存,建立干净的基线:

# 将 dirty page 写回磁盘,并清空 page cache、dentry、inode cache
$ sync
$ echo 3 | sudo tee /proc/sys/vm/drop_caches

drop_caches=3 会丢弃干净的 Page Cache 和可回收的 slab 对象,包括 dentry / inode 缓存。它不会丢弃脏对象(dirty objects),因此先执行 sync 可以让实验结果更容易解读。该接口适合测试和调试,不建议用作日常“释放内存”的手段,因为后续重建缓存会产生额外的 I/O 与 CPU 开销。16

场景一:读取普通文件 → cache 上升

先确认测试文件位于基于磁盘的文件系统,而不是 tmpfs。在一个终端运行 vmstat -y 1-y 会省略系统启动以来的第一行平均值),在另一个终端读取大文件:

$ dd if=/var/tmp/bigfile of=/dev/null bs=1M status=progress
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 7452100  2120   98500    0    0     0     0  120  210  1  0 99  0  0
 1  0      0 6890200  2135  652800    0    0 524288    0  850 1600  2 12 70 16  0
 1  0      0 6320400  2150 1214500    0    0 524288    0  860 1620  1 13 68 18  0

文件内容进入 Page Cache,因此在这组示意输出中,cache 每秒增加约 500 MiB,而 buff 几乎不变。实际增幅会受到文件大小、读取速度、已有缓存以及其他系统活动的影响。

场景二:直接读取原始块设备(raw block device)→ buff 上升

重新执行前面的 syncdrop_caches,然后使用 lsblk 确认设备名称,并把下面的 /dev/sdX 替换成实际设备。这个命令只读取设备并把数据丢弃到 /dev/null;不要把 ifof 写反,也不要加入 iflag=direct,否则会绕过需要观察的缓存:

$ sudo dd if=/dev/sdX of=/dev/null bs=1M count=1024 status=progress
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 7450300   2150  99200    0    0     0     0  115  200  1  0 99  0  0
 1  0      0 6980100 471300  99800    0    0 460800    0  830 1550  1 11 71 17  0
 1  0      0 6420500 1023800 100300   0    0 524288    0  855 1590  2 12 69 17  0

这次对块设备执行缓冲读取(buffered read),数据进入该设备的页缓存映射,而 /proc/meminfo 会把这些页面列入 Buffers,所以 buff 明显上升,cache 几乎不变。12

两个实验对比后,buffcache 的分工就很清楚了:普通文件内容主要体现在 cache 中,块设备映射中的原始磁盘块则体现在 buff。实验数值不必与示例完全相同;后台服务、文件系统和内核版本都可能让其他字段同时发生变化。

常见误解:free 很低不是坏事

最后,再回到开头那个常见的疑问:free 很低,是否意味着内存不足?Linux 会利用空闲 RAM 进行缓存,因此 free 很低本身并不代表系统发生故障。当应用程序需要内存时,可以回收干净的文件缓存和一部分可回收的 slab 内存;dirty page 必须先写回,正在使用的 slab 对象,或者在未配置 swap 时无法换出的 shmem,则不一定能立即回收。4 10

判断内存是否紧张时,不要只看 free

  • 查看 free 命令输出的 available 字段:它估算在不触发 swap 的情况下,还有多少内存可供新应用程序使用;计算时会考虑 Page Cache、可回收的 slab 内存和内存区域低水位线(zone low watermarks)。4
  • 查看 vmstatsi / so:持续的换入和换出(swap in / out)是内存压力的重要信号;仅仅看到已经使用了一些 swap 空间,并不代表系统当前正在发生内存颠簸(thrashing)。17

总结

  • 从一般概念来说,buffer 注重协调数据传输,cache 注重保留可复用数据;这不等于 Linux 中的“写入与读取”
  • Linux 的 Cached 主要反映 Page Cache,也包括 tmpfs / shmem;普通文件的缓冲读取和缓冲写入都会经过它
  • Buffers 主要反映块设备映射中的原始磁盘块,以及部分文件系统使用的元数据块;inode / dentry 对象通常位于可回收的 slab 内存中
  • Page Cache 与 Buffer Cache 在 Linux 2.4 时期完成统一,现代系统中的主要文件缓存是 Page Cache
  • 使用 vmstat 1 对比“读取普通文件与读取原始块设备”,可以清楚看到 cachebuff 分别上升
  • free 很低不等于内存不足,还需要进一步查看 available 和 swap 活动

参考资料

Eason Cao
Eason Cao Eason is an engineer working at FANNG and living in Europe. He was accredited as AWS Professional Solution Architect, AWS Professional DevOps Engineer and CNCF Certified Kubernetes Administrator. He started his Kubernetes journey in 2017 and enjoys solving real-world business problems.
comments powered by Disqus