什么是操作系统的多级缓存?

#计算机网络#操作系统#面试八股

什么是操作系统的多级缓存?

操作系统的"多级缓存"不是一层东西,而是从寄存器到远端存储的整条层次结构,外加内核自己在内存里维护的几类缓存 (页缓存、目录项缓存、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 小不等于内存不足",基本就完整了。

笔记加载中…