Operating Systems: Three Easy Pieces
第十三章:Address Spaces
之前,建立一个电脑操作系统是非常容易的事情,也许你会问为什么?因为用户的期望很低。是那些“该死”的用户们,期望“易用”,“性能高”,“可靠”等,才导致所有的这些另人头疼的事情。下次碰到这些用户时,别忘感谢他们,制造了所有的这些问题。
13.1早期的系统:
从内存的角度看,早期的机器没有将其抽象给用户,基本上,机器的物理内存看起来如图13.1
操作系统是一套程序(事实上,是一个库), 位于内存中(本例中公位于物理地址0), 同时会有一个运行的程序(一个进程) 位于物理内存中,本例中位于物理地址64K处。早期的系统没有一些不切实际的想法,用户对OS没有太多的期望,所以对OS开发者来说,日子比较好过,是不是?
13.2并行处理和分时系统
不久后,由于机器比较昂贵,人们开始更加有效的共享机器。因此并行处理时代来到了,这种情况下,多个进程就绪,可以同时运行,OS负责在他们之间切换,例如一个进程需要执行IO操作。这样做,增加了CPU的使用率。用这种方法增加效率有早些时间是非常重要的,当时的机器价值几十或者上百万美元(你认为你的MAC是昂贵的).
然而很快,对机器的需求越来越多,分时处理的时代到来。特别对程序员来说,他们认识到批处理计算的局限性,他们对长时间的编码-调试周期感到厌烦。交互性就变得非常重要,因为很可能很多用户同时使用一个机器,每个用户都在等待他们正在运行的程序的及时响应。
实现分时的一种方法就是短时间运行一个程序,给他访问所有内存的权限,之后暂停它,把它所有的状态保存到类似磁盘的介质中(包括所有的物理内存数据), 加载其它进程的状态,同样短时间运行,这样就实现了某种程度上的暴力机器共享。
不幸的是,这种实现有一个大问题:太慢,尤其对于内存增长来说更加显著。保存和加载毫寄存器级别的状态(如PC,通用寄存器)是相对来说比较快速的,但是保存所有的内存数据到磁盘中, 不夸张的说,没有效率。因此在切换进程时,宁愿将进程保存在内存中,OS中这种分时的实现是更加有效率一些。
在13.2图中,有三个进程,A,B,和C,每一个分配了512K物理内存,假定单CPU,OS选择执行一个进程,比如A,此时B和C位于就绪队列,等待执行。
随着分时变得越来越流行,或许你能猜到,对操作系统来说,就会有新的需求。特别对于允许多任务位于内存中,就使得“保护”变成一个重要的问题。你当然不希望一个进程可以读或者写其它进程的内存。
13.3 Address Space
然而,我们不得不在脑子里想着那些讨厌的用户,这样就需要OS对物理内存建立一个容易使用的抽象。我们叫这个抽象为address space,它是运行着的程序,在系统中看待内存的视角。理解这个基本的OS抽象,是理解内存抽象的关键。
进程的address space包含了运行程序的所有的内存状态。例如,位于内存中某一处的程序的代码指令,它们就在address space中。程序在运行的时候,用stack来记录他在调用序列中的位置,同时申请的局部变量,传递的参数返回的值都保存在stack中。最后,heap有于动态申请,用户管理的内存,如你可能用c语文中的malloc,或者面向对象语文C++或者JAVA中的new。当然,address space中还包含其它的东西,如静态初始化的变量,目前为止,我们假定只有三部分,code, stack和heap。
13.3中,介绍了一个address space只有16KB的例子。程序的code部分位于address space头部(从0开始,占据1KB的address space)。代码是静态的(因此容易存放到内存中),所以把它放在头部并且代码部分,在程序运行时也不会需要额外的空间。
下面,address space的另外两个区域,它们可能在程序运行时增长或者变小。顶部的heap和底部的stack。把它们这样放置,是因为这两部分空间都希望增长,通过将它们放在address space相反的两端,可以实现这种增长:它们只需要向相反的方向增长即可。heap起始地址位于code区域后,向下增长;stack起始于16KB向上增长。然而,这样放置heap和stack只是一种约定俗成,你可以按照你的方式安排address space地址空间。
当然,描述address space时,我们所述的是OS提供给运行程序的抽象。程序实际上不一定位于物理地址从0到16KB之间;实际上它位于物理地址的任意地址。例子中的进程A,B,和C,在图13.2中你可以看到每一个进程是怎样Load到内存中不同的地址的。因此就产生了一个问题:
OS是怎样在基于一个物理内存的基础上,为多个运行的程序建立这个抽象,私有,可能很大的地址空间?
OS这样做的时候,我们把它叫做虚拟内存,因为运行的程序认为,它们被加载到一个特定的地址,0地址,并且拥有一个很大的地址空间(32bits或者64bits), 事实并非如此。
例如,13.2图中的进程A,试图加载到0(我们叫它为虚拟地址)地址,某种程度上对OS来说,在硬件的帮助下,会确保不会加载到物理地址0处,而是加载到物理地址320KB的地址。这就是虚拟内存的关键点,它存在于所有的现代的操作系统当中。
13.4 目的
到这里,我们需要讲讲OS的虚拟内存了。OS不仅仅对内存虚拟化,它这样做遵循一套标准。确保OS这样做,我们需要列出一些指导目标,之前已经提到过这些目标,这里再看一遍,它们是非常值得再看一遍的。
对于虚拟内存系统的一个主要的目标就是,透明。OS实现虚拟内存时,要对运行的程序透明,这样程序不必关心内存是虚拟化的;并且 ,程序运行起来就像拥有自己的物理内存。在这个假象后面,OS(包含硬件)做了所有的事情。
另一个目标是效率。OS应用确保虚拟化尽可能高效,从时间和空间上来算均需要如此。实现时间高效的虚拟化,OS依赖于硬件,包括硬件的TLB等。
最后,VM的第三个目标是保护性,OS应该保护每一个进程。一个进程执行load,store或者取址时,不应该访问或者影响其它进程的内存。这种保护机制使每个进程运行在自己的独立的空间中,防止对其它进程造成恶意的破坏。