问题

虚拟地址、线性地址、物理地址之间是如何转换的?

回答
在计算机的世界里,我们经常听到“虚拟地址”、“线性地址”和“物理地址”这几个词,它们之间到底是怎么一回事,又是如何相互转换的呢?这涉及到操作系统和硬件之间一个非常精妙的协作过程,我来给你掰开了揉碎了讲讲。

先来认识一下这几个概念的“身份”:

1. 虚拟地址 (Virtual Address): 这是我们程序(比如你正在运行的浏览器、游戏等等)最直观看到的地址。当你写代码时,看到的内存地址就是虚拟地址。它就像是给每个程序发的一个“专属地址簿”,在这个地址簿里,所有内存地址都是从 0 开始的,而且每个程序都觉得自己拥有了整个内存空间(当然,这个空间的大小取决于 CPU 的架构,比如 32 位系统是 4GB,64 位系统能访问的就大得多了)。这种方式的好处是显而易见的:
隔离性: 每个程序都有自己独立的虚拟地址空间,一个程序无法直接访问另一个程序的内存,这大大提高了系统的安全性和稳定性。如果一个程序崩溃了,不会影响到其他程序。
简化编程: 程序员无需关心物理内存的实际布局,也不需要管理内存的分配和释放(这部分由操作系统代劳),只需要按照自己的逻辑来使用地址就行。
内存共享和保护: 不同的程序可以通过映射机制共享同一块物理内存,同时也可以设置对内存的访问权限(只读、读写等)。
内存扩充: 即使物理内存不足,也可以通过虚拟内存技术(后面会提到)利用硬盘空间来扩展可用的内存。

2. 线性地址 (Linear Address): 这个名字听起来有点奇怪,它其实是CPU 在进行地址转换之前,内部产生的一个“中间地址”。你可以把它想象成是CPU在计算和处理指令时,基于虚拟地址经过一系列运算(比如段式寻址、分页寻址等,这取决于CPU的模式和架构)后得到的一个没有经过物理地址映射的、连续的地址。
在早期的一些CPU架构(如16位或32位实模式下的x86架构),线性地址的概念更加明显,它是由段基址加上偏移量计算出来的。
在现代的分页机制下,虚拟地址和线性地址通常是等价的。因为分页模式下,CPU直接将虚拟地址作为输入的,经过页表查找后直接得到物理地址。所以,很多时候提到现代CPU(如x8664)的地址转换时,虚拟地址和线性地址可以被看作是同一个东西,都是进入地址转换流程的入口。但从概念上来说,线性地址是虚拟地址在进入分页系统前的“形态”。

3. 物理地址 (Physical Address): 这是内存条(RAM)上实际存在的、硬件能够直接访问的地址。它对应着内存芯片上的每一个字节的位置。CPU 要真正读写数据,最终都需要将地址转换成物理地址,然后通过内存控制器发送到内存总线上。

地址转换的“流水线”:

简单来说,这个转换过程就是:虚拟地址 →(CPU内部处理)→ 线性地址 →(通过MMU/Paging Unit)→ 物理地址。

在现代操作系统和硬件中,这个转换过程主要由一个叫做 内存管理单元(MMU,Memory Management Unit) 的硬件部件来完成,最核心的技术就是 分页(Paging)。

我们以现代的 分页机制 为例,详细说说这个转换流程:

背景:分页和页表

为了实现高效的地址转换,操作系统会为每个进程维护一个叫做 页表(Page Table) 的数据结构。页表的作用就像一个“翻译字典”,它记录了虚拟地址空间中的“页”(Page)是如何映射到物理地址空间中的“页框”(Page Frame)的。

页(Page): 虚拟地址空间被划分为固定大小的块,这些块称为“页”。页的大小通常是 4KB、2MB 或 1GB。
页框(Page Frame): 物理地址空间也被划分为同样大小的块,这些块称为“页框”。
页表项(Page Table Entry,PTE): 页表由一系列的页表项组成。每一项对应虚拟地址空间中的一个页,记录了该页对应的物理页框的起始地址,以及一些权限位(如可读、可写、可执行等)。

转换流程(以x8664为例,通常是四级页表):

假设 CPU 要访问虚拟地址 `VA`。

1. CPU 获取虚拟地址: 操作系统加载程序时,会为每个进程创建一套独立的页表。当程序执行时,CPU 需要访问某个虚拟地址 `VA`。
2. MMU 接管: CPU 的 MMU 接收到虚拟地址 `VA`。
3. 页目录指针表(PML4): 在 x8664 架构中,虚拟地址 `VA` 会被分割成几个部分,其中一部分用于索引 页目录指针表(Page Map Level 4,PML4)。MMU 会使用 CPU 寄存器(如 `CR3`)中存储的当前进程的 PML4 表的基地址,结合虚拟地址中的 PML4 索引,找到对应的 PML4 表项。这个表项里包含了下一级页表的基地址。
4. 页目录(PDPT): MMU 使用从 PML4 表项中获得的基地址,加上虚拟地址中的 页目录指针表(Page Directory Pointer Table,PDPT) 索引,找到 PDPT 中的表项。这个表项又包含了下一级页表的基地址。
5. 页目录(PD): MMU 使用 PDPT 表项中的基地址,加上虚拟地址中的 页目录(Page Directory,PD) 索引,找到 PD 中的表项。这个表项继续包含下一级页表的基地址。
6. 页表(PT): MMU 使用 PD 表项中的基地址,加上虚拟地址中的 页表(Page Table,PT) 索引,找到 PT 中的表项。
7. 找到页框基地址: 这个 PT 表项(也称为 页表项,PTE)包含了目标 物理页框的基地址。它还包含了权限信息,比如该页是否可读、可写、可执行,以及是否已被加载到内存(即是否存在有效映射)。
8. 地址拼接: MMU 将 PT 表项中获得的物理页框基地址,与虚拟地址中剩下的 页内偏移(Page Offset) 部分进行拼接。
虚拟地址 `VA` 可以被看作是:`[PML4索引][PDPT索引][PD索引][PT索引][页内偏移]`
物理地址 `PA` 就是:`[页框基地址][页内偏移]`
9. 访问物理内存: 最终得到的 `PA` 就是 CPU 要访问的物理内存地址。MMU 将这个物理地址发送到内存总线,硬件开始从该物理地址读取或写入数据。

特殊情况和优化:TLB (Translation Lookaside Buffer)

每次访问内存都要经过多级页表查找,这个过程是非常耗时的。为了加速这个过程,CPU 内部有一个叫做 快表(Translation Lookaside Buffer,TLB) 的缓存。

TLB 存储了近期使用过的虚拟地址到物理地址的映射关系。
当 MMU 接收到一个虚拟地址时,它会先检查 TLB。
如果 TLB 中存在该虚拟地址的映射(TLB Hit),那么直接从 TLB 中获取对应的物理地址,大大加快了访问速度,避免了多次内存访问来查询页表。
如果 TLB 中没有该虚拟地址的映射(TLB Miss),MMU 才需要执行上面描述的多级页表查找过程,并将找到的映射关系缓存到 TLB 中以备后续使用。

总结一下这个转换过程的“角色”:

CPU: 发出虚拟地址的“源头”,并且包含执行地址转换的必要逻辑(虽然大部分工作由 MMU 完成)。
MMU(内存管理单元): 地址转换的“核心执行者”,负责解析虚拟地址,查询页表,计算物理地址。
操作系统: 页表的“创建者”和“管理者”,负责为每个进程分配虚拟地址空间,维护页表,处理缺页中断等。
页表: 地址转换的“翻译手册”,记录虚拟页与物理页框的对应关系。
TLB: 地址转换的“加速器”,缓存近期映射关系,提高访问效率。
物理内存: 最终的“目的地”,CPU 实际读写数据的地方。

这个流程看起来有点复杂,但正是通过这种精密的协作,才使得现代计算机系统能够实现内存隔离、高效利用内存、支持虚拟内存等强大的功能,为我们提供稳定、安全且灵活的计算环境。虚拟地址就像是你家的门牌号,线性地址是你出门后走向小区门口那段路,而物理地址就是你最终到达的那个真实存在的楼层和房间号。转换的过程就是你根据小区地图(页表)找到自己家的具体位置。

网友意见

user avatar
一个进程中每个数据的虚拟地址是固定的

不一定,可能是动态变化的,比如堆栈,固定的一般指的是代码段的部分(但代码段其实也可能是可变的)

但受其它进程占用真实内存空间的影响,虚拟地址映射到的物理地址是不断变化的

通常来说,操作系统会优化内存申请,尽量保持连续,不连续的内存对于操作系统来说也是不友好的。同时,操作系统不会频繁更改虚拟地址到物理地址的映射,除非是内存十分紧张。频繁的更改映射会降低性能。

所有进程的虚拟内存加起来远大于真实的内存大小,

虚拟地址空间通常确实是远大于真实物理内存,但真正提交映射的虚拟地址,可能不会“远大于”。

所以每一时刻所有进程正在使用的虚拟内存是可以单射到物理内存的

如果正在使用的虚拟内存已经映射但没在物理内存里,会报缺页中断,操作系统会负责加载这部分内存。所以并不是所有已经被提交的内存都在物理内存里,可能大部分都不在。

考虑到多核的问题,那么真正意义上“正在被使用”的内存最多只有:4K (页大小) * (1代码页 + 1 数据页)* CPU核数,这个数量其实很小。

即使加上页表,加上中断管理、驱动等内容,这部分的内存也不大。

(1)这个虚拟内存与物理内存的映射关系储存在哪里?如果也储存在内存中,那么如何让这个映射表所占空间尽可能的小?

映射关系也保存在内存里,也就是“页表”,页表通常不会很大。

打开PAE的情况下,一个页表项占8字节,一个4K页可以保存512个页表项,映射512*4K=2MB的内存,相当于2M内存浪费4K,比例不到0.2%,并不高。如果不打开PAE,那么一个4K页可以保存1024个页表项,内存浪费比例不到千分之一。

况且还允许有2M页等更大的页存在,所以不用担心浪费的问题。

一个32位的地址空间有4G,但操作系统不需要对整个4G空间都做映射,页表是分级的,三级页表的顶级里,并不是每一个页表项都是可用的,如果顶级页表项(PDPTE)没有指向一个有效的子项(PDE),那么PDE往后的内容不再需要内存来存储页表。这样的话,如果映射连续的2M内存,只需要4K+4K+4K的页表就足够了。

同样的,我前面说了操作系统会倾向于分配更加连续的内存,不管是物理上的连续还是虚拟地址的连续,连续的内存占用的表项不会太多。

(2)如何控制每一时刻所有进程正在使用的虚拟内存的大小小于等于物理内存?

操作系统有换页算法,某一个物理页不用了,会换到磁盘上。页表中,并不要求所有的页表项都处于有效的状态。

物理内存不够用的话,把不经常使用的页换出去就可以了(LRU算法)

(3)操作系统完成相应的分配和调度,也需要占用内存,这个占用的内存空间大概有多大?

不需要多大,以Windows为例,Windows95只需要4M内存就可以运行。通常情况下内核页表和用户页表可能是分开的,内核页表一般是一次性分配的,如果用的少的话,3-4个页就够用了。页表的使用量跟物理内存大小有关,物理内存越大,页表的体积也越大。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有