操作系统必须对内存空间进行合理的划分和有效的动态分配,而这就是内存管理的概念。有效的内存管理在多道程序设计中可以方便用户使用存储器、提高内存利用率。
内存管理的主要功能有:
将用户源程序变为可在内存中执行的程序需要三个步骤:
程序的链接有以下三种方式:
(1)静态链接:程序运行之前,将各个目标模块连接起来,以后不再拆开
(2)装入时动态链接:在装入内存的时候采用边装入边连接的方式
(3)运行时动态链接:在程序执行中需要该目标模块才进行链接。可以加快程序装入过程和节省大量内存空间。
内存的装入模块在装入内存的时候,有以下三种方式:
绝对装入
这种方式在编译时就知晓程序在内存中的位置,然后编译器会将程序内所有的相对地址单元替换成绝对地址。由于多道程序执行的异步性,这种方式只适合单道程序环境
可重定位装入
编译时对相对地址不做处理,在装入时根据现在内存的使用情况,将程序装入到适当的内存位置中,然后获取到当前内存地址的起始地址。因此程序需要一次性全部装入内存中,如果内存空间不够就无法装入,在装入后地址也不会发生移动
动态运行时装入
程序在内存中如果发生移动,则需要动态装入方式。装入程序在把装入模块装入内存后,不立刻将装入模块中的相对地址转化为绝对地址,而是在运行时才进行转换。在进程取得处理机开始运行时,一个地址寄存器从PCB中获得程序头的位置,在运行时的时候将地址寄存器和程序中的相对位置相加就可以得出物理地址。现在绝大部分操作系统都是使用动态运行时装入
优点是可以将程序分配到不连续的存储器,在程序运行之前只需要装入部分代码就可运行,在程序运行期间根据需要动态申请内存分配。
当一个程序掉入内存运行时,就构成了进程的内存映像。一个进程的内存映像一般有以下几个因素:
代码段和数据段在程序调入时就制定了大小,但是堆和栈大小是可变的。
内存保护是确保每一个进程都有一个单独的内存空间,不被其他进程访问。内存保护主要采用两种方法:
并不是所有进程内存空间都适合共享的那个,只有那些只读的区域才可以共享。可重入代码又称为纯代码,是一种允许多个进程同时访问但是不允许被任何进程修改的代码,但是进程可以通过读将纯代码复制回到自己的内存空间中进行修改。纯代码对节省内存空间有显著的效果。
后续提到;
覆盖
如果在程序运行期间要将所有的程序数据装入内存,那么60G大的GTA5就需要60G的运行内存了。但是实际上在程序运行时,有些数据是暂时不需要的,他们可以暂时不装入内存这么快。比如下图:B模块和C模块是不会同时执行的,因此只需要在内存中空出一个10K的覆盖区,在需要B的时候将B存入,要C的时候将C存入,而不是将B和C都存入内存。
覆盖技术提升了内存的使用效率,将暂时不需要使用的信息留在了外存。但是哪些是需要使用的哪些是不需要使用的,需要在编写程序的时候需要指明,这增加了程序猿的负担和设计程序的难度,这种技术现在基本已淘汰
交换
交换的思想是,把处于等待状态,暂时不需要使用的程序移动到外存(换出);将准备好的,等待执行的进程移动到内存(换入)。通过动态调度平衡内存和外存的空间。采用了交换技术之后,挂起态可以细分为就绪挂起和阻塞挂起两种情况。
在外存中,将磁盘功能分为文件区和对换区。文件区主要用于存储文件,追求磁盘空间利用率,因此采用离散分配的方式;对换区只占小部分,用于存放被换出的进程数据,主要追求换入换出速度,因此采用连续分配的方式。
可以优先换出阻塞进程、优先级低的进程、内存驻留时间长的。进程的PCB会常驻于内存,因为需要通过PCB找到进程的数据被调出到了外存中的哪个地方,并且PCB是进程存在的证明,PCB调出外存会让计算机以为进程不存在了。
首先解释两个概念:外部碎片指的是没有被分配给程序的,而且空间不足以满足程序需求的小块内存碎片;内部碎片指的是已经分配个程序了的,但是程序用不上的内存区块。
连续分配管理方式指的是为用户程序分配到一个连续的内存空间。连续分配管理方式主要包括以下几种:
1.单一内存分配
在此方式下内存分为系统区和用户区,在用户区中只有一道程序独占整个用户区内存。这种方式优点是:简单、无外部碎片,缺点是只能用户单用户,单任务的操作系统中,有内部碎片,存储器利用率极低。
2.固定分区分配
固定分区分配会将用户内存空间划分为若干固定大小的区域,每个分区装入一道作业。当有空闲分区的时候,便可以从外存的后备作业队伍中选择适当大小的作业装入,而划分分区又有两种做法:
为了便于内存分配,通常将分区按照大小排队,并且建立分区说明表,用于说明分区大小吗,起始地址以及是否已被分配。这种方式的两个问题是,如果程序大于任意一个分区,则会装不下,需要采用前面所述的覆盖技术实现装入;如果程序小于固定分区,也会独占一个分区,从而导致分区利用不充分,这就是内部碎片
3.动态内存分配
在进程装入内存的时候根据进程的实际需要,动态的为之分配内存,并且分区大小正好适合进程需要,因此内存中的大小和数目是可变的,而且不存在内部碎片。但是随着时间的推移,内存中会产生越来越多的小内存块,内存利用率也随之下降,这种小内存块称之为外部碎片。克服外部碎片需要使用紧凑技术,就是操作系统时不时对进程进行移动和整理,比较的费时间。
为了上述分页思想产生碎片的问题,提出了分页的思想:把主存空间划分为大小相等而且固定、容量较小的块,作为主存的基本存储单位,每个进程的信息也以块大小划分,在存入内存的时候进程以块为单位存入,一个进程装入一个或者多个块。在形式上看分也类似于固定分区,但是每个分区都很小,因此即便进程没有用满分区,留下来的内部碎片也很少。
1.分页基本概念
(1)页面和页面大小
进程中的块又称之为页面或者页,内存中的块称之为页框或者页帧。内存中的页框有页号,从低地址到高地址递增。页面的大小为整数幂,方便进行划分。为了管理分页,内存中会有页表以标记每个进程的页面对应的是内存中的哪个页框,同时页面大小设置应该适中,太小会使得页表过长,进程页数太多,换出换入次数增多;太大会导致碎片增多。在进程装入内存的时候,会向页表中写入,进程的每一个页面对应的是内存的哪个一页框,为后面寻址提供根据。
页表结构如下:
页号 | 页物理地址 |
---|
(2)地址机构
分页存储管理结构的逻辑存储结构如下:
31…12 | 11…0 |
---|---|
页号P | 页内偏移量W |
假设内存大小为2n比特,并且划分页面大小为2k比特,则需要划分2n-k个页,其内存地址表示需要n位,其中低k位表示的是页内地址,高n-k位表示的是在第几个页。因此,将进程中的逻辑地址转化为页内地址步骤如下:
假设页大小为32B,页数为16页,需要访问的地址为0010 01010
页号 = 逻辑地址/页面大小;页内偏移量=逻辑地址mod页面大小;
(3)页表
系统会为每一个进程建立一个页表,它记录页面在内存中对应的物理块号,一般存放在内存中。
2.基本地址变换机构
在系统中会设置一个页表寄存器(PTR),用于存放页表在内存的起始地址F和页面长度M。进程未执行的时候,页表的起始地址和页表长度存放在本进程PCB中,当进程被调度的时候,才会将页表起始地址和页表长度装入页表寄存器中。主要地址变换过程如下:
为了方便找到页表项,页表一般放在连续的内存块中。
3.具有快表的地址变换机构
存取数据第指令至少要访问两次内存:第一次是访问页表,确定所存取的指令或数据的物理地址;第二次是根据该地址存取数据或者指令。
根据程序执行的空间局部性原理,上一条指令所在的页框很有可能也是下一条指令执行所在的页框,因此为了提高逻辑地址到物理地址转换的速度,地址比那换机构中增设了一个具有并行查找能力的高速缓冲存储器——快表,又称相连存储器(TLB),与之相对的在内存中的页表则是慢表。
在快表出现之后,地址变换如下:
该做法类似于高速缓存,一般命中率在90%以上。
4.两级页表
单级页表在使用上也会存在一定的问题:首先是所有页表箱需要在内存中连续存放,页表要是很大时需要很大的连续空间;其次,一段时间内并非所有页面都用得到,让整个页表常驻内存有一定浪费,并且根据程序的局部性原理,大多数所需要的页面都是扎堆的。
因此提出了两级页表的概念,逻辑地址被划分为了如下模型:
一级页号 | 二级页号 | 页内偏移量 |
---|
两级页表中, 只要求一级页表常驻于内存,而需要哪个二级页表再调用对应的二级页表进入内存进行查询即可,因此可以有效减少页表在内存中占用的空间
页式存储时从计算机的角度考虑设计的,目的是提高内存利用率。分页通过硬件实现,对用户完全透明。
段式管理方式按照用户进程中的段划分逻辑空间,比如用户进程由主程序段、两个子程序段、栈段和数据段组成,则可以将用户进程划分为五段,并且为每一个段分配一个连续的内存空间。因此,段也是大小不等长的。
由于计算机是不知道程序内部是如何分部分的,因此分段是要程序猿在编写程序的时候进行标记的,分段是用户可知;但是分页是用户透明的,无论是何种情况进程都会被放入到等大小的页框中,因此用户并不清楚是怎么分页的。
分段比分页更好实现信息保护。可以将两个进程的某一段指向内存的同一个物理副本来实现信息共享(将需要共享的信息划分为一个单独的段,不与其他信息混在一起),而一般的共享的信息是用的是不可修改的,比如纯代码和可重用代码。这也使得类似吸烟者问题这种需要共享缓冲区的问题在分段存储下更容易被解决,如果是页式存储,分页大小是固定的,可能会将共享内存缓冲区和其他数据切分到同一个页框中。
段式存储管理不能通过给出一个整数便确定对应的物理地址,因为段是不等长大小的,无法通过简单的取余来的出段号。
1.传统存储方式的特征
一次性:作业必须要一次精全部装入内存后才能开始运行,可能会导致两种情况:1.当作业很大而且不能全部被装入内存的时候会无法运行 2.当大量作业要求运行时,由于内存不足容纳所有作业导致多道程序执行效率下降。
驻留性:作业被装入内存之后,就一直驻留在内存中,运行中的进程会因为等待IO而被阻塞,可能处于长期等待的状态。
2.局部性原理
时间局部性:一条指令被执行后,不久后该条指令可能被再度执行,因为程序中有大量循环语句。
空间局部性:程序访问了某个单元后一段时间内,可能会访问临近单元的数据,因为数据往往使用向量、数组等形式聚合存储。
3.虚拟存储器的定义和特征
基于局部性原理,在程序装入的时候,只需要将程序当前要运行的少数页面转入内存,其余部分留在外存。程序执行中,如果访问的信息不在内存,则操作系统将所需的从外存调入内存;同时也会将内存中暂时不需要的调换出外存。这样,系统好像给用户提供了一个比实际内存容量大的多的存储器。
虚拟存储器有以下三种特征:
1.多次性:无需一次性装入
2.对换性:无需在作业运行时一直常驻内存
3.虚拟性:逻辑上扩充内存容量
4.虚拟内存技术的实现
虚拟内存如果使用连续分配的话,会造成内存资源的严重浪费。因此虚拟内存的实现需要建立在离散分配内存的管理方式的基础上:
虚拟内存需要的硬件支持如下:
请求分页系统建立在基本分页系统基础之上,为了支持虚拟存储器而新增了请求调页和页面置换功能。
页表机制
在虚拟存储器中,因为只是把进程的部分数据调入内存运行,所以会出现要访问的页面不在内存的情况,这种情况称之为缺页。为了解决这种问题,请求分页系统中的页表项被设置成了以下的结构:
页号 | 物理块号 | 状态位P | 访问字段A | 修改位M | 外存地址 |
---|
新增了四个字段:
缺页中断机构
每当出现缺页情况的时候,就会产生一个缺页中断,请求操作系统将缺的页调入内存。缺页中断作为中断,基本流程和一般中断一致
地址变换机构
相对于基本分页主要是多了缺页中断,缺页判断以及访问为和修改位的更改
驻留集指的是决定给进程分配几个页框,需要考虑以下几点
固定分配局部置换
为每个进程分配一定数量的物理块,运行期间都不改变,局部置换则是,如果进程出现缺页,则只能从分配给进程的内存页面中选择某一页换出,然后调入一页。这种策略难以确定应该为每个进程分配多少物理块。
可变分配全局置换
为每个进程分配一定数量物理块,运行期间可以视情况适当增减。全局置换指的是,如果发生缺页,系统会从空闲物理块队列中取出一块分配给该进程,并且将所缺页调入。虽然比第一种灵活,但是盲目增加物理块会降低系统并发能力
可变分配局部置换
为每个进程分配一定数目的物理块,缺页的时候只允许从该进程在内存中的页面中选择一页换出。如果频繁发生缺页中断,则会再给该进程分配若干个物理块,直到缺页率趋于正常。
采用固定分配策略的时候,可以使用一下算法分配物理块:
为了确定系统将进程所缺页面调入内存的时机,可分为两种调页策略:
进程运行时如果其分页面不在内存中需要将其调入,如果内存中无空闲空间,则需要选择调出页面。这种就是页面置换算法。常见的页面置换算法有以下四种:
最佳(OPT)置换算法
选择被淘汰的页面是以后永远不会使用的页面,或者是最长时间内不再被访问的页面,以保证获得最低的缺页率。但是由于多道程序系统中并发执行的异步性,操作系统无法预测到未来什么页面需要被访问,因此这只是一个理想算法,实际上无法实现。
先进先出(FIFO)页面置换算法
实现起来非常简单,最早调入的页面最早被换出,但是该算法和进程实际运行规律并不是赢,因为进程中,有的页访问非常频繁,但是也可能被调出内存。而且先进先出可能会出现Belady异常,指的是更大的物理块数反而会导致更频繁的缺页。因此先进先出算法效率很差
最久未使用(LRU)置换算法
选择最久没有被使用的页面予以换出。该算法会为每个页面设置一个访问字段,用于记录上次被访问到现在过了多久了。LRU算法的性能较好,但是需要寄存器和栈等硬件的支持,这是一种堆栈类算法,而堆栈类算法不会出现Belady异常。虽然性能上接近OPT,但是实现的硬件成本比较大。
时钟(CLOCK)置换算法
(1)简单的CLOCK置换算法:
为每一帧设置一个访问位,当某页首次被装入或者访问的时候,就将访问位置为1。而在替换的时候,可以将内存中的所有块看作是一个循环队列,并有一个替换指针,当某一页被替换的时候,则将该页换出,将新页换入,该指针就向下移动一次。而替换指针只会选择访问位为0的页换出,如果页访问位为1,则将访问位置为0并且继续向下搜索。
该算法循环检查各个页面的使用情况,当替换指针循环检查的时候,会选择未访问的页换出,而如果是已访问的页则会被设置为未访问状态,等到下次再次循环到该页,如果该页在过去的一个循环中没有被访问也会被换出。
(2)改进型CLOCK置换算法
将一个页面换出的时候,如果页面已经被修改过,则需要写回外存,但是如果页面没有被修改过(也就是页面和外存块中的信息一样),再进行写回是一种浪费的操作。因此在改进型始终置换算法中,还增加了修改位,用以标记一个页面是否经过修改。
在该算法中,替换的优先级如下:
1.先替换既没有被访问又没有被修改的页
2.然后替换没有被访问但是被修改的页
3.然后访问被访问但是没有被修改的
4.最后访问又被修改又被访问的页
改进型CLOCK算法的置换算法可以减少磁盘的IO操作次数。但是为了找到可置换的页面,可能要经过几轮扫描,实现算法本身的开销有所增加。
1.驻留集
操作系统需要必须决定读取多少页,就是决定给特定的进程分配几个页框,给一个进程分配的物理页框集合称为这个页框的驻留集,需要注意的有以下几点:
2.内存分配策略
有两种内存分配策略:固定和可变分配策略。在进行置换的时候,可以采取两种策略,全局置换和局部置换。
3.物理块调入方法
4.调入页面的时机
(1)预调页策略:一次调入若干临近的页,但是如果调入过多的页会使得大多数未被访问,而且效率低下。因此会采用预测算法进行预调页。预调页的命中率在50%
(2)请求调页策略:在运行中会使用请求将五在内存中的页调入,因此调入的页一定会被访问,目前虚拟存储器一般都使用该种方法。但是每次只调入一页,IO开销比较高
5.从何处调入页面
6.如何调入页面
1.抖动
在页面置换的时候,最坏情况是刚刚换出的页面又需要调入,岗换入的页面又要换出外存,这种频繁的调度行为被称之为抖动
系统发生抖动的原因是系统中同时运行的进程太多,因此分配给每个物理块的地址太少,不能满足进程的正常运行需求,因此会频繁的要求调页。这回事的系统中排队等待页面调度的进程树木增加,从而导致处理机的利用率急剧下降的情况。抖动时进程工作时出现的严重问题,为了解决它引入了工作集概念
2.工作集
工作集指的是某段时间内进程要访问页面的集合,根据局部性原理,可以使用最近访问的页面作为工作集,工作集模型解决抖动的原理是,让操作系统跟踪每个进程的工作集大小,并且给他们分配大于工作集的驻留集大小,从而避免抖动。