在早期,构建计算机系统是很简单的。为何?你可能会想。由于用户指望值不高。都是那些『该死』的用户,想要一个"易用"、"高性能"、"可靠"的系统,才引发了一系列头疼的问题。下一次你遇到这些计算机用户们,别忘记感谢一下他们制造出来的问题:)编程
从内存的角度来讲,早期的机器并无提供太多的抽象给用户。通常而言,机器的物理内存能够用下图表示:缓存
整个 OS(图中的阴影部分,译者注) 不过就是物理内存中的一些routines(本质上来讲,一个 library),在这个例子中,从物理地址的 address 0开始。同时,还有一个运行的程序(进程)位于物理地址的 address 64k 处,而且独自占据了整个剩下的内存。用户并没对操做系统寄予太大的指望,操做系统程序员的生活仍是很轻松的,不是吗?函数
一段时间事后,由于计算机实在是太贵了,人们开始更有效地共享有限的计算资源。因而多道编程
DV66:Programming Semantics for Multiprogrammed Computations 的时代开始了,在一个时刻,多个进程能够同时等待被执行,而后操做系统不断在这些进程中切换(好比当前进程进行 IO 的时候就能够切换到其余进程)。这样颇有效地提升了 CPU 的利用效率。在那个计算机动辄几百万美圆的时代,这些效率的提升是很是重要的。性能
然而,人是永远不会知足的,人名开始期待可以从操做系统那获取更多功能,因而分时系统的时代开始了。不少人尤为是程序员们本身意识到了 batch programming 的局限性,厌倦了很长(因而效率低下)的 debug 周期。由于不少用户在同时使用一台计算机,每一个人都在等待(甚至能够说是指望)本身的程序返回结果,可交互性(interactivity)的概念开始变得愈来愈重要。操作系统
实现 time sharing 的一种方法是让一个进程运行一小段时间,这段时间内让它占据全部的内存,而后中止它,把其全部的状态保持到硬盘上(包括全部的物理内存!),接口导入另一个进程,运行一段时间...... 这也算是一种简陋的实现方法。debug
显然,这种方法有很大的问题:太慢了,尤为是内存不断增大的时候。保存和欢迎寄存器级别的状态(好比 PC,general-purpose registers等等)是很快的,可是要把整个内存的所有内容保存到硬盘,这无疑不是一种高效的方法。因此,咱们得想办法在切换的时候把进程的状态保持在内存里面。设计
在这个图中,有三个进程(A,B,C),每一个占据 512KB内存的一小部分。如今对于 CPU,他能够选择任何一个抽象来运行(好比说 A),而后让另外两个(B 和 C)等待。3d
随着分时系统愈来愈流行,你确定猜到了人们又开始提出新的需求了。尤为重要的一点是:让这么多程序同时保存在内存里,保护措施就很重要,你可不但愿一个进程能够看到其余进程的内容,更别说能够随意修改其余进程的内容。code
用户是很难伺候的,操做系统的设计者得对物理内存作一个抽象,提供一个好用的接口给他们。咱们把这种抽象就叫作地址空间。对于运行中的程序而言,它对物理内存是无感知的,它知道的只有地址空间(虚拟的)。理解基本的操做系统抽象,是理解内存是如何虚拟化的关键!
进程的地址空间包含了运行进程的全部状态。好比说,代码总得保存在某个地方吧?因此代码就在地址空间里面。进程运行的时候,会用 stack 来保存当前函数调用链的位置,分配空间给局部变量,而且返回值。还有一部分,叫作 heap,被用来保存动态分配、用户管理的内存,好比调用 malloc() 函数,或者 new 一个对象,都是从这里面获得须要的内存。固然,保存在地址空间的内容还有不少,目前而言咱们只须要关注于这三点就好了。
上图中,有一个很小的地址空间(16kb)。程序的代码保存在地址空间的最上方(0-1kb)。代码是静态的,不会增长也不会减小,能够直接放在地址空间的最上部。还有两部分大小可变的区域,那就是 heap 和 stack。上图中,stack 和 heap 往两个不一样的方向增加。如进行 malloc()
申请内存时,heap 往下增加;当进行一个procedure call
时,stack 往上增加。固然这只是一个惯例,并无物理上的限制要求必定要这样,你能够以其余方法从新组织地址空间。(事实上,当多个 threads 共存的时候,也不存在这么优雅的地址空间划分方法。)
咱们谈地址空间的时候,咱们谈的是操做系统提供给进程的抽象。上图的进程并非在物理内存的0-16kb 位置,它能够被存放在物理内存的任意位置。回顾一下图13.2,你能够看到每一个进程被加载到内存的不一样位置。
当操做系统这么作的时候,咱们说操做系统是在虚拟化内存,由于运行中的进程是觉得本身占据了全部的内存的。然而现实很不同。
好比说,图13.2的进程A想要加载地址0处的数据(这里指的是虚拟内存),操做系统,具有特定的硬件支持,会把这个这个地址0映射到物理内存中另外的位置。这个就是内存虚拟化的核心,几乎每个现代计算机系统都在试用这种方法。
虚拟内存系统的最主要目标是透明化
。也就是底层的物理地址对运行的程序不可见。因此,程序根本就不会意识到内存是虚拟化的,而是认为本身有一个完整的私有物理地址。操做系统在幕后,承当着虚拟地址到物理地址的转换工做。这极大地下降了程序开发者地复杂度!
第二个目标是高效率
。操做系统应该更可能让抽象更加高效,包括时间上的和空间上的。好比,操做系统会依赖于一些硬件,好比 TLB 缓存,能够缩短操做系统访问用户内存的时间。
最后一个目标是保护
。操做系统须要确保进程间是隔离的,一个进程不能随意读取另外一个进程的数据,尤为是不能随意在别人的地盘乱写数据,否则会带来不少不少不可预期的问题。
虚拟内存本质上就是一层抽象。操做系统给程序提供了一个简单易用的接口,把虚拟内存映射成底层的物理内存,这中间的复杂度所有交给了操做系统,极大地下降了软件开发的难度,同时下降了软件出错的可能性。
再次深入地体会到了那句名言:
计算机领域的一切难题均可以经过抽象解决。
个人公众号:全栈不存在的