内存虚拟化
1. 动态重定位(base and bound)
基于硬件实现,通过基址寄存器(base)和界限寄存器(bound)。基址寄存器负责地址转换,界限寄存器用于确保这个地址处于进程地址空间范围内(界限检查)。
这种将虚拟地址转换为物理地址的技术,就是所谓的地址转换技术,在这个过程中,硬件取得虚拟地址,然后将他转换为实际的物理地址,这种重定位是运行时发生的,所以被称为动态重定位。
真实物理地址 = 进程基址 + 进程中的虚拟地址
2. 分段(segmentation)
若使用动态重定位技术,可以发现,栈与堆之间有大量的空闲空间,如果将这个进程的整个地址空间放入物理内存,那么栈和堆之间的地址虽然没有被进程使用,但依然会占用实际的物理内存。此外,还有另一个问题,当剩余物理内存无法提供连续区域来放置完整的地址空间,显然无法为这个进程开辟独立的地址空间。
因此在动态重定位基础上更进一步的分段技术(同样是基于硬件的),通过使用三个段寄存器,使得虚拟地址空间中的稀疏空间(堆与栈之间的空闲空间)消除。具体原理是增加了对堆、栈、代码区的基址和界限详细描述规范,而不是仅规范了整个进程的基址和界限。通过细化的描述,消除了虚拟地址空间的空闲空间。并且通过对堆、栈、代码区的详细分段,使得进程的真实物理地址不必连续。提高了物理内存的利用率。
现在整个流程是 进程中的虚拟地址 -> 对应的分段的虚拟地址二进制形式(段基址 + 段偏移) -> 真实物理地址
真实物理地址 = 当前进程下具体某段的基址(段基址) + 当前进程下具体某段的偏移(段偏移) = 当前进程下具体某段的基址 + 虚拟地址 - 虚拟空间中段的偏移量
段 | 段基址 | 大小 | 增长方向 | 二进制代号 |
---|---|---|---|---|
代码 | 32KB | 2KB | 1 | 00 |
堆 | 34KB | 2KB | 1 | 01 |
栈 | 28KB | 2KB | 0 | 11 |
注意: 在机器中,16KB虚拟地址的真实传递地址是14位的二进制,其中前两位用于标识段。后12位用于标识段偏移。计算可知,最大段偏移为4KB。
堆的虚拟地址-> 虚拟地址的二进制形式
以进程中虚拟地址4200为例,它处于堆区。它的段基址为34KB
从虚拟地址空间堆的虚拟地址偏移量为 4KB 。
真实物理地址 = 34KB + 4200 - 4KB
段偏移 = 4200 - 4KB = 4200 - 4096 = 104
下面计算其 虚拟地址的二进制形式
堆的二进制代号 01
段偏移 104 = 0x 0000 0110 1000
虚拟地址的二进制形式为 0x 01 0000 0110 1000
栈的虚拟地址 ->虚拟地址的二进制形式
进程中的虚拟地址 15KB, 处于栈区。 段基址为28KB。
虚拟地址空间 栈的虚拟地址偏移量为 16KB。
真实物理地址 = 28KB + 15KB - 16KB = 27KB。
段偏移 = -1 KB
下面计算其 虚拟地址的二进制形式
栈的二进制代号 11
段偏移 -1KB 由于段偏移最大为4KB 因此段偏移为 -1KB = 3KB = 0x 1100 0000 0000
虚拟地址的二进制形式为 0x 11 1100 0000 0000
3. 分页
分段可以使得虚拟地址空间内的未分配空间不会占用实际物理内存,但是没法解决实际物理内存的碎片问题,随着越来越多的物理内存未分配空间碎片化。新的程序将面临没有内存分配的尴尬,虽然剩余内存总量是够的,但是它们不连续,无法分配。
于是分页技术诞生了,分页的核心思想是将内存的使用按照固定大小分配,即划定某个固定量大小的内存为基本量,任何栈、堆、代码区的大小都是这个基本量的整数倍,这就是在物理内存中对应的页帧大小。这样做的优点有:方便了物理内存的分配,虚拟地址的栈、堆是连续的,这不意味着物理内存上的堆、栈也要连续,只要找到一定数量的页帧放入即可。
当然需要注意的是,如果仅仅使用简单的分页思想,是无法解决虚拟空间未分配空间对物理内存的占用问题。所以一般会结合分段一起使用。这样既可以使虚拟地址空间未分配空间不会占用物理内存,同时解决物理内存的碎片问题(虽然到处是碎片,但我有映射,不怕这个碎片)
分段、分页混合方法
与上文中所述的混合方法中的虚拟地址对应如下:
地址转换缓存TLB
分页技术中,虚拟地址与实际物理地址的映射称为页表(储存在内存中),如果不使用分段技术,显而易见,那么页表是巨大的,所以使用分段技术同时可以使得分页技术更加有效率。而且为了加速这个映射过程,在硬件上,有专门的技术来加速,称为快速地址转换缓存(TLB,translation-lookaside buffer),对于每次内存访问,硬件都会检查TLB,看看其中是否有期望的映射,如果有,就完成这个转换。不需要访问页表。