内存虚拟化的相关概念
虚拟地址空间:
- 每个进程在运行时都有自己独立的虚拟地址空间,从逻辑上看,每个进程都拥有连续而私有的地址空间。
- 进程是操作系统为正在运行的程序提供的抽象,进程可以访问的内存称为地址空间。地址空间是物理内存的抽象,是运行的程序看到的系统中的内存。用户程序生成的每个地址都是虚拟地址,每个进程都有自己的虚拟地址空间,操作系统负责将这些虚拟地址转换为物理地址。
物理地址空间:
- 物理地址空间是实际存在于计算机硬件中的内存地址空间,用于存储进程的真实数据和程序代码。
- 当进程访问虚拟地址时,操作系统会将虚拟地址转换为对应的物理地址,从而找到数据或指令在内存中的真正位置。
- 物理地址空间是由计算机的物理内存组成,包括主存(RAM)、磁盘交换空间(swap space)等。
虚拟内存: 虚拟内存是操作系统提供的一种内存管理能力,使得程序可以使用比实际物理内存更多的内存。它通过将程序的地址空间映射到物理内存来实现。 所谓的内存虚拟化就是操作系统在单一的物理内存上为多个运行的进程(所有进程共享内存)构建一个私有的、可能很大的地址空间的抽象。和CPU虚拟化一样,内存虚拟化需要一些底层机制和相关策略
上图左边就是虚拟地址空间空间,右边就是物理地址空间,而虚拟地址要转换为物理地址就需要做地址映射,下面介绍下地址转换机制
地址转换机制
地址转换: 硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址
- 基址加界限的动态重定位: 基址加界限机制是地址转换的应用实例,具体来说就是每个 CPU 需要一对硬件寄存器,基址(base)寄存器和界限(bound)寄存器,有时称为限制(limit)寄存器。这组基址和界限寄存器,让我们能够将地址空间放在物理内存的任何位置,同时又能确保进程只能访问自己的地址空间。进程中使用的内存引用都是虚拟地址(virtual address),基于硬件的地址转换接下来会将虚拟地址加上基址寄存器中的内容,得到物理地址(physical address),再发给内存系统。CPU 的这个负责地址转换的部分统称为内存管理单元(Memory Management Unit,MMU)。缺点: 是已经分配的内存单元内部有未使用的空间(即碎片),造成了浪费,这种浪费通常称为内部碎片(internal fragmentation),比如下图中未使用的内存空间
分段机制 Segmentation
基本思想: 在 MMU 中引入不止 一个基址和界限寄存器对,给地址空间内的每个逻辑段(segment)一对。比如在典型的地址空间里有 3 个逻辑不同的段:代码、栈 和堆,那就要三对基址和界限寄存器,而一个段是地址空间里的一个连续定长的区域。每个进程有一个段表,存储段的基址和限长,用于地址转换。
缺点: 在内存中分配不同大小的段会导致空闲内存被割裂成各种奇怪的大小,并且分段还是不足以支持更一般化的稀疏地址空间。
分页机制 Paging
基本思想: 分页是将将虚拟地址空间和物理地址空间分成固定大小的块(页和帧),通过页表进行映射。
页表: 为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表。页表的主要作用是为地址空间的每个虚拟页面保存地址转换,从而让我们知道每个页在物理内存中的位置。由于页表如此之大,所以把页表保存在物理内存中
英文术语:
- VPN:virtual page number,虚拟页面号,表示哪个页
- PFN:physical page number,物理帧号,表示哪个物理页
- PTE:Page Table Entry,页表项,页表项是页表中的一个条目,存储虚拟地址到物理地址的映射关系,每个条目对应一个虚拟页
- PDE:Page Directory Entries,页目录项
分页带来的问题:
- 额外的内存访问: 使用分页作为虚拟地址空间管理,就意味着需要一个页表保存每个进程的虚拟页到物理页帧的映射关系。这样每次进行虚拟内存地址到物理内存地址转换时都需要去访问页表获取映射信息,而页表又保存在物理内存中,所以相当于增加了一次额外的内存访问。
- 内存浪费: 页表太大,因此消耗的内存太多。
下面就是这两个问题如何解决。
分页 - TLB
为了解决分页带来额外的内存访问,可以使用缓存来减少内存访问次数。
TLB(Translation Lookaside Buffer)就是一种用于提高虚拟地址到物理地址转换速度的缓存机制,用于缓存最近使用的页表项(Page Table Entries, PTE),从而加速虚拟地址到物理地址的转换过程
分页:较小的表
页表放在内存里,太多的话消耗的内存也会变多,所以要想方设法减少页表的大小。
- 使用更大的页:
在虚拟地址空间32位不变的情况下,页越大,偏移量也越大,那VPN也就越小,页表对应的PFN也就越小,所以页表整体就会变小 - 分页和分段
- 多级页表
重点介绍下多级页表:
基本思想:首先,将页表分成页大小的单元。然后,如果整页的页表项(PTE)无效,就完全不分配该页的页表。为了追踪页表的页是否有效(以及如果有效, 它在内存中的位置),使用了名为页目录(page directory)的新结构。页目录因此可以告诉你页表的页在哪里,或者页表的整个页不包含有效页。
其实就是多加了页目录这一层用来表示该项指向的页表对应的PFN和页表是否有效
当进程访问一个虚拟地址时,操作系统首先获取该虚拟地址。操作系统通过将虚拟地址分解为虚拟页号和页内偏移量,使用虚拟页号查找页表中的页表项,检查有效位来确定该页是否在物理内存中,并根据页表项中的物理页框号构建物理地址,最终访问物理内存
页交换的机制和策略
机制:
进程支持巨大的地址空间能够带来程序编码的方便和易用性。为了支持更大的地址空间,操作系统需要把当前没有在用的那部分地址空间找个地方存储起来,这个地方就是磁盘交换空间,而使用交换空间能够让系统假装内存比实际物理内存更大。
在硬盘上开辟一部分空间用于物理页的移入和移出。在操作系统中,一般这样的空间称为交换空间(swap space),因为我们将内存中的页交换到磁盘,并在需要的时候又交换回内存。
进程的虚拟地址空间被分为很多页,可能只有一部分有效页在内存中,剩下的在硬盘的交换空间中。也可能进程目前没有运行,所有页都被交换到硬盘上。
策略:
空闲内存空间是固定有限的,每当页从磁盘交换空间换入到内存时(page in),空闲内存就会减少,当内存满了之后操作系统就会先交换出(page out)一个或多个页,以便为操作系统即将交换入的新页留出空间。这种页的换入换出就涉及到页交换策略
- 最优替换策略:替换内存中在最远将来才会被访问到的页(这可以达到缓存未命中率最低,但不切实际,仅用来做对比)
- FIFO(先入先出)替换策略:页在进入系统时,简单地放入一个队列。当发生替换时,队列尾部的页(“先入”页)被踢出。
- 随机替换策略:在内存满的时候它随机选择一个页进行替换
- LRU:最少最近使用,可以利用历史数据。
- 近似LRU:于实现完美的 LRU 代价高昂,近似 LRU 更为可行。
内存访问的完整流程
现在我们可以尝试描述下内存访问的完整流程:
硬件首先从虚拟地址获得 VPN,检查 TLB 是否匹配(TLB 命中),如果命中,则获得最终的物理地址并从内存中取回。如果在 TLB 中找不到 VPN(即 TLB 未命中),则硬件在内存中查找页表(使用页表基址寄存器),并使用 VPN 查找该页的页表项(PTE)作为索引。如果页有效且存在于物理内存中,则硬件从 PTE 中获得 PFN,将其插入 TLB,并重试该指令,这次产生 TLB 命中。
当硬件在 PTE中查找时,可能发现页不在物理内存中。页表项增加一个存在位,如果存在位设置为 1,则表示该页存在于物理内存中;如果存在位设置为零,则页不在内存中,而在硬盘上。访问不在物理内存中的页,这种行为通常被称为页错误(page fault)。
-
页错误如何处理
- 当操作系统接收到页错误时,它会在 PTE 中查找地址,并将请求发送到硬盘,将页读取到内存中。
- 当硬盘 I/O 完成时,操作系统会更新页表,将此页标记为存在,更新页表项(PTE)的PFN 字段以记录新获取页的内存位置,并重试指令。下一次重新访问 TLB 还是未命中,然而这次因为页在内存中,因此会将页表中的地址更新到 TLB 中(也可以在处理页错误时更新 TLB 以避免此步骤)。最后的重试操作会在 TLB 中找到转换映射,从已转换的内存物理地址,获取所需的数据或指令。