虚拟内存技术允许执行进程不必完全处于内存。这种方案的一个主要优点就是,程序可以大于物理内存。此外,虚拟内存将内存抽象成一个巨大的、统一的存储数组,进而实现了用户看到的逻辑内存与物理内存的分离。这种技术使得程序员不再担忧内存容量的限制。
虚拟内存还允许进程轻松共享文件和实现共享内存。此外,它为创建进程提供了有效的机制。然而,虚拟内存的实现并不容易,并且使用不当还可能会大大降低性能。
说了这么多,到底什么是虚拟内存呢?
内存管理算法的实现有一个基本要求,就是执行的指令应处于物理内存中。满足这一要求的第一种方法是,将整个逻辑地址空间置于物理内存中。动态加载可以帮助缓解这种限制,但它通常需要特殊的预防措施和程序员的额外工作。
指令应处于物理内存以便执行的要求,似乎是必要的和合理的,但它也是有缺点的,因为它将程序的大小限制为物理内存的大小。事实上,通过实际程序的研究会发现,在许多情况下并不需要将整个程序置于内存中。
例如,分析以下内容:
- 程序通常具有处理异常错误条件的代码。由于这些错误很少实际发生,所以这些代码几乎从不执行。
-
数组、链表和表等所分配的内存量通常多于实际需要值。按
100X100
个元素来声明的数组,可能实际很少用到大于10X10
个的元素。虽然汇编程序的符号表可能有 3000 个符号的空间,但是程序平均可能用到的只有不到 200 个符号。 - 程序的某些选项和功能可能很少使用。例如,美国政府计算机的平衡预算程序多年来都没有使用过。
即使在需要整个程序的情况下,也可能并不同时需要整个程序。分段能够执行只有部分处于内存的程序,可以带来许多好处:
- 程序不再受物理内存的可用量所限制。用户可以为一个巨大的虚拟地址空间编写程序,从而简化了编程任务。
- 由于每个用户程序可占用较少的物理内存,因此可以同时运行更多的程序,进而增加 CPU 利用率和吞吐量,但没有增加响应时间或周转时间。
- 由于加载或交换每个用户程序到内存所需的 I/O 会更少,用户程序会运行得更快。因此,运行不完全处于内存的程序将使系统和用户都受益。
虚拟内存将用户逻辑内存与物理内存分开。这在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存(如图 1 所示)。
图 1 虚拟内存大于物理内存的图例
因此,虚拟内存使得编程更加容易,因为程序员不再需要担心有限的物理内存空间,只需要关注所要解决的问题。
进程的虚拟地址空间就是进程如何在内存中存放的逻辑(或虚拟)视图。通常,进程从某一逻辑地址(如地址 0)开始,连续存放,如图 2 所示。
图 2 虚拟地址空间
注意,在图 2 中,随着动态内存的分配,允许堆向上生长。类似地,随着子程序的 不断调用,还允许堆栈向下生长。堆与堆栈之间的巨大空白空间(或空洞)为虚拟地址的一部分,只有在堆与堆栈生长时,才需要实际的物理页。包括空白的虚拟地址空间称为稀疏地址空间。采用稀疏地址空间的优点是随着程序的执行,堆栈或堆会生长或需要加载动态链接库(或共享对象),此时可以填充这些空白。
前面讲过,物理地址可以按帧来组织,并且分配给进程的物理帧也可以不连续。这就需要内存管理单元(MMU)将逻辑页映射到内存的物理页帧。
图 3 采用虚拟内存的共享库
除了将逻辑内存与物理内存分开外,虚拟内存允许文件和内存通过共享页而为多个进程所共享。这带来了以下好处:
- 通过将共享对象映射到虚拟地址空间中,系统库可以为多个进程所共享。尽管每个进程都将库视为其虚拟地址空间的一部分,但是驻留在物理内存中的库的实际页可由所有进程共享(图 3)。通常,库按只读方式映射到与其链接的进程空间。
- 类似地,虚拟内存允许进程共享内存。进程之间可以通过使用共享内存来进行通信。虚拟内存允许一个进程创建一个内存区域,以便与其他进程共享。共享这个内存区域的进程认为,它是其虚拟地址空间的一部分,而事实上这部分是共享的,如图 3 所示。
- 当通过系统调用 fork() 创建进程时,可以共享页面,从而加快进程创建。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/22003.html