在了解完内存的连续分配管理的三种方式后,可以看到它们之所以称之为连续分配是因为都有一个共同特点:每个进程所分配的内存都是一个连续分区(一个分区内部)。那么会导致什么问题呢?
由于每个进程的内存空间都是连续分配,而进程所需要的内存空间并不会完全与分配的内存分区大小一致,也就导致了内存的“碎片化”。对于固定分区分配而言,“碎片”来自于每个进程的分区内部;对于动态分区分配而言,每次都要从用户区寻找一块符合条件的分区,导致整个用户区在进程的各个内存分区之间夹杂很多“外部碎片”。
而“碎片化”直接将会导致内存的利用率降低。
所谓的非连续分配,即一个用户进程所分配的内存分区不是连续的,由多个分区组成。
非连续分配管理的方式有三种:
基本分页存储方式,将内存分为了大小相等的分区(这个分区是明显小于连续分配的固定分配方式的)。每个分区可以称为页/页框,通常1页的大小为4K,页号从0开始,操作系统在分配内存时将会以页为基本单位进行内存的分配。
由于进程所需的内存大小不一定刚好就是4K的倍数,因此还是会产生一定的“碎片”,称之为“页内碎片”。
内存地址在物理上进行了分页,且进程分配的内存是不连续的,为了便于对进程的内存进行管理,我们需要对物理地址进行转换。操作系统通过页表记录了进程的逻辑地址空间与物理地址空间的映射关系。每个进程的PCB中都会存放页表的内容。
按照这个逻辑,假如要对逻辑地址1024空间的值加1,处理流程是怎样的?
首先,在页表中找到逻辑地址1024,查看对应的物理地址后,将实际保存在物理地址空间的值取出来,CPU计算完成后,再通过逻辑地址1024找到物理地址,并将计算完成的值存放。
在逻辑地址与物理地址的转换过程中,操作系统使用的方式称为基本地址变换机构。
针对上面的地址转换过程,我们来详细看看。
首先,物理地址的计算表达式可以用如下方法表示:
物理地址=(页号–>块号)+偏移量
其中:
页号P = 逻辑地址A / 页面长度L
偏移量W = 逻辑地址A % 页面长度L
也就是说对逻辑地址1024空间的值加1过程如下:
因此先找到页号0对应的块号1,然后在块号1对应的物理地址空间页框1中偏移量1024处取值计算即可。
由于计算机通过2进制进行运算,因此在计算页号P和偏移量W的时候,可以将表达式写为:
P = A >> 12; (4K是2的12次方,除法相当于二进制的右移操作)
W = A & 4095;(取模相当于二进制的与操作)
当我们需要将逻辑地址1024的值加一的过程中,首先CPU要先计算出页号(访问一次内存),然后再根据页号去找到内存的物理地址(第二次访问内存),最后计算完成,将值更新(第三次访问内存)。可以看到,在计算之前就访问了两次内存,由于通常页表较大,这样无疑会降低系统的效率。
为了优化上面的问题,提出了快表的概念。
快表结构存放在计算机的高速缓存中,读写效率明显高于内存,但是高速缓存的空间更小。
快表的存储信息与页表一致(页号与块号),不过在数据结构上是不同的。之所以数据结构不同是因为内存中存放完整页表(现在称为慢表),页号是连续的且与块号一一对应,因此页号和块号可以保存至同一结构中从而形成一个一维数组结构。但是快表并不存放完整的页表内容,CPU在读取物理地址空间前会优先访问快表,如果快表中存在计算出的页号则可以直接获取到对应的块号。但是假如快表中不存在当前页号,CPU还是要去内存中访问慢表进行全表的查询。在进行慢表查询后把当前结果保存一份到快表中,这也就导致了快表中存放的页表信息不是连续的,因此页号与块号是一个二维结构。
快表结构的引入之所以提高效率,一是因为高速缓存的读写效率更高;二是我们的程序中通常会存在大量的循环操作(称为局部性原理),所以遍历第二遍开始,由于快表中已经添加了需要访问的页表信息,结合第一点,使得操作系统的运行效率得到了提升。
以上的设计有一个明显的缺点:整个页表大概有4M,每次都要全部放入内存,且由于页表是一维数组,这部分内存必须是连续空间,导致了大量连续空间被占用。
针对上面提出的问题,可以每次不完全生成整个页表,类似于动态装入,每次需要的时候再生成部分页表,称之为:分页离散存储,这就需要建立页目录表管理离散页表。如下图,再页表完全形成的状态下,将之前的页表分成了1024份(二级页表),每份1024哥页号。上图在经过此方法优化后,图下图所示:
之所以二级页表每个表具有1024个页号,是因为这样每个二级页表刚好是4K大小,刚好是一个页框的大小。
在采用了二级页表的设计后,一级页表总共1024个页号(相当于索引),二级页表可以分散在1024个页框中(是动态装入的),性能进一步得到了优化。
此时,逻辑地址(在PCB中)可以如下表示:
分页存储管理的方式对于操作系统而言还是很方便了,但是对于人却不是。一个程序从功能上是可以拆分成多个模块(高级程序语言一般都通过编译器向开发人员屏蔽了这部分过程)的,因此在逻辑上假如可以将不同模块方便的在内存中进行管理,将会有效减轻程序开发的难度。
基本分段存储管理方式将进程从逻辑上进行了分段,将这些分段信息存储在段表中,段表中就包括了内存的逻辑地址,这些逻辑地址在地址变换机构的映射下得到内存的物理地址。示意图如下:
分段存储管理方式可以方便的实现段的共享与保护(颗粒度大,实现简单)。
首先对比下前面两种方式:
在结合分页与分段管理后,提出了段页式管理方式。其总结如下: