机制:地址转换-重定位技术

在开发CPU虚拟化时,我们关注的是一种称为有限直接执行(LDE)的通用机制。LDE背后的想法很简单:在大多数情况下,让程序直接运行在硬件上;但是,在某些关键时刻(例如当一个进程发出一个系统调用,或者发生一个计时器中断)时,要安排操作系统参与,并确保正确的事情发生。因此,使用少量硬件支持的操作系统,尽最大努力摆脱运行程序的方式,实现高效的虚拟化;然而,通过在关键时刻插入,操作系统确保了它对硬件的控制。效率和控制是现代操作系统的两个主要目标。在虚拟化内存中,我们将采用类似的策略,在提供所需虚拟化的同时实现效率和控制效率要求我们使用硬件支持,这在开始时是相当初级的(例如,只有几个寄存器),但将会变得相当复杂(例如,TLBs,页表支持等等,如您所见)。控制意味着操作系统确保不允许应用程序访问任何内存但自己可以;因此,为了保护应用程序从一个另一个,从应用程序和操作系统,我们将从这里的硬件也需要帮助最后,在灵活性方面,我们还需要更多的VM系统。具体来说,我们希望程序能够以他们喜欢的方式使用他们的地址空间,从而使系统更容易编程。

  • 症结

如何有效和灵活地虚拟化内存?我们如何提供应用程序所需的灵活性?我们如何保持对应用程序能够访问的内存位置的控制,从而确保应用程序内存访问受到适当的限制?我们如何保持对应用程序能够访问的内存位置的控制,从而确保应用程序内存访问受到适当的限制?

我们将使用的通用技术,您可以考虑添加到有限直接执行的一般方法,这是指基于硬件的地址转换,或者简称为地址转换。通过地址转换,硬件可以转换每个内存访问(例如,一个指令获取、加载或存储),改变由指令提供的虚拟地址到实际地址的物理地址。因此,在每个内存引用中,一个地址转换由硬件执行,以将应用程序内存引用重定向到内存中的实际位置。

当然,单独的硬件不能虚拟化内存,因为它只是提供了低层次的机制来高效地工作。操作系统必须参与关键的点来设置硬件,以便进行正确的翻译。因此,它必须管理内存,跟踪哪些位置是空闲的,哪些位置正在使用,并明智地进行干预以保持对内存使用方式的控制。再一次,所有这些工作的目标是创建一个美丽的幻觉:程序有自己的私有内存,它自己的代码和数据驻留在那里。在这个虚拟现实的背后,隐藏着一个丑陋的物理事实:许多程序实际上是同时共享内存的,因为CPU(或CPU)在运行一个程序和下一个程序之间切换。通过虚拟化,OS(在硬件的帮助下)将丑陋的机器变成了有用的、强大的、易于使用的抽象。

15.1假设

我们第一次尝试虚拟化内存将非常简单,几去吧,尽情地笑吧;很快,当你试图理解TLBs、多层次的页面表和其他技术奇迹的细节时,它就会成为你的笑柄。乎是可笑的。你不喜欢有人嘲笑你吗?好吧,那你就不走运了;这就是操作系统的运作方式。具体地说,我们现在假定用户的地址空间必须连续地放在物理内存中。为了简单起见,我们还假设地址空间的大小不太大。具体来说,它小于物理内存的大小。最后,我们还假设每个地址空间都是完全相同的大小。不要担心这些假设听上去不现实;我们将在离开时放松它们,从而实现对内存的现实虚拟化。

15.2一个例子

为了更好地理解我们需要做什么来实现地址转换,以及为什么需要这样的机制,让我们来看一个简单的例子。假设有一个进程,其地址空间如图15.1所示。我们将要研究的是一个简短的代码序列,它从内存中加载一个值,将其增加3,然后将值存储回内存中!

您可以想象这段代码的c语言表示可能是这样的:


编译器将这行代码转换成程序集,它看起来像这样(在x86汇编中)。在Mac上使用objdump或otool来分解它。


这个代码片段相对简单;它假定x的地址已被放置在寄存器ebx中,然后将该地址的值加载到通用寄存器eax中,使用movl指令(用于longword移动)。下一条指令将3添加到eax,最后一条指令将eax中的值存储到相同位置的内存中

在图15.1(第4页)中,您可以看到在进程的地址空间中如何列出代码和数据;三个指令代码序列位于地址128(位于顶部附近的代码段),而变量x的值位于地址15kb(位于底部的堆栈中)。在图中,x的初始值为3000,如它在堆栈上的位置所示。当这些指令运行时,从进程的角度来看,会发生以下的内存访问。

在地址128上获取指令;此指令(从地址15kb加载);在地址132取指令;执行此指令(无内存引用);在地址135取指令;执行此指令(存储地址为15kb)。


从程序的角度来看,它的地址空间从地址0开始,增长到最大的16kb。它生成的所有内存引用都应该在这些范围内。然而,为了虚拟化内存,操作系统希望将进程放置在物理内存的其他地方,而不是在地址为0的地方。因此,我们有一个问题:我们如何将这个过程以一种透明的方式重新分配到内存中?我们如何能提供从0开始的虚拟地址空间的错觉,实际上,地址空间位于其他物理地址?


当这个进程的地址空间被放置在内存中时,物理内存可能看起来是什么样的一个示例如图15.2所示在图中,您可以看到操作系统使用物理内存的第一个槽,并且它已经将进程从上面的示例迁移到从物理内存地址32kb开始的槽中。另外两个插槽是免费的(16 KB-32 KB和48 KB- 64kb)。

15.3动态(基于硬件)搬迁

为了理解基于硬件的地址转换,我们将首先讨论它的第一个版本。在20世纪50年代末的第一次分时机器中引入的是一个简单的概念,它被称为基础和边界;该技术也被称为动态重新定位;我们将交替使用这两个术语。具体地说,我们需要在每个CPU内部有两个硬件寄存器:一个称为基本寄存器,一个称为边界(有时称为限制寄存器)。这个基础和边界对允许我们将地址空间放置在物理内存中的任何地方,并且在确保进程只能访问自己的地址空间时这样做。

在这个设置中,每个程序都被编写和编译,就好像它是在地址0中加载的。但是,当一个程序开始运行时,操作系统决定在物理内存中应该加载它,并将基寄存器设置为该值。在上面的示例中,操作系统决定在物理地址32 KB上加载进程,从而将基寄存器设置为这个值。当进程运行时,有趣的事情开始发生。现在,当进程生成任何内存引用时,它由处理器以如下方式翻译

物理地址=虚拟地址+基数。

基于软件的迁移--加载器,静态重定位

在早期,在硬件支持出现之前,一些系统仅仅通过软件方法进行粗略的迁移。基本技术被称为静态重定位,其中一种称为加载器的软件接受即将运行的可执行文件,并将其地址重写为物理内存中所需的偏移量。例如,如果一个指令是一个从地址1000到寄存器的负载。程序的地址空间是从地址3000(而不是0,如程序所认为的)开始加载的,加载程序将重写指令,将每个地址偏移3000(例如,movl4000, %eax)。通过这种方式,实现了进程地址空间的简单静态重新定位。

然而,静态迁移有很多问题。首先也是最重要的一点是,它不提供保护,因为进程可以生成糟糕的地址,从而非法访问其他进程s甚至OS内存。一般来说,真正的保护可能需要硬件支持[WL+93]。另一个缺点是,一旦放置,很难将地址空间重新定位到另一个位置[M65]。

进程生成的每个内存引用都是一个虚拟地址;硬件依次将基础寄存器的内容添加到这个地址,结果是可以向内存系统发出的物理地址。

为了更好地理解这一点,让我们来看看执行单个指令时会发生什么情况。具体来说,让我们看一下前面的序列中的一条指令。


程序计数器(PC)设置为128;当硬件需要获取该指令时,它首先将值增加到32 KB(32768)的基础寄存器值,以获得32896的物理地址;硬件然后从那个物理地址获取指令。下来,处理器开始执行指令。在某个时候,进程会从虚拟地址15kb中发出负载,处理器会将其添加到基本寄存器(32kb)中,获取最终的物理地址为47 KB,从而获得所需的内容。虚拟地址转换为物理地址正是我们所称的地址转换技术;也就是说,硬件接受一个虚拟地址,该进程认为它正在引用并将其转换为物理地址,而物理地址就是数据实际驻留的地方。因为这个地址的迁移是在运行时发生的,而且由于我们可以在进程开始运行之后移动地址空间,所以这种技术通常被称为动态迁移[M65]。

提示:基于硬件的动态迁移

关于绑定寄存器的一小部分,可以用两种方式之一定义。有了动态的重新定位,一个小的硬件就能走很长的路。也就是说,一个基本寄存器用于将虚拟地址(由程序生成)转换成物理地址。边界(或限制)寄存器可以确保这些地址在地址空间的范围内。它们一起提供了简单而高效的内存虚拟化。现在你可能会问:那个界限(极限)寄存器发生了什么变化?毕竟,这不是基础和界限的方法吗?事实上,它是。正如您可能已经猜到的,边界寄存器是用来帮助保护的。具体来说,处理器将首先检查内存引用是否在一定范围内,以确保它是合法的;在上面的简单示例中,边界寄存器总是被设置为16 KB。如果一个进程生成一个大于边界的虚拟地址,或者一个为负数的虚拟地址,则CPU会引发异常,并且进程可能会被终止。因此,边界的目的是确保过程生成的所有地址都是合法的,并且在流程的范围内。我们应该注意到,基础和边界寄存器是保存在芯片上的硬件结构(每个CPU一个对)。有时人们称处理器的一部分帮助处理地址转换的内存管理单元(MMU);当我们开发更复杂的内存管理技术时,我们将为MMU增加更多的电路。

第一种方法如上所述),它拥有地址空间的大小,因此在添加基础之前,硬件首先检查虚拟地址。在第二种方法中,它包含地址空间的末尾的物理地址,因此硬件首先添加了基础,然后确保地址在边界内。在第二种方法中,它包含地址空间的末尾的物理地址,因此硬件首先添加了基础,然后确保地址在边界内。这两种方法在逻辑上是等价的;为了简单起见,我们通常采用前一种方法。

例子的翻译

为了更详细地理解地址转换,让我们来看一个例子。假设有一个地址空间大小为4 KB(是的,非常小)的进程已经加载到物理地址16kb。下面是一些地址转换的结果。


从示例中可以看到,简单地将基本地址添加到虚拟地址(可以正确地将其看作是地址空间的偏移量),以获得最终的物理地址。只有当虚拟地址太大或太消极时,才会导致错误,引发异常。

旁白:数据结构的自由列表。操作系统必须跟踪哪些部分的空闲内存没有使用,以便能够将内存分配给进程。当然,许多不同的数据结构可以用于这样的任务。最简单的(我们将在这里假设)是一个自由列表,它只是一个当前没有使用的物理内存范围的列表。自由列表存储没有被使用的物理地址。

15.4硬件支持:总结。

现在让我们从硬件中总结我们需要的支持(参见图15.3,第9页)。

首先,正如在CPU虚拟化章节中所讨论的,我们需要两种不同的CPU模式。操作系统运行在特权模式(或内核模式),在那里它可以访问整个机器;应用程序在用户模式下运行,在它们所能做的事情上受限。一个字节,可能存储在某种处理器状态字中,表示当前CPU正在运行的模式;在某些特殊情况下(例如,系统调用或其他类型的异常或中断),CPU切换模式。

硬件还必须提供基础和边界寄存器本身;因此,每个CPU都有另外一对寄存器,这是CPU的内存管理单元(MMU)的一部分。当用户程序运行时,硬件将通过向用户程序生成的虚拟地址添加基本值来翻译每个地址。硬件还必须能够检查地址是否有效,这是通过在CPU内部使用边界寄存器和一些电路实现的。

硬件应该提供特殊的指令来修改基础和边界寄存器,允许操作系统在不同的进程运行时更改它们。这些指令是特权;只有在内核(或特权)模式下,寄存器才能被修改。想象一下,如果用户进程可以在运行时任意更改基本寄存器,那么它将造成多大的破坏。想象一下吧!然后迅速将这些阴暗的想法从你的头脑中冲洗出来,因为它们是做噩梦的可怕的东西。

最后,CPU必须能够在用户程序试图非法访问内存的情况下生成异常(带有超出范围的地址);在这种情况下,CPU应该停止执行用户程序,并安排OS越界异常处理程序运行。操作系统处理程序可以找到如何反应的方法,在这种情况下可能会终止这个过程。类似地,如果用户程序试图更改(特权)基础和边界寄存器的值,则CPU应该引发异常,并运行试图在用户模式下执行特权操作的操作。

机制:地址转换。

  • 硬件要求
  • 注释
  • 特权模式
需要防止用户模式进程执行特权操作。
基地/界限寄存器每个CPU需要一对寄存器来支持地址转换和边界检查。
翻译虚拟地址的能力和检查是否在边界内。电路进行翻译和检查图形界限,在这种情况下,非常简单。
特权指令(s)更新基础/范围操作系统必须能够设置这些值在让用户程序运行之前。
特权指令(s)登记异常处理器操作系统必须能够告诉硬件。如果异常发生,代码将运行什么代码
提高异常的能力当进程试图访问特权指令或超出范围的内存时

15.5操作系统问

就像硬件提供了支持动态重新定位的新特性一样,操作系统现在也有了它必须处理的新问题;硬件支持和操作系统管理的结合导致了一个简单的虚拟内存的实现。具体地说,有一些关键的连接,操作系统必须参与实现我们的虚拟内存的基本和边界版本。

首先,在创建进程时,操作系统必须采取行动,为内存中的地址空间寻找空间。幸运的是,假设每个地址空间(a)小于物理内存的大小(b)大小相同,这对于操作系统来说非常简单;它可以简单地将物理内存看作一个插槽的数组,并跟踪每个槽是否自由或在使用中。当创建一个新的进程时,操作系统将不得不搜索一个数据结构(通常称为空闲列表),以便为新的地址空间找到空间,然后标记它。使用可变大小的地址空间,生活更加复杂。但我们将把这个问题留给以后的章节。

让我们来看一个例子。在图15.2(第5页)中,您可以看到操作系统使用物理内存的第一个槽,并且它已经将进程从上面的示例迁移到从物理内存地址32kb开始的槽中。另外两个插槽是免费的(16 KB-32 KB和48 KB- 64kb);因此,自由列表应该包含这两个条目。

第二,当一个进程被终止时,操作系统必须执行一些工作。当它优雅地退出时,或者由于它的错误行为而被强制执行,它会回收所有内存,以便在其他进程或操作系统中使用。在终止一个进程后,因此,操作系统将其内存放回空闲列表,并根据需要清理任何相关的数据结构

第三,当发生上下文切换时,操作系统还必须执行一些额外的步骤。毕竟,每个CPU上只有一个基础和边界寄存器对,而且每个运行程序的值都是不同的,因为每个程序都是在内存中的不同物理地址加载的。具体地说,当操作系统决定停止运行一个进程时,它必须将基础和边界寄存器的值保存到内存中,比如在一些进程结构或过程控制块(PCB)中。类似地,当OS恢复运行进程(或者第一次运行它)时,它必须将基础的值和CPU的边界设置为这个过程的正确值。我们应该注意到,当一个进程被停止时(即:在内存中,操作系统可以很容易地将地址空间从一个位置移动到另一个位置。为了移动一个进程的地址空间,操作系统首先对进程进行分解然后,操作系统将地址空间从当前位置复制到新位置;最后,操作系统更新保存的基本寄存器(在过程结构中)指向新的位置。当进程恢复时,它的(新的)基础寄存器被恢复,它开始再次运行,并没有注意到它的指令和数据现在处于内存中一个全新的位置。

第四,操作系统必须提供异常处理程序或函数,如上所述;操作系统在引导时(通过特权指令)安装这些处理程序。例如,如果进程试图在其边界之外访问内存,则CPU将引发异常;当出现这样的异常时,操作系统必须准备好采取行动。操作系统的共同反应将是敌意:它很可能会终止这一过程。操作系统应该高度保护正在运行的机器,因此它不会对试图访问内存或执行的进程友好

图15.5 (page 11)展示了时间轴上的大部分硬件/操作系统交互。图中显示了OS在启动时所做的工作,以便为机器准备使用,然后当进程(进程a)开始运行时会发生什么情况;注意,它的内存转换是如何由硬件处理的,没有操作系统的干预。在某个时点,会发生一个计时器中断,操作系统切换到处理B,它执行一个坏的负载(到一个非法的内存地址);在这一点上,操作系统必须参与进来,通过释放B的内存并从进程选项卡中删除它的条目来终止进程并清理。

15.6总结

在本章中,我们将有限直接执行的概念扩展到虚拟内存中使用的特定机制,即地址转换。通过地址转换,操作系统可以控制进程中的每个内存访问,确保访问驻留在地址空间的范围内。这种技术的效率的关键是硬件支持,它可以快速地为每个访问执行转换,将虚拟地址(进程的内存视图)转换为物理地址(实际的视图)。所有这些都以一种透明的方式执行。

我们还看到了一种特殊的虚拟化形式,称为基础和边界或动态迁移。基于基础和边界的虚拟化是非常有效的,因为只需要稍微多一点硬件逻辑就可以在虚拟地址中添加一个基本寄存器,并检查进程生成的地址是否有界。Base-and-bounds还提供保护;操作系统和硬件结合起来,确保没有进程可以在自己的地址空间之外生成内存引用。保护无疑是操作系统最重要的目标之一;没有它,操作系统就无法控制机器(如果proc)。

不幸的是,这种动态迁移的简单技术确实存在效率低下的问题。例如,正如您在图15.2(第5页)中所看到的,迁移过程使用物理内存从32kb到48kb;但是,由于进程堆栈和堆不是太大,所以两者之间的所有空间都被浪费了。这种类型的浪费通常被称为内部碎片,因为所分配的单元内部的空间并不是全部使用的。,是碎片化的,因此被浪费了。在我们目前的方法中,虽然可能有足够的物理内存用于更多的进程,但是我们目前限制了。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值