文章目录
- 0. 虚拟存储的定义
- 1. 目标
- 2.局部性原理
- 3. 虚拟存储的思路与规则
- 4. 虚拟存储的基本特征
- 5. 虚拟页式存储管理
- 5.1 页表表项
- 5.2 示例
0. 虚拟存储的定义
1. 目标
虚拟内存管理技术,简称虚存技术。那为什么要虚存技术?在于前面覆盖和交换技术,其实还觉得技术上面还不够理想,还达不到很方便、很高效的内存使用的效果,所以需要能够有些更好的办法。
- 覆盖技术到底什么问题?程序员需要去管理哪些应该覆盖,哪些不应该覆盖,它需要把这个调用关系分析清楚,然后告诉相应的处理单元完成相应工作,这个对程序员负担太大。
- 交换技术的力度太大,它是一次以一个程序做一个交换力度,会导致整个系统开销变大。
有时候没必要把整个数据导出,希望是把一小部分导出去,能不能做到这一步? 其实是可以做到,希望通过一种更好的办法,充分地把前面覆盖技术和交换技术的问题解决掉,就是虚存技术。
当然这听起来很好,但它怎么能达到呢?先看看它的目标:
- 它能够像覆盖技术一样,它不是把程序的所有都放在内存中去,只是把程序中一小部分放到内存中去,既然不把所有内容放内存中去,它所需要的空闲空间其实没那么大,另外它跟覆盖技术最大的不同,它的过程由操作系统自动来完成,不需要程序员做出干预。这样可以极大地减轻程序员的负担。内存中存放的内容可以是一小部分和覆盖技术一样。
- 内存上也像交换技术一样,它能够实现就是程序在内存中跑之后可以动态地根据当前执行情况来把某些数据直接导出和导入,交换技术的导入导出是以一个程序为力度,是程序间的,那能不能做到不管是在程序间还是程序内都无所谓,以一个更小的力度,比如说以一个页为力度来作为交换单位把它导入导出到内存中去。
看上图可能就可以看出来这特点,有四个程序,操作系统有 CPU 的 MMU 支持,有硬件支持之后,比如分页机制支持,内存中,比如说以 P3为例,它只放了两个内存页,其他的数据全是导在硬盘上去的,那这种情况下,P3 运营时它只用了这两个数据,其它没有用到,既然没用到就没必要放到内存中去,这样的话就可以使得这个程序本来整体运行需要很大空间,但是在某一个有限的时间段内,实际需要一小段空间就够了。这种方法可以实现虚存管理想达到一种理想,这种方法其实通过操作系统和 MMU 是可以做到。
2.局部性原理
需要注意,做到之前由于整个处理过程是完全自动的,也就说它不需要程序介入,那其实表面上不需要,其实还是对程序提出一个要求。什么要求呢?就需要程序具有局部性特征。
程序的局部性原理是指程序在执行过程中,在一个较早的时间之内它所需要的数据和操作的指令分别局限在一定的区域。 这样就可以实现高效地执行。
局部性存在两个细分的概念,一个时间局部性,一个空间局部性。
- 时间局部性是指,一条指令的一次执行和下一次执行,一个数据的一次访问和下一次访问,都集中在很短的时间之内就完成了。
- 空间局部性是指,当前指令和临近指令,或者当前访问数据和它临近的数据,都集中在一个区域里面,它们在内存中的位置靠得很近。
程序如果具有时间局部性和空间局部性,认为程序的局部性很好,意味着它执行效率会很高。设计程序的时候需要考虑局部性的特征,如果说程序局部性做得很好,操作系统就可以利用程序局部性的特点来实行高效的虚存管理,可以得到很理想的效果,让程序可以很小的一部分的空间放到内存中去,同时它执行效率很高,达到虚拟内存的理想,访问速度快(在执行过程中基本上就是跟在内存中访问完全一样,而且给执行程序提供很大的虚拟空间),空间大,使用方便(不需要程序员干预)。
也许这时候虚拟空间并没有都放在内存中,只是放在硬盘上,但由于读写硬盘的次数很少,使得整个执行效率还是可以很高,但这需要程序本身局部性的保证。
示例:
~
~
C语言是按照行优先来放置二维数组 ,数组元素大小是4字节,所以一维数组大小为4K.
~
3. 虚拟存储的思路与规则
要实现高效的虚存管理机制,除了操作系统和硬件之外,需要程序具有一定的局部性,如果程序不具有局部性,那么高效机制实现起来就很困难。有了操作系统,有了内存管理单元,有分页机制,或者分段机制,再加上程序具有局部性,怎么实现虚存技术?
操作系统有了硬件支持(分段或者分页), CPU + MMU 有效地实现分段和分页机制。
有了分段和分页之后,在分段分页的内存管理之上来实现以页或者以段为单位的虚存管理。
大致流程这样:
-
首先装入程序时候,不必将所有代码或者数据放内存中去,而只需将当前需要执行的代码和数据放入到相关的页或者段中去,这样使得一小部分代码和数据放到内存中去了,只要一小部分程序代码在内存中,那就可以让程序执行。
-
当然很明显,当执行到一定阶段的时候,它可能接下来需要访问的代码或者数据还在硬盘中,所以会产生访问异常,称之为缺页或缺段异常,产生异常之后,操作系统会得到通知,通知之后,操作系统就会看一看,它如果觉得现在内存还有空间,就会把所需要代码数据从硬盘导到内存中去,那导入内存之后,这条没有访问到的指令可以重新执行,它可以继续往下走了。
-
但也有可能这时候内存中没有空间了,那这时候操作系统可能干的事要更复杂一点,它要考虑现在这些被占的这些内存的数据和代码,哪些是将来可能不太会用到的,或者一段时间内不会被访问到的,会把这些代码数据给导到外存中去,这样可以空出更多的空闲空间来放入当前需要的代码数据,继续执行。
那它的选择也很重要,选择得好,也可以保证将来跟内存访问的读写次数就会少,整体性能开销就会大大减少,整个系统性能就会提高。
4. 虚拟存储的基本特征
- 空间很大。 它把硬盘当成了一个虚拟的空间,实际整个虚空间包含的大小是物理空间加上硬盘合在一起形成了虚拟空间,那这空间就很大。
比如说 CPU 是32位的机器,那么其实它的理论空间可以访问 4G,那其实物理内存可能只有26M,在这种情况下硬盘可以很大,通过硬盘的补充可以实现使得在内存中可以跑多个程序,而且每个程序还都认为它自己占了 4G的空间。
~
这感觉很不错,更大的空间会随便使用,但其实在用的过程中,操作系统会自动根据它的判断,来把程序当前正在用到的代码数据放到内存中去,因为内存只有256M,256M内存还要包含Linux 内核,内核本身是不能被换出的,操作系统内核是常驻内存的,但是内存中运行的用户程序,它们不像操作系统那么关键,所以说它可以根据当时的执行情况来选择一小部分放在内存中执行,通过这种方式可以给每个自然运行的程序一个很大的虚拟地址空间。
- 部分交换, 它跟前面交换技术相比,需要注意,它每次换入换出是很规整的,要么一个段要么一个页,基于分段分页机制,分段分页是操作系统管理的,它不需要把整个程序都给换出去。交换力度更小,使得效率会更高。
- 不连续性, 不连续体现在它物理内存分配是不连续,同时虚拟内存空间使用也是不连续的,前面讲虚拟内存空间是连续的,由于有换入换出机制,可能把虚拟空间连续的这块区域一部分换出去,换出到外存中去。使得实际执行过程中可能会出现,连续执行到某个阶段的时候,这个页不存在,这个情况下就会产生访问异常,但这个异常没关系,操作系统会自动处理这个情况,会把需要连续执行数据和代码,再从外存中再放到内存中去,使得这个程序继续执行,不连续性体现在,本来所有的数据都应该连续地放在虚拟内存中,由于操作系统要把某些数据和代码换出去,造成所谓的不连续,但没关系,操作系统会帮助把它弥补好,使得程序可以正常去访问。
5. 虚拟页式存储管理
讲完这个特征之后,就要考虑到怎么去实现它,在现有硬件和操作系统管理下,能不能具体实现它?
首先以页式存储管理的基础来讲解虚拟内存管理。
看上图,左侧是逻辑地址空间,右侧是物理地址空间,左侧右侧以页为单位映射关系是靠页表来维护的,页表里面维护了映射关系,页表项的索引是页号,页表项的内容是页帧号。
~
除了页帧号之外还有几个 bit,bit 其实是有很关键的作用,有 bit 代表页存在或不存在,内存地址访问时,通过查找页表发现对应页表项它的存在位是0,意味着这个虚拟机空间对应的物理空间是没有的,不存在映射关系,就会产生访问异常,那异常机制会用来作为虚拟内存管理的效率手段。
根据当前页式内存管理,再增加两个新的功能,一个是请求调页,第二个是页面置换:
- 请求调页: 需要访问这个页, 才把这个页调到内存中来。
当一个用户程序需要调入内存运行时,不是把所有程序都放到内存来,而是装入一部分程序,放到个别页里面去,运行时候就有可能出现缺页异常,因为建立映射的时候,只映射一部分内存空间,接下来访问那些数据和代码还没有映射上去会产生异常,因为访问数据或者代码不在内存中,这就需要请求调页,真正访问的时候,不在的时候才发出请求。
~
这时候 CPU 会向操作系统发出缺页异常信号,操作系统接收信号之后,会根据当前产生异常地址来找到对应硬盘或者外存中的地址到底是哪个数据需要被调到内存中去。数据找出来之后再放到相应的物理页去,使得该页可以继续执行。
- 页面置换: 随着程序执行,占用内存越来越多,可能内存不够用,这时候就需要把某些给换出去,把某些需要的页换进来,那换出去的页是不常用页,换进来页是当前正需要的页,那这换入换出就是置换功能。这个功能实现得好坏决定了整体的效率,后面专门介绍有效的置换算法。
5.1 页表表项
为了能够实现请求校验和页面置换,需要在页表中增加一些位,辅助完成这个功能,4个位是比较重要的。
本来页表项里面存的最主要是页帧号,为了能够有效实现虚存管理,还需要增加几位:
-
第一个位 驻留位:表示是该页在不在内存里面,1 代表该页在内存中,意味着它的逻辑页号一定会有对应的物理页号,若为0 代表是当前逻辑页在内存中没有对应的物业页来支持。它的数据有可能是放在硬盘上,这时候如果访问到这一页,就会产生一次缺页中断,这是所谓驻留位,这很重要,它决定了所访问数据是否在内存。
-
保护位: 代表能否访问这个地址。有可能这个页是只读的,那写操作就会产生错误,因为它是只读的。有可能是可读可写的,那这时候写操作没问题。还有可能是可执行的,表示这段区域可以执行,那这时候做具体代码的执行操作是没问题,但是如果这个区域是不可执行的,它只是可读可写的,对这块区域做执行操作也会产生异常。
-
修改位: 代表这个页是否被修改过。如果这个页被写过,会被置1,若没被写过,那个页会被置成 0,这很重要。
如果这页被写过,在内存中就维持这个数据,与之前放在硬盘的数据是不一致的,这时候在做换入换出时候,需要把数据导回到硬盘中,使得硬盘中保持数据和内存数据是一致的。如果修改位是0,意味着对这个页没有做写操作,那么它的数据和当时在硬盘中数据是一样。这时候就不需要做写回,如果把这页给换掉的话,直接释放就 OK 了。因为硬盘中的数据和内存中的数据是一致的,下次需要的时候再从内存中调就行了,通过这个修改为处理可以有效地提高置换功能的效率。
-
访问位:,代表这个页最近是否访问过。被访问过,会置成1,没有被访问过,置成0。这个很重要,因为在置换算法中,要把一些页换出去,到底换哪些?应该换当前没有被访问的页,把它换出去,那这个位在一定程上表明当前这个页是否经常被访问,如果这个页经常被访问,它应该置成1,既然它没有被置成1就意味着这个页很长时间没访问了,这个页也许不是将来很近的时间之内要会访问的页,就把这个页给换出去。
所以说这几个 bit 位会有效地帮助后续去做页的置换。
5.2 示例
看下图可以更进一步了解,有了这个位之后会产生现象。
左边图是它的虚存的页表的映射关系,它一共有64K,每一个页表项代表有4K物理页,X 表示驻留位为0,如果是一个具体的数,代表驻留位为1,那它的映射关系是有效的。
- 在这种情况下,如果说做一个访问,比如说想把虚拟的0地址的内容赋给一个寄存器,那查0地址在页表里面对应的映射关系,首先可以看最底下的值为 2,表示驻留位为1,且叶帧号是2,所以 2再乘以一个页大小是 8K,所以说它实际访问的物理地址是8192,这个操作很正常,没问题。
- 接下来,把虚拟地址32780的内容读到寄存器里面去。32780 其实对应页表项是第八项,32K ~ 36 k 区间,它里面驻留位设置是0,所以没有对应页帧号,也意味着访问这页会产生缺页异常,根据缺页机制把相应的页从外存中调进来。