旧游无处不堪寻
无寻处,惟有少年心
操作系统(三)

上一篇我们说完了 CPU 的虚拟化,本篇我们介绍一下内存虚拟化相关内容。

用户程序生成的每个地址都是虚拟地址,在一些硬件帮助下,操作系统会将这些虚拟地址变成真实的物理地址。操作系统为什么要提供这种假象呢?主要是为了易于使用,操作系统会让每个程序觉得,它拥有一个连续地址空间来存储其代码和变量。同时也是为了隔离和保护其他程序以及操作系统,不希望一个错误的程序能够读取或者覆写其他程序的内存。

根据以上需求,操作系统提供一个易用的物理内存抽象。这个抽象叫作地址空间(address space),是运行的程序看到的系统中的内存。

我们使用 C 语言程序打印指针时,打印出的地址也是虚拟内存,虚拟地址只是提供地址如何在内存中分布的假象,只有操作系统和硬件才知道物理地址。

地址转换

如何实现高效的内存虚拟化?操作系统利用了一种通用技术,被称为基于硬件的地址转换(hardware-based address translation),简称为地址转换(address translation)。

利用地址转换,硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址。因此,在每次内存引用时,硬件都会进行地址转换。

分段

如果我们将整个地址空间放入物理内存,那么栈和堆之间的空间并没有被进程使用,却依然占用了实际的物理内存。为了解决这个问题,分段(segmentation)的概念应运而生。我们常听到的段错误指的是在支持分段的机器上发生了非法的内存访问。

分页

操作系统有两种方法,来解决大多数空间管理问题。第一种是将空间分割成不同长度的分片,就像虚拟内存管理中的分段。遗憾的是,这个解决方法存在固有的问题,将空间切成不同长度的分片以后,空间本身会产生碎片化。由此产生第二种方法,就是将空间分割成固定长度的分片。在虚拟内存中,我们称这种思想为分页。
分页不是将一个进程的地址空间分割成几个不同长度的逻辑段(即代码、堆、栈),而是分割成固定大小的单元,每个单元称为一页。

为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。页表的主要作用是为地址空间的每个虚拟页面保存地址转换(address translation),从而让我们知道每个页在物理内存中的位置。

交换空间

假设我们需要支持许多同时运行的巨大地址空间。 为了达到这个目的,需要在内存层级上再加一层。在硬盘上开辟一部分空间用于物理页的移入和移出。在操作系统中,一般这样的空间称为交换空间(swap space),因为我们将内存中的页交换到其中,并在需要的时候又交换回去。