7.4 Step by step之虚拟内存四

   五,空间与地址转换

   有了前面的地址空间概念后,在这一部分,我们来具体看看,如何去做一个优雅的映射,将不同的地址空间关联起来。

   在前面部分,我们了解到,一个程序编译、连接后形成的地址空间是一个虚拟地址空间。但是,程序最终还是要运行在物理内存中,因此,应用程序所给出的任何虚地址最终必须被转化为物理地址,即虚拟地址空间必须被映射到物理内存空间中。

   虚拟地址到物理地址的转化方法是与体系结构相关的。一般来说,需要借助硬件体系结构所规定的数据结构来建立联系,常用的有分段、分页两种方式。以X86架构CPU来看,分段和分页都是支持的。

   在CPU模型部分,我们提到了,实现地址转换需要MMU(Memory Mangement Unit)的支持。也就是说,CPU中负责地址转换的部件叫MMU。 CPU将逻辑或者线性地址送给MMU,MMU负责完成到物理地址的翻译,并将最终的的物理地址送到地址总线上。整个过程总结起来如下:逻辑地址----(段表)---> 线性地址----(页表)---->物理地址。如果没有分段,那么逻辑地址就等于线性地址;如果没有分页,那么线性地址就等于物理地址。

   以上,是一个总体的说明。下面,我们借助一幅图(图片来自网络)来细化一下上述的过程,以便读者有一个清晰的认识。

   这幅图中,提供了逻辑地址到物理地址映射的全过程,其实就是给出了MMU工作的基本原理。我们来捋一下整个过程。

   首先,最左上角给出一个逻辑地址。这个地址被拆解为两个部分,段选择符和偏移。CPU使用段选择符在全局描述符表中找到段描述符。段描述符中的内容指示了段基址地址。这个段基址地址加上最开始拆解剩余的偏移,就得到了线性地址空间的一个线性地址。这样就完成了从逻辑地址空间到线性地址空间的转换。

   什么?这就完成了第一步转换?可能读者还没有搞清怎么回事,就已经被上面的专业名词搞糊涂了。我们都知道,学习时,如果总是死记硬背,往往不会有好的效果。如果搞清楚设计者背后的想法、目的,则可能取得事半功倍的效果。

   所以,我们用大白话,再将上面的过程讲一遍,并举个例子,大家就清楚了。过程是CPU将逻辑地址拆分为两部分,一部分用来确定逻辑地址在线性空间的起始位置,另一部分用来确定相对于起始位置的距离。这样就实现了逻辑地址对应到线性地址。举个例子,假设我们的线性地址空间为4G,逻辑地址空间为1G,那么准备的全局描述符表中只需有四项段描述符即可。拆分查找后的段基址地址无非是0G、1G、2G、3G四个地址中的一个。也就是说,通过拆解后的第一部分,确定了逻辑地址最终映射到线性地址空间的0G-1G或者1G-2G或者2G-3G或者3G-4G之间的某一个区段。偏移部分就是这某一个区段中间的偏移。所以,如果逻辑地址空间和线性地址空间一样大的话,那就走到了一个极端,起始区段总是0开始,自然,最终的线性地址就是逻辑地址。

   接着,我们再根据上图,看转换后的线性地址如何转换成物理地址。这部分的关键概念,在前面讲分页的时候,已经涉及到了。线性地址会被拆解为三部分:页目录、页表和偏移。通过页目录部分,在页目录表中找到一个表项,然后以这个表项的内容为索引,找到页表。页表再加上拆解的中间部分,找到页表中的一个表项。以页表中找到的表现为索引,找到一个物理页面。这个页面加上拆解的最后一部分,即偏移,最终确定一个物理内存地址。这样就完成了线性地址到物理地址的转换。

   虽然还是涉及到了不少专业名词,但是,因为前面已有一些介绍,所以应该比逻辑地址到线性地址转换的部分轻松点。同样,我们再用大白话过一下。借用一下之前的页目录树形逻辑结构。

 

   通过拆解的第一部分,从树根出发,找到了一个分支。通过拆解的第二部分,从上图绿色节点出发,找到了一个红色叶子节点。每一个叶子节点都指向了一个物理页面。通过第三部分,确定了地址在物理页面的具体位置(偏移)。这样,整个过程就转换完了。

   通过上面的转换过程,我们可以再思考一下这么做的目的是什么?或者说本质的好处是什么?因为整个转换,内存并没有增加,操作流程倒是增加了不少。如果得到的好处不能抵消付出的复杂操作,那就是得不偿失的。其实这个好处,就是虚拟内存的好处:地址得到了隔离,不单是横向的进程间的,也包括纵向的用户跟操作系统间的。

   前面已有提到,除了物理地址空间是实实在在存在的、可以触摸到的外,另外两个都是人为制造出来的。所以,隔离也就是一种虚拟的隔离。所有的内容都是在实际的物理内存上的。如下图:

 

   物理内存中的各个分页内存块,其含义通过人为解释,最终起了不同的作用。比如,充当了整个虚拟内存架构的管理结构。所以,通过上图,我们最终传达了三重信息:1是充当管理的内耗内存,构建了人为的逻辑和线性地址空间;2是空间映射,构建了用户和操作系统之间的地址隔离(编译器不用管操作系统的实现,只需要在逻辑或者线性空间中布局地址);3是不同进程的各自专属段页表,构建了应用间的地址隔离。这部分是下面需要再进一步说明的。

   通过前面的映射图,我们了解了逻辑地址到线性地址以及到物理地址的映射。这个过程主要强调解决了用户和操作系统之间的地址隔离。但实际中,操作系统同一时间运行多个进程,每个进程都有自己的地址空间,此时,映射又是如何工作的?其实很简单,基本过程还是前面介绍的过程,不过此时的段表和页表,不再是笼统的段表、页表了,而是要跟进程本身产生关联:也就是每个进程有自己的段表、页表,因此就有自己专属的逻辑地址空间和线性地址空间,进一步的借用前述映射过程,最终完成地址空间转换。可能不同的CPU架构和操作系统,具体细节不一样,包含的寄存器名称也不一样,不过万变不离其宗,其背后的本质过程、原理都是一样的。掌握这一点,就可以做的举一反三,面对不同的体系结构和操作系统时,做到有的放矢,而不至于产生似懂非懂,迷茫不解的结果。

   上图已经传达了这一层面的信息,不过下面我们再通过一个更加形象的抽象的图示来强调这一点,以便读者有更好的理解。

 

   为了便于问题说明,我们忽略段表,假设只使用页面(Linux主要通过页表来进行映射),另外构造一个想象的进程A和进程B运行的场景,进程A有A的页目录表,进程B有B的页目录表。

   A的前8K虚拟连续地址A1、A2对应物理地址块8和9,B的前8K虚拟连续地址B1、B2对应物理地址块19和17,即A的0地址为块8地址,B的0地址为块19地址,二者不冲突。另外,物理内存拿出一些内存页构建上图所示的层次关系。

   进程A查找地址100时,先找自己的页目录表,找到100在第一个页表中,进而查找页表1,找到100所在块在物理地址块X中,然后访问X,得到100地址处实际的数据。进程B要访问自己的100地址时,找B的页目录表,再找到B的100地址页表,从页表映射中拿到实际的物理地址Y,然后访问Y得到100地址处的数据。其他进程也类似。

   A和B可共享内存,该块内存在A和B各自页表中都有映射即可,如上图的物理地址块18。显然,物理地址块有限且少,进程映射表中,许多项肯定不在物理内存中,而实际存在于磁盘上,这样就完成了小内存大空间的构想。

   对于多个进程的虚拟空间切换问题,我们通过上面的流程,可以看出,如果给出的页表不同,那么CPU将某一虚拟地址空间中的地址转化成的物理地址就会不同。所以我们为每一个进程都建立其页表,将每个进程的虚拟地址空间根据自己的需要映射到物理地址空间上。既然某一时刻在某一CPU上只能有一个进程在运行,那么当进程发生切换的时候,将页表也更换为相应进程的页表,就可以实现每个进程都有自己的虚拟地址空间而互不影响。所以,在任意时刻,对于一个CPU来说,只需要有当前进程的页表,就可以实现其虚拟地址到物理地址的转化。这个页表信息,一般是保存在进程的控制结构块中的。

   既然在任意一个时刻,一个CPU核心上只有一个进程在运行,则对于此CPU来讲,在这一时刻,整个系统只存在一个4GB的虚拟地址空间,这个虚拟地址空间是面向此进程的。当进程发生切换的时候,虚拟地址空间也随着切换。由此可以看出,每个进程都有自己的虚拟地址空间,只有此进程运行的时候,其虚拟地址空间才被运行它的CPU所知。在其它时刻,其虚拟地址空间对于CPU来说,是不可知的。所以尽管每个进程都可以有4 GB(32位系统)的虚拟地址空间,但在CPU眼中,只有一个虚拟地址空间存在。虚拟地址空间的变化,随着进程切换而变化。一般而言,每次进程切换时,会将该进程的页目录表设置到CPU的专用寄存器中,MMU就利用这些寄存器进行地址空间转换。

   如果存在段表会怎么样?因为上图中的每一棵树代表了一个进程的地址空间,将多棵树的根挂到一个新的根上,这个新根就是段表。其指示的就是总的线性地址空间,而每一个进程代表的子树就是整个线性地址空间中的一段。

   前面有提到,进程间可共享内存块。引申一下,就产生一个新问题,进程和操作系统之间如何共享内存。

   一般来讲,进程存在用户态和内核态。当进程运行在用户态时,它只能访问用户空间内存,这并不包括内核空间;当进程运行在内核态时,理论上是可以访问进程的整个地址空间的。因为进程有多个,内核只有一个,所以所有进程共享同一个内核,也就共享同一个内核空间。比如,对于linux而言,进程的0-3G范围是用户空间,高地址的1G是属于内核地址空间。

   当进程在用户态时,使用自己私有的页目录和页表,就可以完成用户空间内存地址的映射处理。而当进程处于内核态时,除了进程自己的页目录和页表,还要包括内核的页目录和页表。使用内核页表结构,完成内核空间地址的映射。

   进程的创建是通过内核完成的,所以在进程创建过程中,内核会将自己的页表结构提供给进程,从而使得进程在创建之初,就完成内核空间的地址映射关系建立。当然,还有很多细节问题,比如在内核空间,出现缺页时,如何处理,完成页面分配后,如何将信息同步到其他进程等。感兴趣的读者,可以关注具体操作系统的实现。(用户态的缺页处理,因为只跟用户进程自身有关,只需要更新用户私有的页目录和页表即可)

   现在,可以做总结性描述:

   基于分段分页,形成一个逻辑模型,可以构造出不同的空间。配合CPU的特殊寄存器(全局段寄存器,页目录寄存器等),不同的空间可以相互转换,如此,虚拟内存的基本模型构建完成。

   最后,我们通过下图的系统分配内存的粗略过程结束本节:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙赤子

你的小小鼓励助我翻山越岭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值