7.4 Step by step之虚拟内存一

前言:可以说,与计算机行为相关的任何分析,都离不开内存。因为程序就是在内存中运行的,所以内存相关的分析、讨论可能在任何一部分都涉及到,很难完全独立出来。这里只是相对集中的讨论内存管理,在其他模块相关部分也还可能涉及到,需要读者注意。

   一,综述

   前话:内存是个神奇的模块。很多难以想象出的软件功能,只要跟内存关联起来,你就可以大概猜出其中的原理,即使不能了解原理,你也不会再为单纯的不可思议而感到惊奇了。就拿显示来讲,只要将其跟显存关联起来,就容易理解多了。

   内存管理是操作系统设计中非常重要的一块,是整个系统正常运行的基础。作为系统提供的一项基础资源,其贯穿于进程管理,文件系统,显示,网络等各个模块。

   管理内存是操作系统的一项最重要的任务,操作系统必须保证其对内存的使用正确无误,同时还必须限制进程对内存的访问,只允许进程访问自己权限可以访问的空间,禁止非法访问。在此基础上,操作系统需要对紧缺的内存资源合理使用,并利用CPU的内存管理模块,有效的扩充虚拟内存,保证整个系统的正常运转。

   对内存的管理,可以分为两种情况,一种是实模式下的内存管理,一种是保护模式下的内存管理。这里,对于实模式,不存在虚拟内存的概念,整个内存就是平坦的方式被使用,内存的管理相对简单许多,但也就有诸多限制,这一点后面会提到。而保护模式就存在虚地址的概念,因此基本上保护模式下的内存管理就是基于虚拟内存的管理,自然就相对复杂许多,而带来的好处也不少,这一点后面也会进一步探讨。

   在内存硬件模型部分,我们介绍了平坦的紧缺的内存资源如何有规划的使用,并从整个系统的角度简单介绍了内存可能的物理布局。因此,这里所讨论的内存管理,更多的是基于虚拟内存展开,是整体的逻辑层面的规划使用,当然也是在物理布局基础之上的。

   二,虚拟内存

   在正式开始之前,我们再深入了解下虚拟内存存在的价值。对于为什么采用虚拟内存机制,最直接的一点,就是内存总是不够用,因此设计出虚拟内存机制,用一部分磁盘作为模拟内存,达到扩充内存的目的。当然,虚拟内存的作用并不仅仅限于这一点,虽然这一点是很重要的一点。

   首先,我们简单看看没有虚拟内存机制的情况下,操作系统是如何管理内存的。操作系统内部实现一个简单的内存管理模块,它没有映射和转换机制,程序将直接全部加载进系统中运行。如果存在内存不够用的情况,程序将执行不了,除非有别的程序退出,腾出足够的内存空间。这个过程可以通过下面的流程图示来说明。

   关于图示场景的约束说明:内存中当前只存在操作系统,磁盘上有ABCD四个程序,操作系统调度上述四个程序到内存中运行。目前,除了操作系统占用的空间外,内存剩余空间可以容纳上述四个程序同时运行。

 

   上图说明:内存中现在只有操作系统,操作系统内核由代码和数据组成,大小1GB,剩余内存1500MB。最右边为磁盘,存储有ABCD四个程序,每个程序都由代码和数据段组成,各个程序的大小为A:500MB,B:250MB,C:300MB,D:400MB。可见,程序总共需要内存空间1450MB。内存剩余空间大小是可以满足程序要求的。

   需要注意的是,这里的图只是一个示意说明图,或者说是模型图,主要用于说明问题,包含主要的必要的细节,而非所有细节。并且我们使用的是一种最简单的模型,即操作系统以字节为单位分配内存。

   从图中可以看到,内存最开始一部分存储的是操作系统内核,由内核代码和内核数据构成。内核代码中有内存管理逻辑,内核数据中有内存管理需要的数据结构,也有为操作系统使用预留的内存,即不存在操作系统和应用争抢内存的情况。所以,内存剩余的部分全部为应用可用内存,都是可以动态分配的。要运行一个应用程序,需要将该程序全部加载到内存中,包括该应用程序的代码段和数据段,全部都要一次加载完整才可以运行该程序。程序运行过程中可以再从空闲内存空间中动态分配和释放该应用程序所需的动态内存。以上所述都是一些前提条件,也算是模型的假设条件。

   现在我们模拟程序的加载,来分析内存分配使用的情况。首先,加载磁盘上的程序A运行。操作系统的加载程序按照程序A需要的内存大小,在系统可分配内存中找到一块满足程序A需求的内存。可以看到,目前内存空间比较充裕,操作系统为程序A在紧挨其后的空闲内存中分配了程序所需的500MB大小的内存块,并将程序A加载到刚刚分配的区域,之后调用调度模块,运行程序A,如下图所示:

 

   在程序A运行中,根据需要,随机时间点向操作系统申请了多个小块内存来使用。分配的内存区域处于连续地址空间,一切正常。如下图所示:

 

   现在,程序B需要运行,操作系统加载程序B并运行程序B,过程与程序A类似。结果如下图所示:

 

   同样,程序B也申请了部分运行内存。之后,操作系统又加载程序C运行了,过程与上面的A和B类似。并且的ABC三个程序运行过程中,程序A和程序B陆续释放了大部分其申请占用的内存,此时,内存使用的分布情况如下图所示:

 

   

   此时,空闲内存为A释放的动态内存50MB加上B释放的动态内存50MB以及未分配内存,一共有450MB内存。其中,未分配部分为350MB大小。

   此时,操作系统需要加载程序D运行。但是我们看到,目前没有一块足够的空间可以用于完整的加载程序D,即程序D无法运行。但是仔细查看内存情况,发现程序A和B运行过程中释放的空闲内存空间加上未分配内存是能够装得下程序D的,不过现在因为这两块内存在整个内存空间中不连续,导致程序D无法加载运行。这就是说,在内存总的可用空间足够的情况下,出现了程序无法加载的问题。

   上面所讨论的虽是一个假设的情景,但对实模式内存使用存在的问题,却是清晰地展示了出来,那就是只要整块的内存不够,新程序是执行不了的,不管是因为单个程序消耗过多内存还是因为多个程序消耗了大量内存。这是需要虚拟内存机制的一个很重要的原因。那么如何来解决这个问题呢?

   实际上,根据程序的空间局部性和时间局部性原理,其一,程序不一定非要全部加载进内存中才可以执行,这是第一个优化点,即上图中的程序不必总是整块整块使用的;其二,不一定非要有整块的空闲内存才可以加载新的程序,可以将碎片内存管理起来,只要总和足够大,也是可以运行一个新的程序的,只不过管理过程比较复杂罢了;其三,即使真的内存剩余已经很小很小了,还是可以有办法优化的,就是将暂时没有运行的程序占用的内存先缓存到磁盘上,等需要时,再拷贝进来,如此也可以腾出一点宝贵的内存空间。这样一来,上图中所示内存占用情况下,运行新的程序并不是不可能。

   所以,按照上述三点来优化,就可以让小内存运行更多的程序,这也解决了我们前面提出的问题。其实,这三点优化,所包含的思想,正是虚拟内存机制的核心思想,也是虚拟内存管理所要实现的。反过来说,因为虚拟内存所要解决的就是内存不够的问题,而基本思想就是,程序部分存在于内存中,部分存在于外设中,按需加载,并且不需要时,可以调度出去,将内存释放出来。而这一思想又来自于程序的空间相关性和时间相关性。如此一来,为什么要虚拟内存,虚拟内存所要做的工作,以及为何可以这样做的基本道理就算是理顺了。最终总结起来,实现这一想法,基本逻辑应该是这样,程序只将最紧要,最可能执行到的部分存放于内存中,其他部分在需要时,再从磁盘调入内存,并在不需要时,调出内存,以最大可能运行程序,并最大数量的运行程序。

   了解了什么是虚拟内存机制及为何要用虚拟内存后,我们再进一步了解更多的虚拟内存实现的细节问题。很显然,根据按需加载及可换出的思想,同一片内存区域,不同时刻,可能存放的是不同程序的片段或者同一程序的不同片段,而且很可能这个不同程序还是同时运行的,如下图所示:

 

   说明:

   1 操作系统得有内存管理结构,管理可分配内存

   2 管理结构得记录所有内存的使用情况

   3 操作系统从磁盘上加载进程,从内存中分配一块内存给进程使用

   4 进程执行时,认为自己是在连续的地址空间内执行的。进程是这样认为的,操作系统会做转换,比如页目录项。

   5 进程分配内存,操作系统帮其完成。这一步通过系统调用完成。现实中,可能是先由基础库跟操作系统要内存,然后再分配给应用。应用的请求可能在基础库部分就完成处理了,否则,才转向操作系统要内存。

   6 进程可一直在内存中分配空间,直到需要虚拟内存来提供。这是通过地址映射做到的,操作系统会将程序内存中的部分数据搬移到磁盘的交换分区,也就是虚拟内存部分。

   7 上述4 5 6执行

   8 当需要导出的虚拟内存内容时,操作系统会再次将交换分区交换出去的部分加载到内存中。此时需要注意,该数据块此时使用的物理地址,跟之前使用的不是一个地址。这些都需要操作系统内存管理结构记录好。

   9 虚拟地址处理过程中,涉及磁盘IO,性能会打折扣。

   10 如果内存分配按字节来,操作系统管理程序会很复杂,效率很低。这在后面会进一步介绍。

   就上图来看,三条连线表示的过程为:程序B加载到内存运行。程序A需要更多内存,操作系统将程序B导出到虚拟内存,然后在B原来占用的内存空间中,为A分配内存。

   我们同样可以采用情景再现的方式来描述这一过程。这里就不展开了。总之,同一内存区域的内容,在不同时刻可能需要调出到不同的外设区域。另外,运行过程中,同一块内存区域可能运行同一程序的不同部分。

   上面所述的虚拟内存运行过程,可以通过火车轨的接续来形象展示。如下图:

 

   图中红色是新的轨道。可见,只要赶在火车到达轨道头之前,接上另一节轨道,则对火车来讲,会认为轨道不仅是连续的,而且也是无穷尽的。虚拟内存的思想跟这个过程是类似的,只不过程序是会膨胀的,不会像火车那样,固定大小,这是区别。

   现在来思考另一个问题,采用什么方法,能简单、准确、高效的完成上述任务呢?简单是说实现不能太复杂,准确是说替换不能出错,这是显而易见的,高效是说替换过程要快,如果严重影响效率,那又得不偿失了,而简单其实间接的保证了准确高效的原则。

   重新思考三个原则,三条优化:简单、准确、高效原则,少量加载、碎片利用及换入换出优化。

   我们从三条优化入手。可以想见,换入换出其实是少量加载的一种特例情况,机制上同其大体相同,这样,就重点考虑少量加载和碎片利用。对操作系统来讲,少量加载是不难做到的,开始时将程序入口代码载入内存,然后将CPU的PC指针指向该入口,开始执行。当执行到的代码不在内存中时,再将缺失的部分再加载到内存,继续执行。此时,加载虽不难,但是执行就有问题了,因为后续载入的代码和数据跟之前的部分是不连续的,也就是说程序的内容零散的一片一片的存在于内存中,如何让这些碎片仍跟连续整片一样呢?我们的代码在编译后,许多元素都是连续存在的,很多地址都是相对于它之前某个点的偏移地址表示的,要做到在内存任何绝对地址处这种片段间的偏移还能有效,对编译器来讲是不可能预先判断并实现的,对操作系统来讲,可以完成,但是复杂性就陡然增加了,这也其实就是虚拟内存管理较实模式下内存管理的真正区别之处,或者可以说是本质的不同点。再总结一下,这个本质的不同点,就是操作系统如何做到让物理零散的内存中的程序进程片段看起来还是逻辑的连续整块?就如下图:

 

   上图中左边为操作系统看到的内存,右边为程序的磁盘存储。程序D被分散的加载到内存的1 2 3 4四个块中,如何在逻辑上重组,让这四块成为逻辑上的一块,就像下面那样一样。

   如果可以做到,那这样一来,编译器无需做大的调整,关键就看操作系统的设计实现了。联系生活实际,这跟什么有点像?是否跟组装东西类似。比如我们买家具,厂家将其按照节省空间的方法打包发货。接收到货物后,我们按照说明书中的指导,再恢复原样。比喻不算太贴切,大概的原理是一致的。实际中,每个操作系统有自己的实现方式,但不管怎么实现,我们会发现有一点不变,那就是都需要一个记录表,这个表应记录每一块内存实际的地址和在整体空间中的偏移地址,如下图:

 

   

   这次,我们给每个内存块一个地址。从上图看到,D的第一块在地址1处,第二块在地址5处,第三块在地址10处,最后一块在地址3处。如果操作系统通过一个表,记录了每个内存地址对应的块,如图左下角所示,那么操作系统就知道如何按照程序本来的顺序进行排序。对上图来讲,就是1 5 10 3。

   运行中,各个部分通过映射表关联到一起,并且随着内存的调入调出,而动态更新,总是保证整个对应关系的正确。

   有了这个表,即使分配再零碎杂乱,最终也能把它们拼接到一块去。这个记录表就是组装说明书,实际中操作系统的实现比这个要精巧一点,一般叫映射表。但本质原理是差不多的。在将进程片段载入内存中时,会完善这个映射表,此后程序执行时,也将检索这个表,找到属于自己进程的每一个内存段。通过这个映射表,操作系统简单准确高效的实现了虚拟内存管理。下面我们通过几个图示简单展示这个过程的大概流程,读者有个感性的大体印象。

   假设操作系统要加载进程B运行。它首先通过内存管理模块进行检测,发现当前时间,物理内存块9是空闲的。于是将B的第一部分加载到9中,并将该信息记录下来:

 

   此时,操作系统判断发现,进程B可以运行了。于是将PC指针跳转到B的入口,执行B的代码。接着,在运行过程中,发现需要继续加载程序的内容。通过内存管理程序检测,发现地址4所在内存块是空闲的,于是将程序B的第二部分加载到内存地址4所在部分,并做记录,如下图所示:

 

   后续的加载和导出过程都是类似处理的。这样,我们就情景展现了通过映射处理机制实现少量加载、碎片利用以及换入换出的过程。

   基于上面图示过程,再进一步抽象,我们可以看出,内存是一个地址空间,外设是一个地址空间,并通过映射表及微观上的频繁调入调出,达到宏观上的大内存空间效果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龙赤子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值