内存是计算机中需要我们认真管理的重要资源。程序大小的增长速度比内存容量的增长要快得多。帕金森定律指出:“不管存储器有多大,程序都可以把它填满”。人们提出一个很重要的概念就是“分层存储体系”,这个体系包括:高速缓存(cache),内存,磁盘,可移动存储装置。操作系统的工作就是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。
总之把物理地址暴漏给进程会带来下面几个严重问题。
1:如果用户程序可以寻址内存每个字节,他们就可以轻松地破坏操作系统。(除非有特殊的硬件保护,例如上面提到的IBM360锁键模式)
2:这种模型,如果想要同时运行多个程序(如果只有一个CPU轮转)是很困难的。
再说地址空间之前,我们聊一聊。要保证计算机同时运行多个程序而不互相影响,必须解决两个问题:保护和重定位。还是用上面的例子IBM360来说:它给内存块标记上一个保护键,并且比较执行进程的键和其访问的每个内存字的保护键。然而这种方法在解决重定位时用的是:用过程序被装载时重定位来解决,但这是一个缓慢而复杂的解决办法。
接下来我们来讲这个更好的办法:地址空间。地址空间是一个进程可用于寻址的一套地址集合,每个程序都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间(除了一些特殊情况下进程需要共享它们的地址空间外),这和进程的概念创在一类抽象CPU以运行程序思想基本一样。我们可以想一想,我们怎么做可以使得一个程序中的地址28所对应的物理地址与另一个程序中的地址28对应的物理地址不同
解决办法是使用一种简单的动态重定位,就是简单地把每个进程的地址空间映射到物理内存的不同部分。从CDC6600到Intel8088,使用的就是给每个CPU配置两个特殊的硬件寄存器,分别是基址寄存器和界限寄存器。所以使用程序装载到内存中连续的空闲位置且装载期间无须重定位。举个例子:当一个程序运行时,程序的起始物理地址装载到基址寄存器,程序的长度装载到界限寄存器。所以在上图中,第一个程序运行时,寄存器值分别为0和16384,第二个程序运行时,为16384和32768。每一次进程访问内存,取一条指令,读或写一个数据字,CPU硬件会把地址发送到内存总线前,自动把基址值加到进程发出的地址值上,同时检查提供的地址是否=或>界限寄存器里的值。如果越界就会产生错误并终止访问。
比如执行 JMP 28指令,但是硬件就会解释成为JMP 16412,所以程序如我们所愿的跳转到了CMP指令。在上图中第二个程序执行过程中,基址寄存器和界限寄存器的设置如下图:
在CDC6600(世界上最早的超级计算机)中就提供了对这些寄存器的保护,但在Intel8088中则没有,甚至连界限寄存器都没有,但却提供了多个基址寄存器,是程序的代码和数据,是程序的代码和数据可以被独立地重定位,但是没有提供引用的地址越界的预防机制。
使用基址寄存器和界限寄存器重定位的缺点是:每次访问内存都需要进行加法和比较运算。比较可以做的很快,但是加法由于进位传递时间问题,在没有使用特殊电路的情况下会显得很慢。
为了减少因内存区域不够而引起的进程交换和移动所产生的开销,一种可用方法是:当换入或移动时为他们分配额外的内存。然而,当进程被换出到磁盘上时,应该只交换进程实际上使用的内存中的内容,讲额外内存交换出去是一种浪费。下图读者可以看到一中已经为两个进程分配了增长空间的内存配置。
a)图是为可能增长的数据段预留空间,b)图为可能增长的数据段和堆栈预留空间。
进程有两个可增长的段,例如,一个是变量动态分配和释放的作为堆使用的一个数据段,另一个是存放普通局部变量与返回地址的一个堆栈段,则如图b)所示,在图中可以看到所示进程的堆栈段在进程所占内存的顶端并向下增长,紧接着在程序段后面的数据段向上增长。在这两者之间的内存可供两个段使用,用完了,则进程必须移动到足够大的空闲区中(它可以被交换出内存直到内存中有足够的空间),或者结束该进程。
空间内存管理
在动态分配内存时,操作系统必须对其进行管理。一般而言,有两种方式跟踪内存使用情况:位图和链表。下面就来介绍
one:使用位图进行管理
首先内存被划分成小到几千字或大到几千字节的分配单元。每个分配单元对应于位图中的一位,0表示空闲,1表示占用(自己定义的)。举个例子如下图所示:
上图是一段有5个进程和3个空闲区的内存,刻度表示内存分配的单元,阴影区域表示空闲(为图中用0表示),分配单元大小是一个重要的设计因素。分配单元越小,位图越大。然而即使只有4个字节大小的分配单元,32位内存也只需要位图中的1位。32n位的内存需要n位的位图,所以位图只占用了1/33的内存。若选择比较大的分配单元,则位图更小。但若进程的大小不是分配单元的整数倍,那么最后一个单元就会有一定数量的内存被浪费了。内存单元大小+分配单元的大小 决定了-> 位图的大小,所以这是一个提供一块固定大小的内存就能对内存使用情况进行记录的方法。位图的缺点是:在一个站K个分配单元的进程调入内存时,存储器管理器必须搜索位图,在位图中找出有K个连续的0的串,这是很耗时的!
two:使用链表进行管理链表管理如下图,就不在过多赘述了。
当进程终止被换出时链表的更新非常直接,如下图四种情况所示:
对于链表管理更多的可以自己上网百度,这里只是简单的介绍下。
基址寄存器和界限寄存器可以用于创建地址空间的抽象,但是,另一个问题来了需要解决,就是管理软件的膨胀。虽然存储器容量增长快速,但是软件增长更快!发展到后面结果是,需要运行的程序往往达到内存无法容纳,而且必然需要系统能够支出多个程序同时运行,虽然或许内存可以满足一个程序的需要,但是总体看来,他们仍然超出了内存大小,所以交换技术不是已经不是一个好的选择了。原因是,一个典型的SATA磁盘的峰值最高达到100MB/S,这意味着至少需要10秒才能换出一个1GB的程序,并需要另一个10秒才能将一个1GB的程序换入。
所以我们用到了一个方法称为虚拟内存(virtual memory)。其基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称为一页或页面。每一页有连续的地址范围。这些页被映射到物理内存,并不是所有的页必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当不在时,有操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
从某个角度讲,虚拟内存是对基址寄存器和界限寄存器的一种综合。例如,8088为正文和数据分离出专门的基址寄存器(上面提到过8088不包括界限寄存器)。而虚拟地址使得整个地址空间可以用相对较小的单元映射到物理内存,而不是为正文段和数据段分别进行重定位。虚拟内存适合在多道程序中使用,许多片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。下面我们来介绍分页吧:
大部分虚拟内存都是用的一种称为分页的技术。程序指令比如:MOV REG, 1000,他把地址1000的内存单元的内容复制到REG中。
由程序产生的地址称为虚拟地址,他们构成了虚拟地址空间。在有虚拟内存的情况下,虚拟地址不是被直接送到内存总线上的,而是被送到内存管理单元(Memory Management Unit, MMU),MMU把虚拟地址映射为物理内存地址,如下图所示:
具体分页过程请看下图。
在这个图中,有一台可以产生16位地址的计算机,地址范围从0K到64K,且这些地址是虚拟地址。然而,可以看到这台计算机只有32KB的物理内存,因此,虽然可以编写64KB的程序,但它们却不能被完全调入内存运行,在磁盘上必须有一个可以大到64KB的程序核心的完整副本,以保证程序片段在需要时能被调入内存。虚拟地址空间按照固定大小划分成称为页面(page)的若干单元,在物理内存中对应的单元称为页框(page
frame)。页面和页框一般大小一样,本例,我们将其设为4KB(所以其中12位来表示偏移量,这里不懂没关系,后面会有解释)。对应于64KB的虚拟地址空间和32KB的物理内存,我们得到16个虚拟页面和8个页框。RAM和磁盘之间的交换总是以整个页面为单元进行的。
我们举个例子吧: MOV REG, 8192
因为虚拟地址8192虚拟页面2中,即8k到12k的页面(页面从0开始计数),它被映射到了物理页框6中(即24576~28671)。所以上述指令就为:MOV REG, 24576
通过恰当的设置MMU,可以把虚拟页面映射到8个页框中的任何一个。但是这并没有解决虚拟地址空间比物理内存大的问题。当我们访问一个未映射的页面, MOV REG, 32780将会发生什么呢?
首先虚拟页面8(他的范围是32768~36863)的第12个字节对应物理地址时什么呢?MMU注意到该页面没有被映射(图中用X表示),于是CPU陷入到操作系统,这个陷阱被称为缺页中断(page fault)。他的处理分为3步:1)如果操作系统决定放弃页框1,那么它将把虚拟页面8装入物理地址8192,并对MMU映射做两处修改。2)首先他要标记虚拟页面1表项为未映射。3)随后把虚拟页面8的表项的叉号改为1。所以在引起陷阱的指令重新启动时,它将把虚拟地址32780映射为物理地址4108(4096+12)。
接下来我们看一下MMU内部结构吧,看看它是如何工作的,了解为什么我们选用的页面大小都是2的整数次幂。看下图:
上图可以看到一个虚拟地址的例子,虚拟地址8196(二进制为001000000000100)用上图所示的MMU进行映射,输入为16位虚拟地址被分为4位的页面号和12位的偏移量。4位页号可以表示16个页面,12位偏移量可以为一页的全部4096个字节编址。可用页号作为页表的索引,从而得出对应于虚拟页面的页框号。如果在/不在位是0,则将引起一个操作系统陷阱。如果是1,则将在页表中查到页框号复制到输出寄存器的高3位,再加上输入虚拟地址中的低12位偏移量。如此就构成了15位的物理地址。输出寄存器的内容随即被作为物理地址送到内存总线。
总结:虚拟地址被分成虚拟页号(高位部分)和偏移量(低位部分)两部分。例如,对于16位地址和4KB的页面大小,高4位可以指定16个虚拟页面中的一页,而低12位接着确定了所选页面中的字节偏移量(0~4095)。使用3或5或其他位数拆分虚拟地址也是可行的。不同划分对应不同的页面大小。
对于页表本身他还有很多位的标志,比如有修改位,访问位,高速缓存禁止位,保护位。
不同计算机的页表大小可能不一样,但32位是一个常用的大小。
页框号:映射的目的就是找到这个值。
在/不在位:1是有效,0则表示该表项对应的虚拟页面不在内存中,访问该页面会引起一个缺页中断。
保护位:支出一个页允许什么类型的访问。比如:0->读/写, 1->只读。
修改位:它是用来记录页面使用情况的。在写入一页时有硬件自动设置修改位。如果一个页面被修改过,那么就是“脏”的,则必须把它写回磁盘。反之就是“干净”的,简单地丢弃就可以了。这一位有时也被称为脏位。
访问位:系统会在该页面被访问时设置访问位。他的作用是帮助操作系统在发生缺页中断时选择要淘汰的页面。不再使用的页面要比正在使用的更适合淘汰。这一位在页面置换算法中很重要。
高速缓存禁止位:对那些映射到设备寄存器而不是常规内存的页面而言,这个特性是非常重要的。例如:操作系统正在紧张地循环等待某个I/O设备对它刚发出的命令做出相应,保证硬件是不断地从设备中读取数据而不是访问一个旧的被高速缓存的副本是非常重要的。通过这一位可以禁止高速缓存。具有独立I/O空间而不使用内存映射I/O的机器不需要这一位。
原文:http://blog.csdn.net/kenan00000/article/details/23784739