前言
本文依旧是对《Operating Systems: Three Easy Pieces》一书的一个读书笔记,主要分享虚拟内存的一些内容,篇幅不多。
早期系统
从内存来看,早期的机器并没有提供多少抽象给用户。基本上,机器的物理内存看起来如图所示:
多道程序和时分共享
目前的机器都是多道程序,其中多个进程在给定时间准备运行,比如当有一个进程在等待I/O操作的时候,操作系统会切换这些进程。
粗糙的机器共享
很快,人们开始对机器要求更多,分时系统的时代诞生了,一种实现时分共享的方法,是让一个进程单独占用全部内存运行一小段时间,然后停止它,并将它所有的状态信息保存在磁盘上,加载其他进程的状态信息,再运行一个一段时间,这就是实现了某种比较粗糙的机器共享。
这个方法存在的问题是:速度太慢了,将进程的全部内存信息保存到磁盘中还是太慢了。因此,在进程切换的时候,我们仍然将进程信息放在内存中 (目前的做法) , 这样做更快,操作系统可以更有效率地实现时分共享。
高效的时分共享
如下图所示,在3个进程 (A, B, C) ,每个进程拥有从512KB物理内存只切出来给它们的一小部分内存,假定只有一个CPU,操作系统选择运行其中一个进程 (比如A) ,同时其他进程 (B和C) 则在队列中等待运行。
随着时分共享变得流行,人们对操作系统又有了新的要求,特别是多个程序同时驻留在物理内存中,「保护」 成为重要问题,具体来说就是如何保护一个进程拥有的内存不被其他进程读取,当然修改更不允许。
地址空间
操作系统提供了一个易用的物理内存抽象,这个抽象叫做 地址空间 (address space),是运行的程序看到的系统中的内存。一个进程的地址空间包含运行的程序的所有内存状态,比如:程序的代码必须在内存中。当程序运行时,利用栈来保存当前的函数调用信息,分配空间给局部变量,传递参数和函数返回值。最后,堆用于管理动态分配的、用户管理的内存.
下面是一个简答的地址空间的例子,我们假设有一个很小的地址空间 (只有16KB)。程序代码位于地址空间的顶部 (本例从 0 开始,并且装入到地址空间的前 1KB)。如图所示:
当然这样的地址空间,是操作系统提供给运行程序的抽象 (abstract),程序实际上不在物理地址 0 ~ 16KB 的内存中,而是加载在任意的物理地址。
如何虚拟化内存
此刻问题来了,多个进程如何加载到内存的不同地址,操作系统如何在的单一的物理内存上为多个运行的进程 (所有进程共享内存) 构建一个私有的、可能很大的地址空间的抽象?
现在的操作系统解决了这个问题,引入虚拟内存机制,虚拟内存系统为程序提供了一个巨大的、稀疏的、私有的地址空间的抽象,其中保存了程序的所有指令和数据。操作系统在专门的硬件的帮助下,通过每一个虚拟内存的索引,将其转换为物理地址,物理内存根据
获得的物理地址去获取所需的信息。
虚拟化内存的三个目标
本文对虚拟内存的具体实现机制不做讨论,主要分享虚拟内存的设计思想,也就是下面这三个目标
画外音:认识这三个目标可以让你更好的理解虚拟内存机制
透明
虚拟内存的一个主要目标是 「透明」。操作系统实现虚拟内存的方式,应该让运行的程序看不见。因此,程序不应该感知到内存被虚拟化的事实,相反,程序的行为就好像它拥有自己的私有物理内存。在幕后,操作系统 (和硬件) 完成了所有的工作,让不同的工作复用内存,从而实现了这个假象。
效率
虚拟内存的另一个目标就是 「效率」。操作系统应该追求虚拟化尽可能高效,包括时间上 (即不会使程序运行得更慢) 和空间上 (即不需要太多额外的内存来支持虚拟化)。在实现高效率虚拟化时,操作系统不得不依靠硬件支持,包括TLB功能。
保护
第三个目标是 「保护」,也可以说成「隔离」。操作系统应确保进程受到保护,不会受到其他进程影响,操作系统本身也不会受进程影响。当一个进程执行加载、存储或指令提取时,它不应该以任何方式访问或影响任何其他进程或操作系统本身的内存内容。
因此,保护让我们能够在进程之间提供隔离的特性,每个进程都应该在自己的独立环境中运行,避免其他出错或恶意进程的影响
参考资料
- Operating Systems: Three Easy Pieces