理解虚拟内存:程序看到的地址为什么不是真实内存

简介: 虚拟内存通过页表、TLB 与缺页机制,实现安全高效的内存管理。

这几天学习了「《Virtual Memory: A Deep Dive into Page Tables, TLBs, and Linux Internals》」里面关于虚拟内存的知识,这篇文章我们来聊聊一个操作系统里的基础概念:虚拟内存

学过「操作系统」的小伙伴可能知道,进程运行时看到的地址,并不一定等同于真实的物理内存地址。它看到的是一套由操作系统和硬件共同维护的虚拟地址空间。CPU 访问内存时,还需要通过页表、TLB 等机制,把虚拟地址翻译成物理地址。

原文讲得很细,从虚拟地址空间、页表、TLB、缺页异常,一路讲到 mmap、写时复制和 Linux 内核行为,不太适合作为一篇简单的入门学习内容。所以这篇文章会做一个简化版梳理,帮助你先理解几个基本问题:虚拟内存解决什么问题,虚拟地址如何映射到物理内存,缺页异常为什么存在,以及这些机制如何影响我们理解程序内存和性能问题。

为什么程序不能直接使用真实内存?

我们知道日常工作中,一台电脑会经常同时开着浏览器、编辑器、终端,连着数据库,以及跑着一堆后台进程。如果每个程序都直接拿着物理内存地址读写,就会遇到两个问题:第一,程序之间如何协调。程序 A 不知道程序 B 是否占用了某块物理内存,如果两个程序直接读写同一块物理地址,就可能互相覆盖数据,导致程序异常甚至系统不稳定。第二,安全性很差。程序 A 中的普通 bug,可能直接修改甚至破坏另一个程序的数据,导致程序异常。

所以,操作系统开始给每个进程安排了一套自己的“地址空间”。在进程眼里,它好像拥有一大片独立内存;但在操作系统眼里,这些地址还需要再翻译成真正的物理内存位置。

这就是虚拟内存的核心作用:让每个进程看到一套独立、受控、可管理的内存视图。

进程看到的内存布局

一个进程的虚拟地址空间通常会被分成几个区域:代码段、数据段、BSS、堆、内存映射区域、栈,以及内核保留区域。每个区域的分工如下:

  • 代码段放程序指令;

  • 数据段放已初始化的全局变量和静态变量;

  • BSS 放未初始化或零初始化的全局变量;

  • 堆用于运行时动态分配,比如 mallocnew

  • 栈用于函数调用、局部变量、返回地址等;

  • 中间的大块区域可以放共享库、文件映射、大块匿名内存分配。

上图就是各区域分工的直观体现,我们可以看到 Stack、Heap、BSS、Data、Text / Code、memory-mapped region 在地址空间里的位置。

在常见的 48-bit x86-64 虚拟地址模式下,虚拟地址最多可以表示 2^48 个字节位置,也就是 256 TiB 的地址空间。这里的 256 TiB 指的是“可寻址范围”,不代表机器真的有这么多物理内存。Linux 通常会把这段虚拟地址空间分成两部分:低地址区域留给用户态进程,高地址区域用于内核映射。对应到上图,就是 low address 和 high address。

这也变相地解释了一个常见疑问:为什么一台只有 16GB / 32GB 内存的机器,进程却能拥有远大于物理内存容量的虚拟地址空间。虚拟地址空间的大小和真实 RAM 容量,本就是两码事。虚拟地址的空间可以很大,但只有被进程实际使用、并且建立了有效页表映射的部分,才会进一步对应到物理内存或页缓存中的数据页。

地址翻译的基本过程

下面轮到页表出场了。

操作系统不会按“每一个字节”去管理映射关系,那样太细、太繁琐了。它通常按页来管理。常见的页面大小是 4KB。虚拟地址空间被切成一页一页的虚拟页,物理内存也被切成一块一块的页框。

当程序访问一个虚拟地址时,CPU 里的 MMU,也就是内存管理单元,会根据页表把它翻译成物理地址。在 x86-64 的四级页表结构中,虚拟地址会被拆成多个字段,分别用于索引 PGD、PUD、PMD、PTE,最后 12 位作为页内偏移。

寻址过程:

上图解释了“一个虚拟地址是怎么被拆开,并逐级查表的”。把它想成查地图的话,大概流程就是:先用第一段地址信息找到大区 PGD;再用第二段找到街区 PUD;再用第三段找到楼 PMD;最后用第四段找到房间 PTE;最后的页内偏移告诉你具体是哪一个字节。

这样做的好处是:不用为整个巨大地址空间提前准备一张完整大表。只有真正用到的区域,才需要建立相应的页表结构。页表的层级是稀疏的,只为实际使用的地址空间分配结构,避免平铺页表带来的巨大开销。

连续地址背后的物理映射

这是虚拟内存里很重要的一点。程序看到的数组可能是连续地址。但这些虚拟页映射到物理内存时,可以分散在不同位置。程序并不需要知道这些细节。它只看到一段连续空间;页表负责把这些虚拟页指向真实的物理页框。

这个图解释了什么叫“程序看到连续,物理上可以分散”。物理映射也是虚拟内存让系统更灵活的地方。操作系统可以把不同进程的物理页框交错放在 RAM 里,同时保证每个进程看到的仍然是一套干净、连续、独立的地址空间。上图就在说这一点:相邻虚拟页可以落在相距很远的物理页框中,不同进程的页框也可以交错分布在物理内存里。

TLB:地址翻译的缓存层

每次访问内存都查页表,会不会很慢?答案是:会。所以硬件还准备了一个缓存:TLB(全称:Translation Lookaside Buffer,地址转换后备缓冲器)。

TLB 可以理解成“地址翻译缓存”。如果某个虚拟页到物理页框的映射刚刚查过,MMU(全称:Memory Management Unit,内存管理单元)下次就可以先查 TLB。命中之后,不需要完整走一遍页表。TLB 会自行缓存已经完成的地址翻译;只有 TLB 未命中时,MMU 才需要进行完整的页表遍历。程序的访问模式会显著影响 TLB 命中率。小工作集、重复访问的循环、复用的缓冲区,通常更容易保持 TLB 友好。

上图展示了 CR3 和四级页表遍历的大致路径。结合 TLB 来看,它也能解释为什么内存访问模式会影响性能:同样是读数据,顺序访问、局部性好的访问,往往比到处跳着访问更友好。

内存申请背后的按需分配

可能会有人以为,程序一申请内存,操作系统就立马分配对应的物理内存。实际上,操作系统会更懒一点。

操作系统可以先给你一段合法的虚拟地址范围,等你真正访问它时,再分配物理页框。这叫 demand paging,也就是按需分页。

比如程序调用 malloc 后,可能已经拿到了一段可用的虚拟地址范围。但这不代表对应的物理内存已经全部分配好了。只有当程序第一次读写某一页时,CPU 发现页表里没有有效映射,就会自动触发 page fault,也就是缺页异常。内核接手后,先检查这个地址是否合法。如果合法,就分配一个物理页框,更新页表,然后让程序继续执行。这个过程可以概括为:page fault 发生后,内核检查 faulting address 是否落在有效 VMA 内;如果合法,就分配物理 frame、更新页表并恢复执行;如果不合法,就会变成 segmentation fault。

上图为“程序访问 → MMU 发现 present=0 → 内核处理 → 更新 PTE → 程序继续运行”整个流程是如何运作的。有时候,我们的程序会看起来申请了很多内存,但实际 RSS(全称:Resident Set Size,常驻集大小)没有立刻涨那么多。因为虚拟地址可以先被预留出来,物理页框往往等到真正访问时才会分配并建立映射。

写时复制背后的进程复制

虚拟内存还支撑了一个很经典的机制:copy-on-write,写时复制。

在 Unix/Linux 系统里,fork() 会创建一个子进程。我们可能会觉得子进程要复制父进程的整个地址空间,这应该很贵。但系统很聪明,不会一开始就把所有内存都复制一遍。

它会让父进程和子进程先共享同一批物理页,并把相关页标记成只读。等其中一个进程真的要写某一页时,CPU 再触发权限异常。然后内核来复制那一页,给写入方一份私有副本。

下图展示了这个过程:fork() 后两个页表先指向同一批物理 frame;当 Alloca 写入 page A 时,内核分配新 frame、复制内容,并只更新 Alloca 的 PTE。

这个机制让 fork() 的执行成本变低了。尤其是常见的 fork + exec 模式里,子进程马上加载新程序,很多旧页面根本没有复制的必要。

mmap 背后的文件映射

虚拟内存还有一个常见用途:mmap,一种把文件或内存区域映射到进程虚拟地址空间里的机制。普通 read() 操作读取文件时,数据通常会先进入内核的页缓存,再复制到用户态 buffer。 mmap() 会把文件的一段内容映射到进程地址空间里,程序可以像访问内存一样访问文件内容。

上图为 read()mmap() 的 I/O 路径对比。使用 read() 时,数据会从磁盘读入页缓存,然后再复制到进程的用户态缓冲区。使用 mmap() 时,进程的 PTE 会映射到承载页缓存数据的物理页框上,从而省去从页缓存到用户态缓冲区的这次复制。代价是,mmap() 不再通过显式的 read 调用来完成读取,而是需要承担缺页异常和页表管理带来的开销。

小结

虚拟内存听起来很底层,但它会影响很多日常开发问题:

  • 为什么空指针访问会崩?

  • 为什么栈溢出会触发 segfault?

  • 为什么进程虚拟内存很大,实际占用却没那么大?

  • 为什么随机访问大数组可能很慢?

  • 为什么 fork() 没有想象中那么贵?

  • 为什么 mmap() 有时快,有时并不快?

这些问题背后都绕不开虚拟地址、页表、TLB、缺页异常和内核内存管理。

理解虚拟内存,不是为了记住一堆术语,而是为了知道:程序眼里的“内存地址”,只是操作系统和硬件共同维护出来的一层抽象。真正的数据在哪里、什么时候分配、能不能访问、访问是否高效,都要经过这一层机制来决定。

所以,下次你看到一个指针地址时,可以多想一步:这个地址看起来像真实位置,但它首先是一张地图上的坐标。真正把它带到物理内存里的,是 MMU、页表、TLB 和内核。

参考资料:

相关文章
|
13小时前
|
存储 数据库
HyDE :让 RAG 检索从"匹配关键词"升级到"理解意图"
HyDE(假设文档嵌入)是一种提升RAG检索效果的创新方法:不直接检索与查询相似的文档,而是先让LLM生成“理想答案”的假设文档,再对其嵌入检索。它绕过关键词匹配局限,聚焦语义意图,显著改善术语差异大、查询表述多样等场景下的召回质量,代码改动小、效果提升明显。
30 1
HyDE :让 RAG 检索从"匹配关键词"升级到"理解意图"
|
13小时前
|
人工智能 自然语言处理 安全
阿里云上线团队版Token Plan:一站式多模型订阅,解决企业规模化AI使用难题
阿里云上线团队版Token Plan,内置Qwen3.6、Kimi-K2.6等十余款主流多模态模型,支持坐席灵活分配、成本管控与多租户隔离,兼容Qoder、Cursor等主流Agent工具,提供标准/高级/尊享三档套餐,一站式解决企业AI规模化使用痛点。
33 1
|
17小时前
|
数据采集 传感器 编解码
基于STM32的可穿戴心率检测仪设计(数据采集+心率分析)
基于STM32的可穿戴心率检测仪设计(数据采集+心率分析)
|
18小时前
|
监控 安全 物联网
室内人员定位系统从技术原理到核心优势详解
该室内人员定位系统融合UWB、蓝牙、北斗与IMU技术,实现50厘米级高精度、无死角定位;具备防爆耐用硬件、多网传输、智能解算与云端平台,支持电子围栏、SOS报警、轨迹回放等闭环管理,适配工业、医疗、矿山等复杂场景,助力企业构建智能化、数字化人员安全管理体系。(239字)
|
18小时前
|
人工智能 自然语言处理 监控
5 分钟上手 AgentRun:从注册到第一个 Agent 运行
阿里云函数计算AgentRun让Agent上线仅需5分钟!告别繁琐运维,模型、提示词、工具由你掌控,容器、扩缩容、监控、灰度等全由平台自动托管。支持快速创建、代码部署、工作流编排等5种模式,开箱即用生产级能力。(239字)
|
18小时前
|
人工智能 JSON 数据可视化
【AI大屏】今晚失眠夜,Claude Code + 积木报表:AI 生成数据大屏,已达生产级水平
JimuReport AI专题研究 JimuReport积木报表 × Claude Code 一句话生成生产级数据大屏的实战观察[![告别拖拽:Claude Code + 积木报表 一句话生成数据大屏](https://oscimg.oschina.net/oscnet/up0ae082bb5
42 0
|
18小时前
|
人工智能 Java 开发者
Java做AI不行?2026年最大的认知误区
2026年,Java做AI已成现实:Spring官方集成DeepSeek,JBoltAI等框架支持MCP协议与Agent工程化落地。Java凭借稳定性、类型安全与现有系统优势,正成为企业级AI应用的首选底座——不换语言,即可构建可运行的AI服务。
34 1
|
15天前
|
人工智能 Java 数据库
DeepAgents 人工介入实战|LangGraph 实现 Agent 高危工具人工审批
本文详解基于 LangChain+LangGraph+DeepAgents 实现 Python 智能体人工介入实战,配置高风险工具中断审批、状态检查点保存与恢复,支持同意 / 拒绝 / 参数编辑,对比 Spring AI Alibaba 方案,附完整可运行源码与生产落地建议。
182 0
|
2天前
|
JSON NoSQL API
开源项目观察|ds4:本地 Agent 推理,不只是把模型跑起来
Redis作者antirez新开源项目ds4(DwarfStar 4),是专为DeepSeek V4 Flash设计的轻量级本地推理引擎。聚焦Agent场景,支持OpenAI/Anthropic API、Disk KV Cache复用、工具调用精准映射与长上下文优化,在MacBook等高端个人设备上实现高效端到端推理。
118 3
开源项目观察|ds4:本地 Agent 推理,不只是把模型跑起来
|
3天前
|
人工智能 前端开发 数据可视化
HTML is the new Markdown:来自 Claude Code 团队的实践
AI Agent兴起后,Markdown因简洁易编辑成为默认输出格式。但Anthropic工程师Thariq提出:HTML正成为“新Markdown”——它通过CSS、交互元素、图表与响应式布局,显著提升信息密度与可读性,更适合PR评审、设计原型、技术报告等复杂场景。业界共识渐明:Markdown适合作为AI与开发者的轻量底稿,HTML则担当面向人类的展示与协作层。
156 3
HTML is the new Markdown:来自 Claude Code 团队的实践

热门文章

最新文章