Linux 虚拟内存与页缓存
Linux 虚拟内存与页缓存
本文讲清 Linux 内存管理的核心机制:虚拟地址如何映射到物理页、缺页与按需调页、页缓存如何加速 IO,以及 free 看到的数字到底是什么意思。
为什么需要虚拟内存
每个进程都以为自己独占一整块连续地址空间,实际物理内存由内核统一分配。虚拟内存带来三个好处:进程间隔离、可以分配比物理内存更大的地址空间、内存可以按页懒加载。
32 位进程地址空间约 4GB,64 位则是 128TB 级别,实际可用受物理内存和交换空间限制。
地址翻译:页表与 TLB
虚拟地址被切成页号和页内偏移,页号查页表得到物理页框号,再拼上偏移。为了节省页表本身的内存,Linux 用多级页表(x86-64 通常是四级:PGD → PUD → PMD → PTE)。
页表查询很慢,所以 CPU 里有 TLB 缓存最近用过的映射。TLB 命中就跳过页表;未命中才走多级查询。进程切换会刷新 TLB(PCID 可缓解),这也是上下文切换成本的一部分。
大页(HugePage)的作用就是减少页表层级和 TLB 压力:2MB 或 1GB 一页,同样内存只需更少的表项。
缺页与按需调页
malloc 只分配虚拟地址,并不立刻给物理页。第一次访问时才触发缺页异常,内核分配物理页并建立映射,这叫按需调页(demand paging)。缺页分几类:
- 匿名页缺页:堆、栈首次访问,分配新物理页。
- 文件页缺页:访问 mmap 的文件或可执行文件,从页缓存映射。
- 写时复制(COW):fork 后父子共享页,任一方写入时才复制。
缺页是正常的,但频繁的 major fault(需要读磁盘)会明显拖慢程序。
页缓存:读写的真相
Linux 把空闲内存几乎全部用作页缓存(page cache)。读文件时,数据先进入页缓存,下次命中就直接从内存返回;写文件默认是写缓存(write-back),先改页缓存并标记为脏页,由内核线程周期性回写磁盘。
这也解释了一个常见疑惑:free -h 里 buff/cache 很大、free 很小,并不代表内存不够,只是内核把没用到的内存拿去缓存了。真正要看的是 available。
free -h
# total used free shared buff/cache available
# Mem: 31Gi 12Gi 1.2Gi 300Mi 18Gi 18Gi
# available 才是应用还能拿到的量
mmap 与文件 IO
mmap 把文件映射进地址空间,读写内存就像读写文件,省掉一次用户态拷贝。适合大文件随机访问、多个进程共享同一份数据。
int fd = open("data.bin", O_RDONLY);
void *p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
// 直接按数组访问 p,缺页时内核从页缓存/磁盘加载
munmap(p, size);
close(fd);
注意:MAP_PRIVATE 的写入是 COW,不会落盘;MAP_SHARED 才会同步回文件。用完必须 munmap,否则地址空间泄漏。
内存回收与 OOM
物理内存紧张时,内核先回收可回收页:干净的文件页直接丢弃(有磁盘副本),脏页先回写再丢弃,匿名页则写入 swap(如果开了)。
回收不及就会触发 OOM Killer,按 oom_score 选一个进程杀掉。容器场景里,cgroup 内存超限会先触发 cgroup 级 OOM,只杀容器内进程。
# 查看内存与回收情况
cat /proc/meminfo
vmstat 1
# 查看某个进程的 OOM 评分
cat /proc/<pid>/oom_score
常见指标怎么读
| 指标 | 含义 | 关注点 |
|---|---|---|
available |
应用可用内存估算 | 真正的余量 |
buff/cache |
页缓存 + 缓冲区 | 大是好事,可回收 |
si/so(vmstat) |
swap 换入/换出速率 | 持续非零说明内存压力大 |
pgmajfault |
主缺页次数 | 高说明频繁读磁盘 |
dirty |
待回写脏页 | 过高可能造成写抖动 |
实战:内存问题的排查顺序
- 先看
free -h的available,确认是不是真的不足。 ps aux --sort=-rss找占用最高的进程。cat /proc/<pid>/status看VmRSS、VmSwap。smem -tk看 PSS,避免共享库重复计算。vmstat 1看si/so和wa,判断是否在换页或等 IO。- 容器里看 cgroup:
cat /sys/fs/cgroup/memory.max与memory.current。
最佳实践
- 不要看到
free低就加内存,先看available和 swap 活动。 - 大文件顺序读写用普通 read/write 即可,页缓存已经很好;随机访问再考虑 mmap。
- 数据库类应用可调
vm.dirty_ratio、vm.dirty_background_ratio控制回写节奏。 - 容器要设置合理的内存 limit,并给 JVM/Go 运行时配置匹配的堆上限。
- 关注 major fault 和 swap,它们比内存使用率更能反映真实压力。
- 大页适合数据库、虚拟化等 TLB 压力大的场景,但要预分配并评估碎片。
总结
| 机制 | 作用 |
|---|---|
| 虚拟地址 + 多级页表 | 进程隔离、按需分配 |
| TLB / 大页 | 加速地址翻译 |
| 缺页 | 懒加载、COW、文件映射 |
| 页缓存 | 读写都先过内存,回写磁盘 |
| 回收 + OOM | 内存紧张时的兜底 |
Linux 内存管理的核心思路是"把空闲内存尽量用起来,同时保证必要时能收回来"。理解页缓存和 available,就不会再被 free 的数字吓到。
- 点赞
- 收藏
- 关注作者
评论(0)