首页 › programming › 地址都是假的:虚拟内存神课深读

地址都是假的:一条虚拟内存神课深读

概述

每个程序员都写过指针,但很少有人停下来想一个问题:两个同时运行的程序,完全可以各自拥有一个地址 0x7ffd1234,而它们指向的却是两个毫不相干的内存位置——程序眼里的一切地址,都是假的。这层“集体幻觉”的制造者,就是操作系统里最精妙、也最被低估的机制:虚拟内存。Core Dumped 频道的这条虚拟内存视频,用干净利落的动画把“虚拟地址如何变成真实数据”的完整链路讲了一遍,是近年讲透这个主题的高口碑之作。本文按视频叙事逐层拆解,补上真实的历史出处与工程细节——没看过视频你能看懂,看过视频你能多懂一层。

视频基本信息

项目 内容
视频标题 How Does Virtual Memory Work?(虚拟内存是怎么工作的?)
所属系列 该频道的内存/操作系统底层系列(据频道页面)
频道 Core Dumped(YouTube 上专注计算机系统底层的知识频道)
发布时间 频道“内存”系列代表作之一,具体上线日期未能实时核验
热度情况 据频道页面与社区讨论,是该频道播放量最高的视频之一,常被高校操作系统课程、Reddit 与 Hacker News 讨论串引用为入门首选

说明:本文写作环境无法实时抓取 YouTube 页面,视频标题、发布信息与热度均为据频道页面及社区讨论的整理,精确数字请以频道页面为准。下文所有技术事实均标注了可独立查证的来源(Wikipedia、LWN、论文与官方文档),欢迎逐条核对。


一、核心主张拆解:视频讲了什么

要点一:每个程序都活在“独占整台电脑”的幻觉里

视频从一个反直觉的事实开场:你在程序里打印任何指针,看到的都是一个“看起来很具体”的地址,比如 0x7ffd4a3b。但这个地址不对应内存条上的任何一根线。它是虚拟地址(Virtual Address)——操作系统和 CPU 联手编造、并让所有进程信以为真的编号。

这层幻觉解决的是一个上古难题:如果程序直接用物理地址,两个程序想用同一块内存怎么办?一个程序越界写了别人的地盘怎么办?程序还能搬动位置吗?在没有虚拟内存的年代(裸机时代和早期批处理系统),这些问题要么无解,要么靠程序员手工“重定位”——把代码里所有地址手动加上一个偏移量。视频用一句话点破虚拟内存的本质:它在每个进程和物理内存之间插了一层翻译,让“程序看到的世界”和“真实的硬件”彻底解耦。

要点二:翻译发生在 CPU 里,单位是“页”

这层翻译不是软件模拟的,而是由 CPU 里的一个专用硬件——MMU(内存管理单元)——在每次内存访问时实时完成。翻译的基本单位不是字节,而是页(Page):x86-64 架构上一个页通常是 4KB。操作系统把虚拟地址空间切成一页页的“虚页”,把物理内存切成同样大小的“页框”,再用一张页表(Page Table)记录“哪个虚页放在哪个页框”。

视频在这里做了一次关键下钻:页表本身也存在内存里,如果给每个进程配一张“从每个字节映射到每个字节”的平铺大表,光表就要吃掉天文数字的内存。所以现代 CPU 用的是多级页表——以 x86-64 为例,一个 48 位虚拟地址被切成 4 段 9 位的索引加 12 位页内偏移,逐级查 4 张表。多级设计的妙处在于稀疏:程序没用到的地址区间,中间那级表可以直接置空,一页都不用分配。这就是为什么一个理论地址空间大得吓人的进程,页表却只占很小一块。

要点三:TLB——每次访问其实要查两次,除非有缓存

多级页表带来新问题:查一个地址最多要走 4 级表,每级都是一次内存访问——也就是说,没有优化的话,读一次数据实际要做五次内存操作。这个代价由另一块硬件来抹平:TLB(Translation Lookaside Buffer,地址翻译缓存)。它把最近用过的“虚页→页框”映射存在 CPU 片上,绝大多数访问都能命中,一次直达。

TLB 也是理解“大页(Huge Pages)”价值的关键:4KB 页在动辄几十 GB 的工作集下意味着上百万条映射,TLB 根本装不下;2MB 或 1GB 的大页让同样的硬件覆盖大得多的内存范围。视频用“翻译的缓存”这个比喻把 TLB、大页和页表三级串成一条线,这是整条视频信息密度最高的部分。

要点四:缺页中断——内存不够用了,怎么办

虚拟内存最华丽的能力,是允许页可以“不在”物理内存里。页表项里有个“在不在”的标志位:CPU 访问一个不在内存的页时,MMU 触发缺页中断(Page Fault),控制权交给操作系统,由它决定:

至于换谁出去?这是操作系统教科书里最经典的问题。经验法则就是“最近没用的先用”:硬件在页表项里维护访问位,操作系统据此做近似 LRU(最近最少使用)的淘汰决策。视频不忘补一刀:如果内存小到系统把时间全花在换页上、程序几乎不前进——这叫颠簸(Thrashing),是虚拟内存时代最著名的性能灾难形态。

要点五:真正的重头戏是副产品——隔离、共享、写时复制

视频结尾把视角拉高:换页(swap)其实是虚拟内存里最不重要的一环,它真正改变软件形态的是这几个副产品:

  1. 进程隔离:每个进程有独立的一套页表,A 进程的地址再怎么乱写,也翻译不到 B 进程的物理页里。操作系统的安全边界,一半是页表画出来的。
  2. 共享:两个进程可以各自虚拟地址不同,却指向同一批物理页——动态链接库就是这么被全系统共享的,一百个进程用 glibc,内存里只有一份代码。
  3. 写时复制(Copy-on-Write):Unix 的 fork() 之所以能瞬间复制一个进程,是因为它根本不复制内存——只复制页表,把所有页标成只读;父子进程谁先写,才触发缺页中断,把那一页真正复制一份。你的每一个命令行工具、每一次 CGI 请求,都在享受这个机制。
  4. 按需加载与内存映射文件(mmap):程序启动时不必把整个可执行文件读进内存,用到哪页加载哪页;数据库和游戏引擎偏爱 mmap,也是把“读文件”变成“访问内存”,把调度权交给内核。

二、知识与理论溯源:这些说法都有据可查

虚拟内存诞生于 1962 年的曼彻斯特。公认的第一个虚拟内存实现是英国曼彻斯特大学的 Atlas 计算机(Tom Kilburn 团队与 Ferranti 公司合作),它的“一级存储(one-level store)”让程序员面对一个统一的地址空间,由硬件和系统程序自动完成虚拟地址到磁鼓/磁心的搬运与翻译。Wikipedia 的 Atlas 词条与计算机史资料对此有详细记载。随后 MIT 的 Multics 项目把分页虚拟内存推向成熟,而 Multics 正是 Unix 的直系祖先——今天 Linux 内核里 mm/ 目录下的代码,血缘可以一路追到 1960 年代。

“工作集”理论的出处:操作系统该给每个进程留多少页才不颠簸?Peter J. Denning 在 1968 年提出工作集模型(Working Set Model),又在 1970 年发表综述论文《Virtual Memory》(ACM Computing Surveys),系统总结了这一领域——这两篇是操作系统课程的标准引文。“颠簸”一词也正是在这批文献中定型。

“缓存越多缺页反而越多”的悖论:Belady 在研究换页算法时发现了著名的Belady 异常——对某些替换算法(如 FIFO),给进程分配更多页框,缺页次数反而可能上升。这是面试和课堂里的常客,也是“凭直觉优化不可靠”的经典案例。

“每次访问是五次访存”的量级账:多级页表与 TLB 的成本分析,最系统的公开材料是 Ulrich Drepper(glibc 维护者)2007 年的长文《What Every Programmer Should Know About Memory》,其中对页表遍历、TLB 容量与大页收益的量化至今被反复引用。

页表曾是真实攻击目标:2018 年公布的 Meltdown 漏洞(Graz 技术大学 Moritz Lipp 等,USENIX Security 2018 论文)利用乱序执行的侧信道,绕过“用户态访问内核地址必被页表拦截”的检查去读内核内存——它恰恰证明了页表隔离是整个系统安全的基石;事后 Linux 内核推出的 KPTI(内核页表隔离)补丁,正是靠“内核态换一套页表”来止损。视频若提到“虚拟内存就是安全边界”,这条是最硬的现实注脚。

大页不总是好事:Redis、MongoDB 等数据库的官方运维文档都明确建议关闭透明大页(THP)——大页让 fork 后写时复制的粒度变粗、放大延迟毛刺。这是“视频讲原理、文档讲取舍”的典型落差,下文批判性审查里还会回到这一点。

手机与容器时代:Android 的低内存查杀(LMK)与 iOS 的 jetsam 机制,本质都是在虚拟内存的账本上做“谁的工作集最该牺牲”的决策;容器(cgroup 内存限额)超限后触发的 OOM kill,同样是缺页路径上“内存确实不够了”这条分支的现代形态。Drepper 那句被引用了快二十年的话今天依然成立:不看页错误,你就不知道程序真正的访存成本。


三、批判性审查:哪些可信,哪些要打个问号

证据充分的部分:地址翻译、多级页表、TLB、缺页处理、写时复制,这些是 Intel/AMD 架构手册与 Linux 内核文档白纸黑字的内容,视频的骨架没有任何“民间传说”成分。把 swap 降格为配角、突出 COW 与 mmap 的讲法,也符合当代 Linux 桌面与服务器“swap 用得极少、缺页主要服务按需加载与复制”的真实工作负载。

表述偏简化、需要平衡的部分:

  1. “虚拟内存 = 换页到磁盘”是最常见的误解。不少入门内容(包括一些同主题视频)把两者画等号。虚拟内存的本质是地址翻译与抽象,换页只是它可选的一个应用;禁用了 swap 的服务器照样满脑子缺页中断——只是缺的页来自文件而不是交换区。
  2. “每次访存都要走四遍页表”是教学简化。有了 TLB,绝大多数访问不付这个代价;即便 TLB 未命中,现代 CPU 有硬件页表遍历器,还有大页减少遍历次数。视频为了讲清机制把成本讲“满”了,实际性能账要按 TLB 命中率算。
  3. overcommit 不是虚拟内存的必然属性。Linux 默认允许“空头支票”式分配(malloc 轻易成功,真写入时不够再 OOM 杀进程),这是内核可调的策略(overcommit_memory);Windows 走的是另一条路,要求进程先做 commit 预约。把 Linux 的行为当成“虚拟内存的规律”,会在跨平台时踩坑。
  4. 大页的取舍被一笔带过。视频只讲了大页省 TLB 的收益;现实中透明大页是知名的延迟毛刺来源,数据库厂商集体建议关闭它。收益与代价都真实,取决于负载。
  5. LRU 的理想与现实。教科书上的 LRU 在硬件里不可行(每条访存都更新链表太贵),真实系统用的是时钟算法这类“够用的近似”。视频若按理想 LRU 讲,概念没错,但别拿它去预测具体内核行为。

总体评价:在系统底层题材里,这条视频属于“机制画对、比例略偏教学”的诚实作品;它的留白——策略差异、性能取舍、真实工作负载——恰好是读者自己补课最有性价比的地方。


四、认知提升与实用启示

启示一:重学一次 free -h,别再被“内存不足”吓到。Linux 把空闲内存大量用作页缓存——这正是虚拟内存按需加载的另一半:free 列里 available 才是真正的可用量,buff/cache 大不是病,是系统在替你给磁盘加速。"Linux ate my RAM"是流传多年的误会,理解页缓存的人不会被它骗第二次。

启示二:用缺页次数给程序做“CT”。延迟毛刺查不出来时,看看 major fault(需要动磁盘的缺页):/proc/<pid>/status、ps -o majflt 或 perf stat -e page-faults 都能看到。一个平时飞快的接口偶尔卡几百毫秒,经常就是某段冷代码或冷数据在那一刻被“按需加载”——知道了这一点,预热、mmap、大页才有明确的靶子。

启示三:给容器配内存限额时,把自己当 OOM killer 的律师。cgroup 限额不是“少用点”的软建议,而是缺页路径上的硬判决——超限即杀。配额前先弄清进程的真实工作集(RSS 而不是 VIRT),给突发留余量,并理解 Java/Go 这类运行时对“被杀”的反应完全不同。虚拟内存给了你幻觉,容器把幻觉关进了账本,两件事一起想才算懂。


五、一图看懂:一次访存的完整旅程

flowchart TD
    A[程序发出虚拟地址] --> B{TLB里有翻译吗}
    B -->|命中| H[直接得到物理地址]
    B -->|未命中| C[遍历多级页表]
    C --> D{页在内存吗}
    D -->|在| H
    D -->|不在| E[触发缺页中断]
    E --> F{缺页类型}
    F -->|数据还在文件里| G[从磁盘读入并填页表]
    F -->|写时复制页面| I[复制页面并解除写保护]
    F -->|物理内存不够| J[换出冷页或OOM杀进程]
    G --> H
    I --> H
    J --> H
    H --> K[MMU放行 本次访问完成]

延伸阅读

  1. Ulrich Drepper《What Every Programmer Should Know About Memory》(LWN.net 连载,2007)—— 关于页表、TLB、大页成本最系统的公开长文,看完再看任何性能文章都不怵
  2. 《How Linux Works》对应的在线常识站 "Linux ate my RAM"(linuxatemyram.com)—— 一页纸讲清页缓存与 available 的区别,五分钟纠正十年的误会

参考来源

  1. Virtual memory — Wikipedia — 虚拟内存历史(Atlas、Multics)、分页与换页机制的总览性词条
  2. Atlas (computer) — Wikipedia — 1962 年曼彻斯特 Atlas 与“一级存储”,公认的第一个虚拟内存实现
  3. What Every Programmer Should Know About Memory — Ulrich Drepper, LWN — 多级页表、TLB 与大页成本分析的原始出处(2007)
  4. MeltdownAttack.com — Meltdown/Spectre 官方站点,USENIX Security 2018 论文与技术细节
  5. Core Dumped — YouTube 频道页 — 本视频所在频道,标题、发布日期与播放量以频道页面为准

本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入