zoukankan      html  css  js  c++  java
  • 内存管理

    操作系统内存管理

    内存是计算机中需要我们认真管理的重要资源。程序大小的增长速度比内存容量的增长要快得多。帕金森定律指出:“不管存储器有多大,程序都可以把它填满”。人们提出一个很重要的概念就是“分层存储体系”,这个体系包括:高速缓存(cache),内存,磁盘,可移动存储装置。操作系统的工作就是将这个存储体系抽象为一个有用的模型并管理这个抽象模型。

    一:无存储器抽象

    最简单的存储器抽象就是根本没有抽象。每一个程序都直接访问物理内存。在这种情况下,要想在内存中同时运行两个程序是不可能的。比如:第一个程序在5位置写入一个值,将会擦掉第二个程序存放在相同位置上的所有内容,所以同时运行这两个程序就会出错或立刻崩溃。
    不过即使存储器模型就是物理内存,还存在一些可行选项的:)


    上图展示的3种变体。
    第一个被用在大型机和小型计算机上,现在很少使用了。
    第二个被用在一些掌上电脑和嵌入式系统中。
    第三个被用于早期个人计算机中,例如运行MS-DOS的计算机,在ROM中的系统部分称为BIOS。第一种和第三种方案的缺点是用户程序出现的错误可能摧毁操作系统,引发灾难性后果(比如篡改磁盘)。

    在不使用内存抽象的情况下运行多道程序

    下面用 IBM360使用的解决方式举例:
    首先内存被划分为2KB的块,每个块被分配一个4位的保护键保护键存储在CPU的特殊寄存器中。比如:一个昂内存为1MB的机器只需要512个块,需要512个这样的4位寄存器,总容量共为256字节。
    PSW(Program Status Word, 程序状态字)中存有一个4位码。一个运行中的进程如果访问保护键与其PSW码不同的内存,360的硬件就会捕获到这一事件。因为只有操作系统可以修改保护 键,这样就可以防止用户进程之间,用户进程和操作系统之间的互相干扰。

    可是,IBM360这种方法有一个重要的缺陷。举一个例子微笑
    假设我们有两个程序,每个大小为16KB,看看下图:


      第一个程序一开始就跳转到地址24,那里是一条MOV指令。 第二个程序一开始跳转到地址28,那里是一条CMP指令。(与讨论无关的没有画 出来),当两个程序连续地装载到内存中从0开始的地址时,内存中的状态就如同最右边所示。(我们假设系统在高地址处,图中没有画出来),现在,让我们来运 行这两个程序:
    程序装载完成后就可以运行了,由于他们的内存键不同,它们不会破 坏对方的内存。当第一个程序开始运行时,它执行了JMP 24,然后结果也是我们想要看到的,但是,当第一个程序运行一段时间后,操作系统可能会决定开始运行第二个程序,即装载在第一个程序上的地址16384处 的程序。这个程序第一条指令为JMP 28,则这条指令会跳到ADD指令那里去而不是事先设定好的跳转到CMP指令。由于对内存的不正确访问,这个程序很可能在1秒之内就崩溃了。
    分析:
    这里的问题是这两个程序都引用的绝对物理地址而这正是我们最需要避免的。下面是IBM 360对上述问题的补救方案就是在第二个程序装载到内存的时候,使用静态重定位的技术修改它。他的工作方式如下:当一个程序被装载到地址16384时,常数16384被加到每一个程序地址上。虽然这个机制在不出错的情况下是可行的,但这不是一种通用的解决办法,同时会减慢装载速度。而且,它要求给所有可执行程序提供额外的信息来区分哪些内存字中存有(可重定位)地址,哪些没有。但是又不如像 MOV REGISTER1, 28里的28就是不可被重定位的。装载器需要一定的方法来辨别地址和常数。
    最 后,其实缺少内存抽象的情况在嵌入式系统和智能卡系统中还是很常见的!比如,收音机,洗衣机和微波炉这样的设备都已经完全被(ROM形式的)软件控制,并 且软件都采用访问绝对内存地址的寻址方式。这些设备之所以能够正常工作,是因为所有运行的程序都是可以事先确定的,用户可不能再洗衣机或者烤面包机上自由 运行自己的软件吧。微笑

    二:有存储器抽象

    总之把物理地址暴漏给进程会带来下面几个严重问题。

    1:如果用户程序可以寻址内存每个字节,他们就可以轻松地破坏操作系统。(除非有特殊的硬件保护,例如上面提到的IBM360锁键模式)

    2:这种模型,如果想要同时运行多个程序(如果只有一个CPU轮转)是很困难的。

    1:地址空间的概念

    再说地址空间之前,我们聊一聊。要保证计算机同时运行多个程序而不互相影响,必须解决两个问题:保护和重定位。还是用上面的例子IBM360来说:它给内存块标记上一个保护键,并且比较执行进程的键和其访问的每个内存字的保护键。然而这种方法在解决重定位时用的是:用过程序被装载时重定位来解决,但这是一个缓慢而复杂的解决办法。

    接下来我们来讲这个更好的办法:地址空间。地址空间是一个进程可用于 寻址的一套地址集合,每个程序都有一个自己的地址空间,并且这个地址空间独立于其他进程的地址空间(除了一些特殊情况下进程需要共享它们的地址空间外), 这和进程的概念创在一类抽象CPU以运行程序思想基本一样。我们可以想一想,我们怎么做可以使得一个程序中的地址28所对应的物理地址与另一个程序中的地 址28对应的物理地址不同疑问

    解决办法是使用一种简单的动态重定位,就是简单地把每个进程的地址空间映射到物理内存的不同部分。从CDC6600到Intel8088,使用的就是给每个CPU配置两个特殊的硬件寄存器,分别是基址寄存器界限寄存器。 所以使用程序装载到内存中连续的空闲位置且装载期间无须重定位。举个例子:当一个程序运行时,程序的起始物理地址装载到基址寄存器,程序的长度装载到界限 寄存器。所以在上图中,第一个程序运行时,寄存器值分别为0和16384,第二个程序运行时,为16384和32768。每一次进程访问内存,取一条指 令,读或写一个数据字,CPU硬件会把地址发送到内存总线前,自动把基址值加到进程发出的地址值上,同时检查提供的地址是否=或>界限寄存器里的 值。如果越界就会产生错误并终止访问。

    比如执行 JMP 28指令,但是硬件就会解释成为JMP 16412,所以程序如我们所愿的跳转到了CMP指令。在上图中第二个程序执行过程中,基址寄存器和界限寄存器的设置如下图:微笑


    在CDC6600(世界上最早的超级计算机)中就提供了对这些寄存器的保护,但在Intel8088中则没有,甚至连界限寄存器都没有,但却提供了多个基址寄存器,是程序的代码和数据,是程序的代码和数据可以被独立地重定位,但是没有提供引用的地址越界的预防机制。

    使用基址寄存器和界限寄存器重定位的缺点是:每次访问内存都需要进行加法和比较运算。比较可以做的很快,但是加法由于进位传递时间问题,在没有使用特殊电路的情况下会显得很慢。

    2:交换技术和虚拟内存

    首先我们来说一说在一个典型的的Windows或Linux系统 中,在计算机完成引导后,会启动40~50个甚至更多的进程。例如,当一个Windows应用程序安装后,通常会发出一系列命令,使得在此后的系统中会启 动一个仅仅用于查看该应用程序更新的进程。这样一个进程会轻易占据5~10MB的内存。还有其他后台进程还会查看所收到的邮件和进来的网络连接,以及其他 很多诸如此类的任务。并且,这一切发生都在第一个用户程序启动之前。当前重要的应用程序能轻易地占据50~200MB甚至更多的空间。所以,把所有进程一直保存在内存中需要巨大的内存。
    有两种处理内存超载的通用方法,分别是交换技术虚拟内存

    1)交换技术

    交换操作系统操作如下图所示:


    开始时内存中只有进程A,之后创建了进程B和C或者从磁盘将它们换入内存。上图d)显示A被交换到磁盘。然后d)被调入,B被调出,最后A再被调入。可以看到A的位置生变化,所以在它换入的时候通过软件或者在程序运行期间通过硬件对其他地址进行重定位,例如,在这里就可以很好地使用基址寄存器和界限寄存器。
    交换在内存中很明显会产生多个空闲区(hole,也称为空洞), 通过吧所有的进程尽可能向下移动,有可能将这些小的空闲区合成一大块。该技术称为内存紧缩。但这个错做我们一般不进行它,因为它太慢了。比如:一台1GB 内存的计算机可以没20ns复制4个字节,它紧缩全部内存大约花费5S!!
    我们先来考虑一个问题,当进程被创建或换入时应该为它分配多大的 内存。若进程创建时其大小固定的并且不再改变,则分配很简单,操作系统准确地按照其需要的大小进行分配,不多也不少。但是如果进程的数据段可以增长,例 如,程序从堆中动态地分配内存没那么当进程空间试图增长时,就会出现问题。1)进程与一个空闲区相邻,那么可以吧这个空闲区分配给该进程让它在这个空闲区 增大。2)进程与另一个进程相邻,那么把增长的程序移到另一个足够大的区域中去,要么把一个或多个进程换出去,以便生成一个足够大的空闲区。若一个进程在 内存中不能增长,而且磁盘上的交换区也满了,那么这个进程只有挂起等待了,知道一些空闲区(或者可以结束该进程)。

    为了减少因内存区域不够而引起的进程交换和移动所产生的开销,一种可用方法是:当换入或移动时为他们分配额外的内存。然而,当进程被换出到磁盘上时,应该只交换进程实际上使用的内存中的内容,讲额外内存交换出去是一种浪费。下图读者可以看到一中已经为两个进程分配了增长空间的内存配置。


    a)图是为可能增长的数据段预留空间,b)图为可能增长的数据段和堆栈预留空间。

    进程有 两个可增长的段,例如,一个是变量动态分配和释放的作为堆使用的一个数据段,另一个是存放普通局部变量与返回地址的一个堆栈段,则如图b)所示,在图中可 以看到所示进程的堆栈段在进程所占内存的顶端并向下增长,紧接着在程序段后面的数据段向上增长。在这两者之间的内存可供两个段使用,用完了,则进程必须移 动到足够大的空闲区中(它可以被交换出内存直到内存中有足够的空间),或者结束该进程。


    空间内存管理

    在动态分配内存时,操作系统必须对其进行管理。一般而言,有两种方式跟踪内存使用情况:位图和链表。下面就来介绍

    one:使用位图进行管理

    首先内存被划分成小到几千字或大到几千字节的分配单元。每个分配单元对应于位图中的一位,0表示空闲,1表示占用(自己定义的)。举个例子如下图所示:


    上图是一段有5个进程和3个空闲区的内存,刻度表示内存分配的单元,阴影区域表示空闲(为图中用0表示),分配单元大小是一个重要的设计因素。分配单元越小,位图越大。然而即使只有4个字节大小的分配单元,32位内存也只需要位图中的1位。32n位的内存需要n位的位图,所以位图只占用了1/33的内存。若选择比较大的分配单元,则位图更小。但若进程的大小不是分配单元的整数倍,那么最后一个单元就会有一定数量的内存被浪费了。内存单元大小+分配单元的大小 决定了-> 位图的大小,所以这是一个提供一块固定大小的内存就能对内存使用情况进行记录的方法。位图的缺点是:在一个站K个分配单元的进程调入内存时,存储器管理器必须搜索位图,在位图中找出有K个连续的0的串,这是很耗时的!

    two:使用链表进行管理

    链表管理如下图,就不在过多赘述了。


    当进程终止被换出时链表的更新非常直接,如下图四种情况所示:

    对于链表管理更多的可以自己上网百度,这里只是简单的介绍下。

    2)虚拟内存

      基址寄存器和界限寄存器可以用于创建地址空间的抽象,但是,另一个问题来了需要解决,就是管理软件的膨胀。虽然存储器容量增长快速,但是软件增长更快!发 展到后面结果是,需要运行的程序往往达到内存无法容纳,而且必然需要系统能够支出多个程序同时运行,虽然或许内存可以满足一个程序的需要,但是总体看来, 他们仍然超出了内存大小,所以交换技术不是已经不是一个好的选择了。原因是,一个典型的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的机器不需要这一位。


    加速分页的过程

    在任何分页式系统中,都需要考虑两个主要问题:
    1)虚拟地址到物理地址的映射必须非常快。
    2)如果虚拟地址空间很大,页表也会很大。
    对于大而快速的页映射的需求成为构建计算机的重要约束。例如:假 设一条指令要把一个寄存器的数据复制到另一个寄存器。在不分页的情况下,这条指令只访问一次内存,即从内存中取指令。分页后,则因为要访问页表而引起多次 的访问内存。执行速度通常被CPU从内存中取指令和数据的速度所限制,所以每次内存访问必须进行两次页表访问会降低一半的性能,这样没人会使用分页机制。
    解决方案是建立一种现象:大多数程序总是对少量的页面进行多次访 问,而不是相反的,因此,只有很少的页表会被反复读取,而其他的页表很少被访问。所以这个方案是为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物 理地址,而不是再访问页表,这种设备称为转换检测缓冲区(Translation Lookaside Buffer, TLB),有时又称为相联存储器。其中每个表项都记录一个页面相关信息,包括虚拟页号,修改位,保护位,物理页框。还有一位用来记录这个表项是否有效。
    现 在来看一下TLB是如何工作的。将一个虚拟地址放入MMU中进行转换时,硬件首先通过将该虚拟页号与TLB中所有表项同时(就是并行)进行匹配,判断虚拟 页号是否在其中。如果发现了一个有效的匹配并且要进行的访问操作并不违反保护位,则将页框号直接从TLB中取出而不必再访问页表。
    当虚拟页号不在TLB中,MMU检测到没有有效匹配项,就会进行正常的页表查询,接着从TLB中淘汰一个表项,然后用新找到的页表代替它。这样,如果以这一页面很快再被访问,第二次访问TLB时自然会命中而不是不命中。当一个表现被清除出TLB时,将修改位复制到内存中的页表项,除了访问位,其他值不变。当页表项中从页表装入TLB中时,所有的值都来自内存。

    针对大内存的页表

    在引入块表后可以加速虚拟地址到物理地址的转换。不过这不是唯一需要解决的问题,另一个问题是怎样处理巨大的虚拟地址空间。
    多级页表
    举一个例子,先看下图吧:


      32位虚拟地址呗划分为10位PT1域,10位PT2域和12位Offset域。因为偏移量为12,所以页面长度是4KB,共有2的20次方个页面。
      在上图,顶级页表具有1024个表项,它对应于10位PT1,当一个虚拟地址被送到MMU时,MMU首先提取出PT1并把该值作为访问顶级页表的索引。因 为整个4GB(原因是32位)虚拟地址空间已经被划分成1024个4MB的块,所以这1024个表项每一个都表示4MB的虚拟地址空间。索引顶级页表得到 的表项中含有二级页表的地址或页框号。顶级页表的表项0指向程序正文的页表,表项1指向数据的页表,表项1023指向堆栈的页表,其他的表项未用。现在把 PT2作为访问选定的二级页表的索引,也变找到对应的页框号。
      举个例子:考虑32位虚拟地址 0x00403004位于数据部分12292字节处。它的虚拟地址对应PT1=1,PT2=3,Offset=4。MMU首先用PT1作为索引访问顶级页 表得到表项1,它对应地址范围是4M~8M,然后用PT2作为我引访问并得到表项3,它对应的地址范围是在它的4M快内的12288~16383(即绝对 地址4206592~210687)。这个表项含有地址所在的页框号。如果不存在内存则缺页中断,否则从二级页表中得到页框号和偏移量结合成物理地址。
      值得注意的是,虽然上图超过100万个页面,但实际值需要4个页表:顶级页表和0~4M正文段,4M~8M的数据段和顶端4M堆栈段的二级页表。其他都被设为0(不在)。

    随着64位计算机的普遍,情况发生了彻底的变化。如果地址空间是2的64次方,页面还是4KB,则需要2的25次方(42-12=52)个表项 的页表。如果一个页表有8个字节,则整个页就会超过3000万GB。因此64位分页虚拟地址空间的系统需要一个不同的解决方案。如下

      倒排页表

    顾名思义,这种方案是在实际内存中每一个页框有一个表项,而不是 每一个虚拟地址页面有一个表项。例如64位虚拟地址,4KB的页,1GB的RAM,一个倒排页表仅需要262144个表项。虽然节省了大量空间,但是它从 虚拟地址到物理地址的转换会变得很困难。走出这样局面的办法是使用TLB。如果TLB能够记录所有频繁使用的页面,地址转换就可能变得像通常的页表一样 快。倒排页表在64位机器中很常见,因为在64位机器中即使使用了大页面,页表项的数量是很庞大的。例如对于4MB页面和64位虚拟地址,需要2的42次 方个页表项。

    转载至http://blog.csdn.net/kenan00000/article/details/23784739
  • 相关阅读:
    互评
    201671010438 王奕晗英文文本词频统计
    201671010438王奕晗 实验二词频统计
    201671010438王奕晗 实验三 作业互评与改进
    通读《构建之法》所提出的问题
    个人学习总结博客(201671010440 王雨竹)
    互评
    201671010440王雨竹+《英文文本统计分析》
    201671010440 王雨竹 词频统计软件项目报告
    201671010440 王雨竹 实验三 作业互评与改进
  • 原文地址:https://www.cnblogs.com/qianlixiangrunzhao/p/3680091.html
Copyright © 2011-2022 走看看