虚拟地址的生命周期🥰
虚拟地址的生命周期
虚拟地址的生命周期与它所属的进程 (Process) 的生命周期是紧密绑定的。我们可以将其划分为四个主要阶段:
第一阶段:孕育期 (Gestation) — 在编译链接时
-
产生: 严格来说,虚拟地址的“蓝图”在程序被编译和链接成可执行文件(如Windows的
.exe
或Linux的ELF文件)时就已经被规划好了。编译器将源代码转换为包含相对地址的目标代码,链接器则将多个目标文件和库组合起来,为整个程序创建一个统一的、线性的逻辑地址空间映像。例如,链接器会决定.text
段(代码段)从虚拟地址0x08048000
开始,.data
段(数据段)从另一个地址开始,等等。 -
状态: 在这个阶段,虚拟地址仅仅是文件中的元数据或偏移量。它还没有与任何实际的物理内存产生关联,只是一个存在于磁盘文件中的“规划图”。
第二阶段:诞生期 (Birth) — 在进程创建与加载时
-
产生: 当用户执行一个程序时,操作系统会创建一个新的进程。在这个过程中,虚拟地址空间才真正“活”了过来。操作系统的加载器(Loader)会:
-
为新进程创建一个进程控制块(PCB)。
-
为该进程创建一套地址映射表(即页目录和页表)。
-
解析可执行文件,根据“规划图”在进程的虚拟地址空间中划定区域(如代码区、数据区、BSS区、堆、栈等)。
-
将页目录表的基地址装入特定寄存器(如x86的CR3寄存器)。
-
-
状态: 从此刻起,一个独立的、私有的虚拟地址空间正式诞生。CPU在执行这个进程时,其发出的所有地址都将被视为这个空间内的虚拟地址。
第三阶段:活跃期 (Active Life) — 在进程执行时
-
使用: 这是虚拟地址最活跃的时期。当CPU执行该进程的指令时,它会不断地产生虚拟地址(用于取指令、读写数据)。这些虚拟地址会被CPU中的内存管理单元(MMU) 截获,并通过查询页表将其翻译成物理地址,最终访问物理内存。
-
变化: 这个时期的虚拟地址空间不是一成不变的:
-
堆的增长: 当程序调用
malloc()
或new
时,操作系统会扩展堆区域,凭空创造出新的可用虚拟地址供程序使用。 -
栈的增长: 当函数调用发生时,栈会向低地址方向增长,使用新的虚拟地址。
-
动态库加载: 程序可能会在运行时加载新的动态链接库(DLL/.so),这也会将新的库映射到虚拟地址空间的某个区域。
-
内存释放: 当调用
free()
或delete
时,对应的虚拟地址被标记为可用,但其映射关系通常不会立即撤销,可能会被后续的malloc
重用。
-
第四阶段:消亡期 (Disappearance) — 在进程终止时
-
消失: 当一个进程终止时(无论是正常退出还是异常崩溃),操作系统会回收其所有资源。在内存方面,操作系统会:
-
遍历该进程的地址映射表。
-
释放该进程占用的所有物理内存页框。
-
销毁该进程的页目录和所有页表。
-
-
状态: 一旦一个进程的页表被销毁,其对应的整个虚拟地址空间就彻底不复存在了。原来那些虚拟地址对于CPU和系统来说,重新变回了毫无意义的数字。
虚拟地址的使用范围
理解了生命周期,虚拟地址的使用范围就非常清晰了。
1. 范围的起点:CPU
虚拟地址的“管辖范围”始于中央处理器(CPU)。当一个进程在运行时,CPU内部的程序计数器(PC)存储的是下一条指令的虚拟地址,CPU执行的访存指令(如mov
)操作的也都是虚拟地址。
2. 范围的终点:内存管理单元(MMU)
虚拟地址的“生命”终结于内存管理单元(MMU)。MMU是CPU内部的一个硬件模块,它的职责就是接收来自CPU核心的虚拟地址,然后通过查询页表(和高速缓存TLB)将其翻译成物理地址。一旦地址离开了MMU,进入到物理地址总线上,它就变成了物理地址。主存(内存条)、内存控制器等硬件只认识物理地址。
3. 范围的边界:进程
虚拟地址最核心的范围限定就是进程 (Process)。
-
私有性: 每个进程都拥有自己的一套独立的、从0开始的虚拟地址空间。
-
隔离性: 进程A的虚拟地址
0x1000
和进程B的虚拟地址0x1000
是完全不同的两个地址,它们通过各自独立的页表,被映射到不同的物理内存位置。 -
上下文: 一个虚拟地址,如果脱离了它的进程上下文(即不知道它属于哪个进程,不知道该用哪张页表去翻译它),它就是没有意义的。操作系统在进行进程切换时,最关键的一步就是切换CR3寄存器,即更换当前生效的页目录表,从而切换到新进程的虚拟地址空间“宇宙”中。
总结一下范围:虚拟地址是操作系统为单个进程提供的、在CPU内部使用的一种地址抽象,其有效性由该进程的地址映射表所定义,并在MMU处被转换为物理世界的地址。