虚拟地址与物理地址的转换机制解析
程序如何访问内存?
在执行指令或读写数据时,CPU需要与内存交互。它会发出请求:"请将地址0x10000处的数据返回"或"将计算结果写入0x20000"。这些地址通常来自指令中的立即数或寄存器值。
早期单任务系统中,程序可在固定物理地址(如0x8000)加载运行,无需担心冲突。但随着多任务需求出现——多个程序轮流执行——直接使用物理地址的方式暴露出严重问题:
- 地址冲突:不同程序可能使用相同内存区域,导致相互覆盖。
- 内存不足:大型程序无法完全载入有限物理内存。
- 可移植性差:程序需适配不同机器的内存布局。
解决方案:引入虚拟地址空间
现代操作系统为每个进程提供独立的虚拟地址空间。每个程序都"认为"自己独占从0开始的完整内存空间,而实际使用的物理内存由系统统一调度。这种逻辑上的地址称为虚拟地址(Virtual Address),它是抽象的、不真实存在的。
例如,通过objdump反汇编一个ELF可执行文件,可以看到类似输出:
0000000000401000 <main>:
401000: 55 push %rbp
401001: 48 89 e5 mov %rsp,%rbp
401004: 48 8d 3d fb 0f 00 00 lea 0xffb(%rip),%rdi
40100b: e8 d0 fe ff ff callq 400ee0 <puts@plt>
左侧列即为虚拟地址。所有用户程序的代码段通常起始于高位虚拟地址(如0x400000),这是链接器在生成可执行文件时设定的。
物理地址的作用
物理地址(Physical Address)是最终用于寻址硬件存储单元的真实地址。它被送至地址总线,经译码电路选择目标设备,包括主内存、显存、I/O寄存器等。
虚拟到物理的转换机制
地址转换本质上是一个映射函数:PA = f(VA),其中VA为虚拟地址,PA为物理地址。若纯用软件实现效率低下;若全靠硬件则缺乏灵活性。因此采用软硬协同方案——MMU(Memory Management Unit)。
MMU是集成在CPU中的专用硬件模块,其工作依赖于保护模式或长模式的启用。实模式下无法激活分页功能。
分页管理模型
现代系统普遍采用分页机制进行地址映射。将虚拟和物理内存划分为固定大小的块——称为"页",常见尺寸有4KB、2MB、1GB等。
映射关系由页表(Page Table)维护,记录虚拟页号到物理页帧号的对应。由于虚拟地址空间巨大,页表采用分级结构以节省空间:
- 一级:页全局目录(PGD)
- 二级:页上级目录(PUD)
- 三级:页中间目录(PMD)
- 四级:页表项(PTE)
每级索引若干比特位,逐层查找直至定位具体页框。
启用MMU的关键步骤
- 切换CPU至保护模式或64位长模式。
- 在物理内存中构建多级页表结构。
- 将根级页目录的物理地址写入控制寄存器CR3:
mov rax, 0x100000 ; 假设页目录位于物理地址1MB处
mov cr3, rax
- 设置CR0寄存器,开启分页模式:
mov rax, cr0
or rax, 0x80000001 ; 设置PG(分页)和PE(保护模式)标志位
mov cr0, rax
地址转换失败的处理
并非所有虚拟地址都能成功映射。以下情况会导致页错误(Page Fault):
- 访问未分配的虚拟页面
- 试图修改只读页面
- 权限越界(如用户态访问内核页)
当发生错误时,MMU会:
- 暂停当前指令执行;
- 将出错的虚拟地址存入CR2寄存器;
- 触发中断号14(#PF异常);
- CPU跳转至预设的异常处理程序。
操作系统内核负责分析CR2内容及错误类型,可能采取如下措施:
- 分配新物理页并建立映射
- 终止非法访问进程
- 从磁盘加载缺页(支持虚拟内存)
进程间地址隔离原理
每个进程拥有独立的页表集。尽管两个进程可能使用相同的虚拟地址(如main函数都在0x401000),但经各自页表翻译后,指向不同的物理内存区域,从而实现地址空间隔离。
同时,操作系统可通过共享某些页表项来实现进程通信(IPC),比如共享内存机制就是让多个进程的部分虚拟地址映射到同一物理页。