写点什么

『内存中的操作系统』如何高效, 灵活的虚拟化内存 (1)

作者:soolaugust
  • 2022 年 1 月 24 日
  • 本文字数:1996 字

    阅读完需:约 7 分钟

『内存中的操作系统』如何高效, 灵活的虚拟化内存(1)

第一篇: 『内存中的操作系统』虚拟化是什么 - InfoQ 写作平台

第二篇: 『内存中的操作系统』内存虚拟化又是什么 - InfoQ 写作平台

思维模型

  1. 如何实现从虚拟地址到物理地址

1. 如何实现从虚拟地址到物理地址


在上文中, 我们讲解了如何虚拟化内存的大致思路, 并讲解了在设计之前的目标和准则. 那么接下来我们就要尝试进行详细设计我们的虚拟化内存方式.


我们最容易想到的就是基于硬件来直接做转换, 也就是硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址. 硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址.


这样的话, 我们同时还需要操作系统来设置好硬件, 以完成正确的地址转换, 同时需要管理内存, 记录已经使用和空闲的内存位置, 防止进程对内存的不合理使用.

1.1 地址转换


这个就是在 20 世纪 50 年代后期出现的虚拟内存的设计思路, 为了实现上面的目标, CPU 增加了两个硬件寄存器:基址(base)寄存器和界限(bound)寄存器,有时称为限制(limit)寄存器。这组基址和界限寄存器,让我们能够将地址空间放在物理内存的任何位置,同时又能确保进程只能访问自己的地址空间。即:


实际物理地址 = 虚拟地址 + 基址(base)


进程中使用的内存引用都是虚拟地址(virtual address),硬件接下来将虚拟地址加上基址寄存器中的内容,得到物理地址(physical address),再发给内存系统。


而界限寄存器则可以保证超过界限的内存访问都会被阻止, 系统会报错, 设置会终止进程, 从而可以有效的防止对内存的不合理使用.


这种基址寄存器配合界限寄存器的硬件结构是芯片中的(每个 CPU 一对)。有时我们将 CPU 的这个负责地址转换的部分统称为内存管理单元(MemoryManagement Unit,MMU)。这个再我们现在的硬件中都是有的, 不过它拥有的比我们这里说的更复杂的内容.


1.2 分段


那么上面的设计有没有什么问题呢? 答案其实很清晰, 那就是逻辑过于简单了, 虽然很多时候设计越简单越好, 但是前提是要满足复杂的现实场景. 根据上面的设计, 我们可以很容易画出我们目前的内存结构:



这里你可能已经看到问题在哪了, 那就是栈和堆之间,有一大块“空闲”空间。如果我们将整个地址空间放入物理内存,那么栈和堆之间的空间并没有被进程使用,却依然占用了实际的物理内存。因此,简单的通过基址寄存器和界限寄存器实现的虚拟内存很浪费。


另外,如果剩余物理内存无法提供连续区域来放置完整的地址空间,进程便无法运行。这种基址加界限的方式看来并不像我们期望的那样灵活。


所以只有基址寄存器和界限寄存器并不能满足实际的内存使用需求. 我们还需要更为复杂的设计, 也就是分段(segmentation). 分段并不是一个新概念,它甚至可以追溯到 20 世纪 60 年代初期。这个想法很简单,在 MMU 中引入不止一个基址和界限寄存器对,而是给地址空间内的每个逻辑段(segment)一对。


一个段只是地址空间里的一个连续定长的区域,在典型的地址空间里有 3 个逻辑不同的段:代码、栈和堆。分段的机制使得操作系统能够将不同的段放到不同的物理内存区域,从而避免了虚拟地址空间中的未使用部分占用物理内存。


那么根据我们的设计, 内存结构可能就是如下的样子:



从图中可以看到,只有已用的内存才在物理内存中分配空间,因此可以容纳巨大的地址空间,其中包含大量未使用的地址空间(有时又称为稀疏地址空间,sparseaddress spaces)。你会想到,需要 MMU 中的硬件结构来支持分段:在这种情况下,需要一组 3 对基址和界限寄存器。下面是一组示例:



根据我们的示例, 我们来看一个例子:


假设现在要引用虚拟地址 100(在代码段中),MMU 将基址值加上偏移量(100)得到实际的物理地址:100 + 32KB = 32868。然后它会检查该地址是否在界限内(100 小于 2KB),发现是的,于是发起对物理地址 32868 的引用。


如果我们有学过 Java 虚拟机 JVM 的内存结构, 就会发现其中的: 堆内存, 方法区和栈的设计思路就是我们这里的设计思路.


有趣的知识: segmentation fault

如果我们试图访问非法的地址,例如 7KB,它超出了堆的边界呢?你可以想象发生的情况:硬件会发现该地址越界,因此陷入操作系统,很可能导致终止出错进程。这就是每个 C 程序员都感到恐慌的术语的来源:段异常(segmentationviolation)或段错误(segmentation fault)。


来看一个堆中的地址,虚拟地址 4200(同样参考图 16.1)。如果用虚拟地址 4200 加上堆的基址(34KB),得到物理地址 39016,这不是正确的地址。我们首先应该先减去堆的偏移量,即该地址指的是这个段中的哪个字节。因为堆从虚拟地址 4K(4096)开始,4200 的偏移量实际上是 4200 减去 4096,即 104,然后用这个偏移量(104)加上基址寄存器中的物理地址(34KB),得到真正的物理地址 34920。


总结


到这里, 我们已经有了虚拟化内存的基本思路, 沿着这条思路, 让我们在接下来的文章继续细化我们的设计. 来达到高效,灵活的效果.

发布于: 刚刚阅读数: 2
用户头像

soolaugust

关注

公众号:雨夜随笔 2018.09.21 加入

公众号:雨夜随笔

评论

发布
暂无评论
『内存中的操作系统』如何高效, 灵活的虚拟化内存(1)