虚拟/逻辑地址生成主体、作用及相关技术疑问咨询
关于虚拟地址、相对地址的疑问解答
1. printf输出的虚拟地址是谁生成的?
不是加载器单独完成的,是操作系统、加载器、CPU三者配合的结果。编译器编译出的是带重定位信息的目标文件或位置无关代码(PIC),加载器在加载程序时,会根据操作系统分配的虚拟地址空间,完成重定位操作——把代码里的相对地址绑定到具体的虚拟地址上。程序运行时,CPU执行指令时使用的就是这些虚拟地址,printf用%p输出的就是这个最终地址。
2. 为什么需要虚拟地址,只用相对地址+MMU基址偏移不行?
早期确实有基址+偏移的内存管理模式(比如实模式的分段),但这种方式存在诸多硬伤:
- 隔离性差:进程若误修改基址寄存器,就能直接访问其他进程的物理内存,极易导致系统崩溃。
- 内存碎片化严重:物理内存必须连续分配给进程,大程序可能因找不到连续物理块无法运行。
- 地址空间受限:比如16位系统只能访问1MB内存,无法利用更大的硬件内存资源。
虚拟地址的优势刚好解决这些问题:
- 它是操作系统通过页表/段表给进程分配的独立地址空间,每个进程的虚拟空间相互隔离,MMU负责虚拟地址到物理地址的转换,进程无法直接修改页表,安全性得到保障。
- 虚拟地址可以映射到不连续的物理内存块,操作系统通过页表将这些分散的物理内存“拼接”成连续的虚拟空间,彻底解决碎片化问题。
- 支持内存交换(把不常用的内存页换到磁盘),能充分利用硬件内存,突破物理内存的限制。
3. 虚拟地址如何让进程产生独占连续内存的错觉?
加载器的核心工作之一,是把程序的代码段、数据段等模块,映射到操作系统分配的虚拟地址空间的连续区域——而这些虚拟地址对应的物理内存,完全可以是分散的。
举个例子:进程A的虚拟地址0x1000-0x2000可能对应物理内存的0x5000-0x6000,虚拟地址0x2000-0x3000对应物理内存的0x9000-0xA000,但在进程A的视角里,自己的内存就是从0x1000开始的连续区域。
另外,不同进程完全可以拥有相同的虚拟地址,因为每个进程的页表是独立的,相同的虚拟地址会被MMU映射到不同的物理内存,彼此不会产生冲突。比如进程A和进程B的虚拟地址0x400000,可能分别对应物理内存的0x123000和0x456000,互相不影响。
4. 为什么网上说“虚拟地址由CPU生成”?
这句话并没有错,只是表述角度不同:
- 操作系统负责规划虚拟地址空间、维护页表,但程序运行时,CPU执行指令(取指令、访问变量等)时发出的地址就是虚拟地址,而MMU本身就是CPU的一部分,负责完成虚拟地址到物理地址的转换。
- 简单来说,操作系统是“规则制定者”,CPU是“实际执行者”。从硬件运行的角度看,程序运行时产生和使用虚拟地址的是CPU硬件,所以网上的说法是从这个层面出发的,和“操作系统负责分配虚拟地址”并不矛盾。
内容的提问来源于stack exchange,提问作者Allexj
相关产品推荐
相关产品推荐

