虚拟地址、物理地址与虚拟内存是什么
内存管理真正难的地方,不在“内存条有多大”,而在程序看到的地址为什么和真实内存地址不是一回事。
这背后对应的就是虚拟地址、物理地址和虚拟内存。
一、什么是物理地址
物理地址就是内存条上的真实地址,也就是 CPU 最终真正访问到的内存位置。
如果没有地址转换机制,程序就必须直接使用物理地址。这种方式实现简单,但问题很多。
二、什么是虚拟地址
虚拟地址是进程运行时看到和使用的地址。
程序员在代码里接触到的变量地址、数组地址、对象地址,本质上通常都属于当前进程的虚拟地址空间,而不是真正的物理地址。
程序访问虚拟地址后,最终需要通过操作系统和硬件把它翻译成物理地址,才能真正读写内存。
三、为什么不能让程序直接使用物理地址
如果程序直接使用物理地址,会带来几个严重问题:
- 进程之间难以隔离,一个进程可能破坏另一个进程的内存
- 程序装载不灵活,如果地址写死,就要求每次都装到固定位置
- 资源利用率低,很难灵活复用零散内存
- 难以实现内存保护、共享和按需调页
所以现代操作系统几乎都会引入虚拟地址空间这层抽象。
四、什么是虚拟地址空间
虚拟地址空间可以理解成:
一个进程可见的全部虚拟地址范围。
每个进程通常都有自己独立的虚拟地址空间。不同进程即使使用相同的虚拟地址,底层映射到的物理地址也可以完全不同。
这就是进程隔离的重要基础。
五、进程的虚拟地址空间里,哪些区域是合法的
进程虽然理论上有很大的虚拟地址范围,但真正已经建立为有效区域的,通常只有其中一部分,比如:
- 代码段
- 数据段
- BSS
- 堆
- 栈
- 动态库映射区
mmap建立的映射区
这些区域对当前进程来说是合法可访问的,但“合法”不代表其中每一页都已经有物理页框。
大量尚未映射的空白地址区间,对当前进程来说通常是非法访问区域,一旦直接读写,往往会触发异常。
六、堆空间和 mmap 区域为什么会动态变化
堆和 mmap 区是进程地址空间中最典型的动态区域。
1. 堆
malloc、new 之类的运行时内存分配,通常会先从堆空间里切出一块给程序使用。
如果堆空间不够,分配器还可能继续向操作系统申请更多虚拟地址空间,让堆增长。
2. mmap 区域
mmap 区会随着:
- 文件映射
- 共享内存映射
- 匿名映射
- 动态库加载
- 大块内存申请
不断新增或释放。
所以堆和 mmap 区域都会随着进程运行动态变化。
七、malloc 返回后,为什么第一次访问仍然可能缺页
malloc 成功返回,说明这段虚拟地址对当前进程已经是合法可访问的。
但这并不代表:
- 所有下级页表都已经建好
- 这块内存当前已经有物理页框
- 这块页现在一定驻留在 RAM 里
因此第一次真正访问时,操作系统可能才会:
- 按需创建缺失的下级页表
- 分配物理页框
- 建立映射
- 必要时把页从磁盘或 swap 调回内存
所以“地址合法”和“页已经在物理内存中”不是一回事。
八、什么是虚拟内存
虚拟内存是一套完整的内存管理机制。
它的核心思想是:
- 进程访问的是虚拟地址
- 虚拟地址通过页表等机制映射到物理地址
- 不是所有虚拟页都必须同时驻留在物理内存中
- 不常用的页可以暂时不在内存里,需要时再调入
从程序视角看,它像是在使用一块连续、独立、很大的内存;从操作系统视角看,这块内存其实是被映射和调度出来的。
九、虚拟内存的主要作用
虚拟内存最重要的几个作用是:
- 给每个进程提供独立地址空间,实现隔离
- 提高内存利用率,只把当前常用的页放在 RAM 中
- 让程序装载更加灵活,不依赖固定物理地址
- 支持按需分配、按需调页、共享和权限控制
十、swap 和虚拟内存的关系
这两个概念很容易混,但不能画等号。
1. 虚拟内存
虚拟内存是一整套地址抽象和页式管理机制。
2. swap
swap 是磁盘上的交换空间,是虚拟内存机制中的一种后备存储手段。某些暂时不用的页可以被换到 swap 中,等再次访问时再换回内存。
所以更准确的说法是:
虚拟内存是机制,swap 是这套机制中的后备存储之一。
并不是所有页都一定会进 swap:
- 一些页可能本来就来自文件,换出后以后可以直接从文件重新读回
- 一些页可能只是尚未真正分配物理页框
十一、总结
物理地址是内存条上的真实地址,虚拟地址是进程运行时看到的地址。每个进程都有自己的虚拟地址空间,但真正合法可访问的通常只是其中一部分映射区域。虚拟内存通过地址映射、按需分配和按需调页,让程序看起来拥有一块连续独立的大内存,同时实现进程隔离和更高的内存利用率。swap 则是虚拟内存机制中的后备存储手段,不等于虚拟内存本身。
