什么是操作系统的多级缓存?
操作系统的"多级缓存"不是一层东西,而是从寄存器到远端存储的整条层次结构,外加内核自己在内存里维护的几类缓存 (页缓存、目录项缓存、inode 缓存)。核心思想只有一句:把热数据放在更快、更近、更贵的介质上, 用空间换时间,用局部性(locality)换命中率。
1. 存储金字塔
容量小、速度快、单价高
┌──────────────────────────┐
│ 寄存器 Register <1ns │ 编译器分配
├──────────────────────────┤
│ L1 Cache 32~64KB ~1ns │ 每核私有
├──────────────────────────┤
│ L2 Cache 1~2MB ~4ns │ 每核私有
├──────────────────────────┤
│ L3 Cache 16~64MB ~15ns │ 多核共享
├──────────────────────────┤
│ 内存 DRAM 几十GB ~100ns │
├──────────────────────────┤
│ 页缓存 PageCache ← 内核用内存缓存磁盘
├──────────────────────────┤
│ SSD 数百GB~TB ~100us │
├──────────────────────────┤
│ HDD / 网络存储 ~10ms │
└──────────────────────────┘
容量大、速度慢、单价低
相邻两层之间通常差一个数量级,所以"命中"与"未命中"的代价差距极大:L1 命中约 1ns,一次缺页读盘可能上百微秒, 差了五个数量级。
2. 操作系统自己管的那几层
| 缓存 | 缓存什么 | 命中后省掉什么 |
|---|---|---|
| 页缓存(page cache) | 文件内容按页缓存 | 一次磁盘读 |
| 目录项缓存(dentry cache) | 路径名到 inode 的映射 | 逐级解析目录 |
| inode 缓存(icache) | 文件元数据 | 读磁盘 inode |
| 块设备缓冲 / 块层队列 | 待合并的 IO 请求 | 多次小 IO |
| swap 与匿名页 | 换出的匿名内存 | 重新分配 |
| 预读窗口(readahead) | 提前拉取的后续页 | 后续读的等待 |
读文件时,内核先查页缓存,命中直接 copy_to_user 返回;未命中才向块层发 IO,读上来之后顺手放进页缓存,
并按预读窗口多读几页。写文件同理,先写页缓存并标脏,由回写线程(kworker/flush)在后台批量落盘。
read() ──► 页缓存命中? ──是──► 拷贝到用户缓冲区(无磁盘 IO)
│否
▼
提交块层 IO ──► 磁盘 ──► 填充页缓存 ──► 拷贝到用户缓冲区
3. 观察与调优
# 页缓存总量、脏页、回写中、可回收
grep -E 'MemTotal|MemFree|MemAvailable|^Cached|^Buffers|^Dirty|^Writeback' /proc/meminfo
# 某个文件有多少页在缓存里(0 表示完全没有)
fincore /var/log/app.log
# 命中率:直接看缓存未命中导致的读 IO
iostat -x 1 # 看 r/s 与 %util,若读很少说明缓存命中高
# 主动放弃某文件的缓存,避免大文件扫描污染热数据
vmtouch -e /data/dump.sql
// 顺序扫一遍大文件但不想污染页缓存:用 fadvise 提示内核
int fd = open("/data/dump.sql", O_RDONLY);
posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); // 加大预读
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); // 用完即弃
可调参数在 /proc/sys/vm/ 下,最常用的是 dirty_ratio(脏页占比上限,超过则写进程被阻塞)、
dirty_background_ratio(超过则后台开始回写)、vfs_cache_pressure(回收 inode/dentry 的倾向)。
4. 常见追问
Q:free 显示内存快没了,是不是内存泄漏?
不是。Linux 会把空闲内存尽量用作页缓存,free 很小是常态。看 MemAvailable(可回收 + 空闲)才有意义;
真正紧张时 MemAvailable 会显著下降,并伴随 swap 换入换出。
Q:缓存(cache)和缓冲(buffer)有什么区别?
缓冲是为了"对齐速度差、攒够再发",比如写合并;缓存是为了"下次还要用",比如页缓存读命中。
/proc/meminfo 里的 Buffers 主要指块设备的元数据缓冲,Cached 才是文件页缓存。
Q:多级缓存会不会有一致性问题? 会。CPU 层靠 MESI 之类的缓存一致性协议;内核页缓存与磁盘之间靠回写保证最终一致,所以掉电可能丢最近几秒的写。 数据库因此常绕过页缓存(O_DIRECT)自己管 buffer pool,避免"双缓存"浪费内存。
小结
多级缓存的本质是用局部性换性能:CPU 缓存藏内存延迟,页缓存藏磁盘延迟,CDN 藏网络延迟。 面试时把它讲成"一条从寄存器到磁盘的层次链 + 内核的页缓存/目录项缓存/预读回写机制", 再补一句"页缓存吃掉了空闲内存,所以 free 小不等于内存不足",基本就完整了。