对于通用计算机而言,存储层次至少分为三级:最高层为CPU寄存器,中间为主存,最低层是辅存,速度逐级变慢,容量逐级增大。详情可见:计算机组成原理:第三章 存储系统
内存分配的过程中会产生一些内存碎片,即空闲内存不能被利用,先了解一下内存碎片的概念:
地址空间以及地址的生成:
逻辑地址与物理地址的转换:
第二步映射的建立和查找由OS完成。
把内存分为系统区和用户区两部分,系统区仅提供给OS使用,通常 放在内存低址部分,用户区是指除系统区以外的全部内存空间,提供 给用户使用。但只能用于单用户、单任务的操作系统中。
将内存用户空间划分为若干个固定大小的区域,在每个分区中只装入一道作业,便可以有多道作业并发执行。当有一空闲分区时,便可以再从外存的后备作业队列中,选择一个适当大小的作业装入该分区,当该作业结束时,可再从后备作业队列中找出另一作业调入该分区。
为便于内存分配,通常将分区按大小进行排队,并为之建立一张分区使用表,其中包括每个分区的起始地址、大小及状态(是否已分配)。
当有一用户程序要装入时,由内存分配程序检索该表,从中找出一个能满足要求的、尚未分配的分区,将之分配给该程序,然后将该表项中的状态置为“已分配”;若未找到大小足够的分区,则拒绝为该用户程序分配内存。
又称为可变分区分配,根据进程的实际需要,动态的位置分配内存空间。当程序被加载执行时,分配一个进程指定大小可变的分区(块、内存块)分区的地址是连续的。
描述空闲分区和已分配分区的情况,常用空闲分区表和空闲分区链两种形式。
思路: 分配n个字节,使用第一个可用的空间比n大的空闲块。
实现: 将空闲分区列表按照地址顺序排序,分配过程时,搜索一个合适的分区,放入第一个合适的分区,释放分区时,检查是否可与临近的空闲分区合并。
特点:
思路: 分配n字节分区时, 查找并使用不小于n的最小空闲分区。
实现: 空闲分区列表按照大小排序,分配时,查找一个合适的分区,释放时,查找并且合并临近的空闲分区(如果找到)。
特点:
思路: 分配n字节,使用尺寸不小于n的最大空闲分区 实现: 空闲分区列表按由大到小排序,分配时,选最大的分区,释放时,检查是否可与临近的空闲分区合并,进行可能的合并,并调整空闲分区列表顺序
特点:
紧凑(compaction):通过移动分配给进程的内存分区,以合并外部碎片,要求所有的应用程序可动态重定位。
分区对换(Swapping in/out): 通过抢占并回收处于等待状态进程的分区,以增大可用内存空间(挂起)。
思路: 整个可分配的分区大小 2^U 需要的分区大小为 2^{U-1} < s ≤ 2^U
数据结构: 空闲块按大小和起始地址组织成二维数组 初始状态:只有一个大小为2U的空闲块
分配过程: 由小到大在空闲块数组中找最小的可用空闲块,如空闲块过大,对可用空闲块进行二等分,直到得到合适的可用空闲块
释放过程: 把释放的块放入空闲块数组,合并满足合并条件的空闲块。
合并条件: 大小相同 2^i ,地址相邻, 弟弟只空闲块起始地址为 2^{i+1}的位数。
分配给程序的物理内存必须连续,存在外碎片和内碎片,内存分配的动态修改困难,内存利用率较低。为了提高内存利用效率和管理灵活性,采用非连续的方式分配,所谓非连续指的是一个程序使用非连续的物理地址空间,允许共享代码与数据,支持动态加载和动态链接。
如图所示,0-11位为页内地址,说明每页大小为4KB,12-31位为页号,说明最多允许有1M页。
– 物理地址的表示:帧号+帧内偏移
页帧和页面的偏移地址一定相同,但是页号和帧号不一定相同,因为在内存中的存储是离散化的。
为了能在内存中找到每个页面对应的物理块,系统为每个进程建立了一张页面映像表,简称页表。进程地址空间中的所有页在页表中依次占有一个页表项,查找表可以找到对应的物理块号(即对应的帧),实现页号到物理块号的地址映射。
如图所示,页表寄存器存放页表始地址和页表长度。指令给出逻辑地址后,先判断页号是否大于等于页表长度,如果是,则说明越界了,直接中断。否则,与页表始地址相加得到页号,然后在页表中找到对应的物理块号,根据页内地址直接找到需要的数据。
页表存放在内存中,使得CPU每次读取数据都要进行两次访问,为了提高速度,利用局部性原理,在寄存器中设置一张块表(TLB),先在快表中找,若未命中则去页表中查找,原理类似Cache。
现代计算机的逻辑地址空间非常大,这样的环境下使得页表必须非常大,每个进程的页表就要占用大量的空间,且还是连续的空间,不太现实。
针对难以找到大的连续的内存空间存放页表的问题,可以将页表进行分页,形成二级页表,使得每个页面的大小与内存物理块大小相同,将其编号,然后离散地将各个页面存放在不同的物理块中,同时也要为离散后的页表再建立一张页表称为外层页表,记录页表页面的物理块号。
同样外表也需要一个外层页表寄存器用于存放外表的始址,逻辑地址结构为:外层页号+外层页内地址+页内地址
现代计算机系统中,经常出现进程的逻辑地址空间非常大,但是内存空间很小的情况,这时候如果为内存的逻辑地址配置一张页表,那么逻辑地址空间会被分成很多页,导致页表十分巨大,占用大量内存空间。但是我们发现,内存空间很小,所以我们可以将内存空间进行分页,为每一个物理块设置一个页表项,按照物理块的编号排序,每个页表项的内容则是页号和所隶属的进程的标识符,建立物理块到逻辑地址的映射,称为反置页表。
根据进程标识符和页号进行检索,如果检索到与之匹配的页表项,则页表项中的序号i就是该页所在的物理块号,否则该页缺失。
如果内存容量也很大,会导致检索很费时,采用Hash算法进行检索,但是需要解决地址冲突的问题,后面会有介绍。
进程的段地址空间被分成若干段,每个段定于了一组逻辑信息,如:主代码段、子模块代码段、公用库代码段、堆栈段(stack)、堆数据(heap)、初始化数据段、符号表等。段表示访问方式和存储数据等属性相同的一段地址空间。对应一个连续的内存“块”,若干个段组成进程逻辑地址空间。
将每个段进行编号,称为段号,每个段从0开始编址,采用一段连续的地址空间,每段长度可以不相等,由逻辑信息组的长度决定。其逻辑地址由段号+段内地址组成。
进程中的各个段可以离散地放入内存,为了找到逻辑地址对应的内存中的物理地址,需要一张映射表,称为段表,每个段占一个表项,记录在内存中的起始位置(基址)和段的长度。段表放在寄存器中可以提高地址转换速度,但一般放于内存中。
系统中的段表寄存器用于存放段表始地址和段表长度TL,进行地址变换时,系统将逻辑地址中的段号与段表长度TL进行比较,判断是否越界。若未越界,再检查段内地址d是否超出该段的段长SL,判断是否越界。
如下图控制寄存器中存放段表的信息,访问指令给出逻辑地址,先将S与段表长度判断该段是否越界,然后将其与段表始址相加得到段号,位移量100小于段长500,未越界,和基址相加得到在内存中的物理地址8292(1024 * 8 + 100)。