在这一章我们一开始要明确我们进行分配的存储器的层次关系,在这个层次关系下的可执行存储器是什么,高速缓存和磁盘缓存的概念以及存在的意义,了解完存储器的分类后需知道用户程序变为可执行程序的过程:编译的介绍,链接的功能和方式,装入的介绍和方式,这些都是程序从外存调入到内存的过程,那么内存是如何分配空间给装入操作的呢?会用到什么装入算法?接着装入内存后,因为转入程序要求是连续的,从而导致内存空间被分割成一个个小的内存块,其他大的程序就无法调入内存运行,为了解决这个问,就采取了“拼接”和“紧凑”的方法。要实现这个技术,就要引入一个新的技术–动态可重定位分区分配。讨论完程序的换入和内存分配,那么在分时系统中,程序的换入和换出(对换)尤为频繁,那么合理有效的对换技术更加重要。连续的分配方式会让内存碎片化,虽然可以通过“拼接”解决,但是地址的转换消耗大量资源,并不实用。我们设想如果程序装入内存不是连续存放的,而是分开离散存放,那么效率就会大大提高。我们会介绍两种新的离散存储管理方式,分别为分页存储管理方式,分段存储管理方式和段页存储管理方式。了解完连续分配方式和离散分配方式之后,还要注意的一个问题就是程序执行时访问内存的次数。
现代计算机采用的都是多层结构的存储器管理系统,多层结构由上到下为寄存器,高速缓存,主存储器,磁盘缓存,固定磁盘,可移动存储介质,总的来说,这些层次结构的存储器大多可分为三类,寄存器,主存和辅存。寄存器和主存为可执行存储器,可执行存储器的访问时间和方式与辅存不同,前者远大于后者,所以进程一般都会从外存(辅存)调入内存执行,这样可以通过高速率的数据访问速度以达到减少执行时间的效果,提高CPU利用率。可执行存储器的主存用于保存正在执行状态下的进程和数据。而寄存器则多运用于数据处理。在寄存器和主存之间存在着一个高速缓存,它主要用户备份主存中经常使用的数据,减少处理机访问主存的次数,以提高程序的运行速度。在主存和固定磁盘之间存在着一个磁盘缓存,用于提高CPU访问辅存的速度,类比于计算机组成原理中的cache。
用户程序变为可执行程序的过程分为三步:编译,链接和装入
用户源程序通过编译程序进行编译,形成若干个模块。
用户源程序是由多个模块组成的,有用户自定义模块,引入内部函数的模块等,经过编译后形成多个模块,要把这些模块进行链接之后,形成一个完整的装入模块,再装入内存变为可执行程序。我们在进行链接操作的时候,必要考虑的一个问题:每个模块在链接之后的相对地址问题,我们知道,每个模块都有起始地址和终止地址,那么当模块链接在一起的时候,从整体的角度来看,每个模块都会有它所处的位置例如,在还未链接时,每个模块的起始地址可能都为0,终止地址取决于模块长度,那么链接操作之后,第一个模块的起始地址为0,终止位置为L第二个模块的起始地址就应该为L+1,于此类推。
链接的方式唯一,但是链接时间可以不同时,根据链接时间的不同大致可分为三类,分别为:静态链接方式,装入时动态链接,运动时动态链接
静态链接的意思是在可执行程序装入之前就把链接操作执行完毕,并且不会再分开,等待调入内存执行。
装入时动态链接的意思是边装入边链接的方式。可能在模块链接的时候有其他的任务要调用其中的一个模块,这种方法就有利于实现对模块的共享,这样的方法也便于修改更正。
运行时动态链接的意思是在内存中运行时需要调用某模块的时候,就会到外村中调用这个模块,执行链接完后在内存中执行。
运行时动态链接和装入时动态链接的区别就在于运行,是否任务运行。而静态链接和装入时动态链接的区别就在于装入内存的是不是一个完整的已经链接结束的用户可执行模块。
装入过程就是把用户可执行程序从外存中调入内存单元中,为以后分配处理机运行做铺垫,那么,我们要考虑的问题就会很多,用户可执行程序装入内存后的位置是固定不变的还是可变的?内存是怎么分配其资源给用户可执行程序的?会使用到哪些分配算法?在程序中的逻辑地址在装入内存后的地址是否应该改变?如果改变的话,怎么改变?
第一个问题:用户可执行程序装入内存后的位置是固定不变的还是可变的?
答案是可变也可不变,装入方式不同,答案也不同。装入方式大致可分为三类:绝对装入方式,可重定位装入方式,动态运行时的装入方式,下面来一一介绍。
这种情况是在计算机发展早期,计算机的系统较小,只能运行单道程序,用户事先知道链接之后的用户可执行程序会装入到内存中的地址,那么用户在编译的时候生成地址就从已经知道的装入内存地址的地方开始拓展,这样就不会存在逻辑地址和物理地址转换的情况。
可重定位装入方式和绝对装入方式的区别就在于用户不知道程序装入内存后的地址(但是程序装入的地址是事先定义好的,不会改变),在编译和链接后起始地址都是从0开始的,装入内存后就会存在逻辑地址和物理地址的转换。
动态运行时的装入方式和绝对装入方式的区别在于用户可以自定义装入内存的地址,不再是事先系统中定义好的了。
为了解决在装入时留下的第二个问题,这里我们分配方式介绍两类,分配算法也介绍两类。
分配方式:
分配方式大致可分为单道处理系统下的分配方式和多道处理系统下的分配方式,单道处理系统下的分配方式为单一连续分配,多道处理系统下的分配方式为固定分区分配和动态分区分配。
因为单道处理系统下,一个程序独占内存,不需要有其他的分配方式。
见名知意,分区是这个装入方式的重点,那么分区的标准自然也是重点,这里我们按照大小来分区,一种是分区大小相等的划分方法,另一种就是分区大小不相等的划分方法,后者相比较于前者更具有灵活性,可以提高内存分配的效率。我们在划分完区域之后,会给每个分区设置一个索引,最后汇总为一个分区说明表,当要从外存调入一个程序的时候,就会按照程序的大小检索分区说明表,为程序分配,分配成功后分区说明表中的对应分区就会有已分配的标志。
动态分区分配就是根据进程的实际内存需要,动态分配内存空间资源。在实现这样的动态分配的时候就要考虑到其中使用的数据结构,分配的算法以及在分配完之后的回收操作。
动态分区分配的数据结构:
这里的数据结构因该要描述的是分区的大小,使用情况,空闲分区和使用分区的情况等。数据结构和空闲区的分区表如下图:
采用一定的内存分配算法为装入程序分配算法,在分区分配算法一节会细说。
按照一定的分配算法把内存分配给调入内存的程序,运行之后还需要释放内存,以供之后的程序使用。按照动态分区分配的数据结构,回收的情况共有四种:
(1)回收区位置的上面有一个空闲区,此时就把回收区和上面的空闲区合并,只需要修改上面空闲区中对应表项内容中的大小。
(2)回收区位置的下方有一个空闲区,把回收区和下方的空闲区和并,形成一个新的空闲区,新空闲区的首址为回收区的手址,大小为两者之和。
(3)回收区处于两个空闲区之间,把回收区和其下方的空闲区于上方的空闲区合并,增加最上方空闲区的大小。
(4)回收区的上下方均不为空闲区,此时,创建一个新的空闲区,首址为原回收区的首址,大小为原回收区的大小。
分区分配算法分为两部分,一个是基于顺序搜索的动态分区分配算法,另一种就是基于索引搜索的动态分区分配算法,下面将会分别介绍。
顺序搜索的就是把内存现阶段中处于空闲状态的区域用链表的形式串联在一起,然后由前往后搜索,当搜索到空闲区的大小符合程序执行所需内存空间的大小时,就会把此空闲区分配给此程序。基于这样的方式,四种对应的算法,分别为:首次适应算法(FF),循环首次适应算法(NF),最佳适应算法(BF),最坏适应算法(WF)。
即从链表的首部开始搜索,知道找到一个大小可以让程序执行的空闲区。这样的方法会让内存碎片化。不利于之后大程序的调入。
循环首次适应算法与首次适应算法的区别在于一开始搜索的起点不同,前者的起点是上一个分配成功的空闲区链表处,而后者每次都是从表首开始,减少了一大部分遍历空闲区链表的使时间。而且可以减少内存的碎片化程度。
把空闲区链表按照大小由小到大排列,第一个查找到的空闲区一定是最佳的。
把空闲区链表按照大小由大到小排列,减少了搜索链表的时间。
从基于顺序搜索的动态算法中的循环适应算法和最坏适应算法的好处我们可以想到一个问题:如果内存中的空闲区划分很多,对应空闲区链表就会很长,同时我们再使用基于顺序搜索的动态分区分配算法时,搜索的时间就会很长,为了减少搜索的时间,就会采用基于索引的动态分区分配算法。有三类,分别为:快速适应算法,伙伴系统和哈希算法。
内存分区之后,会有一部分空闲区的大小相同,这样把大小相同的空闲区分为一类,并且把它们单独放在一张空闲区表上,设置一个索引代表这类的空闲区。这样整个分区就会被分类为多个表,程序在调入内存的时候,通过索引来找到大小合适的类表,在从对应类表中选择一个空闲区分配给进程即可。
这里引用其他博主的链接:https://blog.csdn.net/gao1440156051/article/details/52208312(感谢大神)实际上就是内存的再划分动态自我分配。
哈希算法就是利用哈希快速查找的方式,根据所需空间的大小,通过哈希函数的计算,最后的出来一个最佳分配策略。
前面有的算法解决了一个不可忽视的问题:当基于一定的分配算法把内存分配给程序后,可能会产生内存分区的碎片化,从而导致之后需要大内存空间的程序无法装入。为了让之后的大程序得以装入,我们采取了一个新的技术–“拼接”和“紧凑”。
“拼接”和“紧凑”
要想解决由于内存空间碎片化的问题,我们可以设想,为什么我们不像动态分区分配的内存回收那样,把这些碎片化的内存区正在使用的块和并在一起,换而言之就是将内存中的所有作业进行移动,使它们全部都相邻,这样不仅为之后要装入的程序腾出了空间,还解决了内存区碎片化的问题。“拼接”和“紧凑”最重要的就是地址的变换问题。这种技术也可称为动态可重定位分区分配。
我们在进行“拼接”和“紧凑”操作之后,首要解决的就是要对程序和数据的地址加以修改,对其重定位,这样的地址变换不免会造成系统效率低下,要想解决这个问题,我们使用动态重定位的方法实现,在前面介绍的装入过程中牵扯到的地址变换问题,在这里我们会在使用一个重定位寄存器。这个冲定位寄存器就是把存在其中的程序首地址加上相对地址最后得出”紧凑“之后的地址。
这种动态可重定位分区分配原理在于,如果存在于内存碎片化中的小分区之和刚好可以提供给新的大程序使用,那么就会采用”拼接“和”紧凑“技术来合并为一个大的空闲区,以供之后的大程序使用。
讨论完了源程序经过编译,链接,到装入内存的过程,内存资源分配给程序的方式及算法,那么,在现代计算机系统中,分时系统更加广泛,我们会给每一个作业分配一个时间片,当这个作业的时间片用完的时候,就会把它从内存中调出到外存的就绪队列当中去,这就会涉及到程序的多次换入和换出(统称“对换”),采取何种有效的“对换”技术就显得尤为重要。采取什么样的对换类型?是整体对换?还是页面(分段)对换?
在多道批处理系统中,内存存在因为某种情况导致系统阻塞运行时,就会采取对换技术,把阻塞的进程换出到外存中的就绪队列中,换入一个新的进程运行。
整体的对换技术在前面处理机章节介绍过,对应知识点就是处理机的中级调度。下面将介绍另外两种对换技术,页面对换和分段对换,对应于两种管理系统,分别为分页存储管理系统,分段存储管理系统。
引入两种存储管理系统的原因:
在连续分配方式中,一个进程被单独的存放在一个分区当中,采用了“紧凑”的方式解决了内存的碎片化问题,设想如果进程并不是连续的存放在一个分区当中,而是分散地装入许多不相邻地分区当中(这里把内存分为多个块),这样就需要进行“紧凑”操作了,同时有衍生出一种新地分配方式——离散分配方式。基于离散分配方式的基本单位不同,大致分为三类:分页存储管理系统和分段存储管理系统,以及段页存储管理系统。
为了更好地提高内存的利用率,从而从连续分配方式发展到离散分配方式——分页存储管理。分页存储管理把程序的逻辑地址按照一定的大小分为若干页,为每个页标号,把内存分为若干物理块,为每个物理块分块(页框)。这种离散分配方式同样也要考虑逻辑地址和物理地址之间的转换问题。
页表是页号和块号的映射。逻辑地址的组成为页号和页内地址,要想转换为物理地址,首先把页号拿出来和页表寄存器中的页表初始地址相加,这里页号就相当于相对地址,把两者之和作为页号在页表中检索,找出对应页表项(内容为块号),最后加上页内地址,就为物理地址。(因为页大小和块大小相同,所以页内地址就为块内地址)
那么假设内存地址很大,内存物理块也就很多,对应页表项也就很多,那么检索这些页表项的时候就会浪费很长时间,如果单纯的把物理块变大,又没有考虑到会使页内的碎片化加大,换一种思维方式,我们把得出的页表再次划分,得到一个外部页表,这样的结构就称为两级页表,他的逻辑地址就是由三部分组成的:外层页号,外层页内地址,页内地址。外层页号代表的是外层页表中的页表项地址,内容为页表的首址,外层页内地址为相对于页表首址的相对地址,最后的页内地址是对应物理块中的偏移地址(书P153图4-18)。这种划分两级页表的方式没有改变多页表的存放问题,只是检索的耗时变少,唯一一个可以采用较少的内存空间存放页表的方法是把当前所需要的页表项调入内存(类似于cache理解)。
页大小等于块大小!作业2的每个页就不是连续的分配的,这就是离散分配的特点所在。
分页存储管理系统在最后一页可能不会使用完全,会有一部分空间的浪费,为了解决这个问题,就引入了分段存储管理系统;同时为了满足程序员在编程和使用上的方便。因为一个程序是由多个程序段组成的,主程序段,子程序段…。采用分段存储管理系统的方式不经方便于程序员的编程,一个段就是一个封闭的区域,有利于数据保护。因为段的完整性,所以也利于信息的共享。
逻辑地址是由两个部分组成的,分别为段号和位移量,由有效地址变换为物理地址可参照于分页存储管理中的相关解释。段表是段号(以及段长)和内存块首址的映射。
作业被分为若干个段,每个段分配一个连续的内存区,由于各个分段的长度不同,其对应的内存区也不同,只要求段内连续,段间不要求连续。
(1)页是信息的物理单位,分页是为了满足系统的需要;段是信息的逻辑单位,含有完整的信息,是为了满足用户的需要。
(2)页的大小固定且系统确定,由系统把逻辑地址分为页号和页内地址,由机器硬件实现;段的长度不固定,取决于用户程序,编译程序对源程序编译时根据信息的性质划分。
(3)分页的作业地址空间是一维的,分段的作业地址空间是二维的。
将用户程序分为若干个段,再把每个段分为若干个页,并为每个段赋一个段名。地址结构由三个部分组成,分别为:段号,段内页号,页内地址。
因为把程序划分为若干个段,所以需要有段表,又因为把段划分为若干个页,所以需要一个页表。其余的地址变换参照上面分页存储管理。
因为有段表和页表的存在,所以在获得指令和数据时分页式存储管理和分段式存储管理需要两次访问内存,第一次时是访问内存的页表或者段表,找出对应块号,将块号与页内或者段内地址相加即为物理地址,第二次是按照的出来的物理地址访问内存取出对应指令和数据;段页式存储管理系统则需要三步。为了加快执行速度,就要设置一个高速缓冲器(类似于cache理解)。