操作系统是一组例程(一个图书馆,真的),坐在记忆(在这个例子中从物理地址0),会有一个运行的程序(过程),目前坐在物理内存(物理地址开始64 k在本例中),其余的内存使用。这里几乎没有什么幻想,用户也没有对操作系统有太多的期望。在那些日子里,对于操作系统的开发者来说,生活是很容易的,不是吗?
多程序设计和时间共享。
过了一段时间,因为机器很贵,人们开始更有效地分享机器。:因此,多程序设计时代就这样诞生了。 (DV66)在给定的时间内,多个进程可以运行,并且操作系统会在它们之间切换,例如当一个人决定执行I/O时。这样做增加了CPU的有效利用率。这种效率的提高在那些每台机器花费几十万甚至几百万美元的日子里尤其重要(你认为你的Mac很贵!然而,很快,人们开始要求更多的机器,而时间共享的时代诞生了[S59, L60, M62, M83]。具体地说,许多人认识到批处理计算的局限性,尤其是对程序员本身(CV65),他们厌倦了长时间的(因此无效的)程序调试周期。交互性的概念变得非常重要,因为许多用户可能同时使用一台机器,每个人都在等待(或希望)他们当前执行的任务的及时响应。一个方法来实现分时运行一个进程是很短的一段时间,使它完全访问所有内存(图13.1,页1),然后停止,保存所有的状态的磁盘(包括所有物理内存),加载其他流程年代状态,运行它,从而实现某种原油共享机器的[M + 63]。加载其他进程的状态,运行一段时间,从而实现机器的某种原始共享[M+63]。不幸的是,这种方法有一个大问题:它太慢了,特别是随着内存的增长。:保存和恢复registerlevel状态(PC、通用寄存器等)。比较快,把内存的全部内容都保存到磁盘上是不是很残忍?因此,我们更愿意做的是将进程留在内存中,同时在它们之间进行切换,从而使操作系统能够有效地实现时间共享(图13.2)。
在图中,有三个进程(A、B和C),每个进程都有一个512KB物理内存的一小部分。假设一个CPU,操作系统选择运行其中一个进程(比如a),而其他的(B和C)则位于就绪队列中等待运行。随着时间共享变得越来越流行,您可能会猜测新的需求被放置在操作系统上。特别是,允许多个程序同时驻留在内存中,这使保护成为一个重要问题;您不希望进程能够读取或更糟地写入其他进程的内存。
- 地址空间
但是,我们必须记住那些烦人的用户,这样做需要操作系统创建一个易于使用的物理内存抽象。我们把这个抽象称为地址空间,它是系统中运行程序的内存视图。要理解内存是如何虚拟化的,理解这种基本的OS抽象是关键。进程的地址空间包含运行程序的所有内存状态。例如,程序的代码(指令)必须在内存中存在,因此它们位于地址空间中。该程序在运行时,使用堆栈来跟踪函数调用链中的位置,以及分配局部变量和传递参数,并从例程返回值。最后,堆用于动态分配的、用户管理的内存,例如,您可以从调用C的malloc()中接收到。或者是新的语言,比如c++或Java。当然,这里也有其他的东西(例如,静态初始化的变量),但是现在让我们假设这三个组件:代码、堆栈和堆。在图13.3(第3页)的示例中,我们有一个很小的地址空间(只有16KB)。程序代码位于地址空间的顶部(在本例中从0开始,并被打包到地址空间的前1K中)。代码是静态的(因此容易在内存中放置),因此我们可以将其放置在地址空间的顶部,并且知道在程序运行时它不需要更多的空间。接下来,我们有地址空间的两个区域,在程序运行时可能会增长(和收缩)。这些是堆(在顶部)和堆栈(在底部)。我们把它们放在这样的地方,因为每个人都希望能够成长,把它们放在地址空间的两端,我们就能允许这样的增长:它们只需要向相反的方向生长。因此,堆在代码(1KB)之后就开始了,并向下增长(当用户通过malloc()请求更多内存时);堆栈从16KB开始,并向上增长(比如当用户进行过程调用时)。但是,堆栈和堆的位置只是一个约定;如果您愿意,您可以用另一种方式来安排地址空间(正如我们稍后会看到的,当多个线程在一个地址空间中共存时,没有什么好方法可以像这样分割地址空间了,可惜)。当然,当我们描述地址空间时,我们描述的是操作系统提供给运行程序的抽象。这个程序在物理地址上的内存是0到16KB;相反,它是在一些任意的物理地址(es)上加载的。查看图13.2中的流程A、B和C;在这里,您可以看到每个进程如何在不同的地址加载到内存中。因此这个问题:关键:如何虚拟化内存。:操作系统如何构建一个私有的、潜在的大地址空间,用于多个运行进程(所有共享内存)在单个物理内存之上?
当操作系统这样做时,我们说操作系统是虚拟化内存,因为运行程序认为它是在一个特定的地址(比如0)加载到内存中,并且有一个潜在的非常大的地址空间(比如32位或64位);现实情况完全不同。例如,当过程在图13.2试图执行一个负载地址0(我们将称之为虚拟地址),操作系统,和一些硬件支持,必须确保负载并t真正去物理地址0,而是物理地址320 kb(加载到内存中。
提示:隔离原则。隔离是建立可靠系统的一个关键原则。如果两个实体彼此适当地隔离,这意味着一个实体可以失败而不影响另一个实体。操作系统努力将进程相互隔离,以这种方式阻止一个人伤害另一个。通过使用内存隔离,操作系统进一步确保运行程序不会影响底层操作系统的操作。一些现代操作系统甚至更进一步,通过将操作系统的其他部分从操作系统的其他部分中分离出来。这样的微内核[BH70, R+89, S+03]可能比典型的单片内核设计提供更大的可靠性。
13.4 Goals
因此,我们在这组注释中找到了操作系统的工作:虚拟化内存。操作系统不仅会虚拟化内存;它会以风格来做。为了确保操作系统能够做到这一点,我们需要一些目标来指导我们。
我们以前见过这些目标(想想引言),我们会再次看到它们,但它们确实值得重复。
虚拟内存(VM)系统的一个主要目标是透明的。操作系统应该以对运行程序不可见的方式实现虚拟内存。因此,程序不应该意识到内存被虚拟化的事实;相反,该程序的行为就好像它有自己的私有物理内存。在幕后,操作系统(和硬件)在许多不同的工作中完成了所有的工作,从
而实现了这个错觉。
VM的另一个目标是效率。操作系统应该努力使虚拟化尽可能地高效,无论是在时间上还是在时间上。而不是让程序运行得慢得多)和空间(也就是)。,不要对支
持虚拟化所需的结构使用过多的内存。在实现时间效率的虚拟化时,操作系统将不得不依赖硬件支持,包括硬件特性,如TLBs(我们将在适当的时候学习到)。
最后,第三个VM目标是保护,:操作系统应该确保从进程中保护进程和操作系统本身。当一个进程执行加载、存储或指令获取时,它不应该能够以任何方式访问
或影响任何其他进程或操作系统本身的内存内容(即,在其地址空间之外的任何内容)。因此,保护使我们能够在过程中交付隔离的特性;每个进程都应该在自己的孤
立的茧中运行,免受其他错误甚至恶意程序的破坏。
旁白:你看到的每个地址都是虚拟的。编写一个C程序,打印出一个指针。您所看到的值(通常是十六进制的数字)是一个虚拟地址。:想知道你的程序代码在哪里被找到。
你也可以打印出来,是的,如果你能打印出来,它也是一个虚拟地址。事实上,任何你能看到的用户级程序的程序员的地址都是一个虚拟地址。这仅仅是操作系统,通
过其巧妙的虚拟内存技术,知道机器的物理内存中这些指令和数据值在哪里。所以千万不要忘记:如果你在一个程序中打印出一个地址,它是一个虚拟的,一个在内存中
如何布局的错觉;只有操作系统(和硬件)知道真正的真相。
这里有一个小程序,它打印出main()例程的位置(代码在哪里),:从malloc()返回的heap分配值的值,以及堆栈上一个整数的位置。
:从这里,您可以看到在地址空间中首先出现了代码。然后堆,堆栈在这个大的虚拟空间的另一端。所有这些地址都是虚拟的,并且将由操作系统和硬件翻译,以便从它们
真实的物理位置获取值。在下一章中,我们将重点讨论虚拟化内存所需的基本机制,包括硬件和操作系统支持。我们还将研究一些您在操作系统中会遇到的更相关的策略,包
括如何管理空闲空间,以及当您在空间中运行的时候,哪些页面会导致内存溢出。:在这样做的过程中,我们将建立您对现代虚拟内存系统如何真正工作的理解。
13.5总结
我们已经看到了一个主要的OS子系统的介绍:虚拟内存。VM系统负责为程序提供一个大型的、稀疏的、私有的地址空间的错觉,这些程序包含了它们的所有指令和数据。操作
系统有一些重要的硬件帮助,它会将这些虚拟内存引用转换为物理地址,这些地址可以显示在物理内存中,以获取所需的信息。操作系统将同时对多个进程执行此操作,确保
保护程序彼此不受影响,同时保护操作系统。整个方法需要大量的机制(大量的低级机器)以及一些重要的工作策略;我们将从底层开始,首先描述关键机制。因此我们继续!