操作系统—存储器管理

存储器的层次结构

主存储器

主存储器是计算机操作系统中的一个主要部件,用于保存进程运行时的数据和程序,CPU的控制部件只能从主存储器中取得指令和数据,数据能够从主存储器中读取并将他们装入到寄存器中,或者从寄存器存入到主存储器,CPU与外围设备交换的信息一般也依托于主存储器地址空间。但是,主存储器的访问速度远远低于C PU执行指令的速度,于是引入了寄存器和高速缓存。

寄存器

寄存器的访问速度最快,能和CPU协调工作,价格昂贵,容量不大,寄存器用于加速存储器的访问速度,比如:用寄存器存放操作数,或用作地址寄存器加快地址转换速度等等。

高速缓存

高速缓存容量远远大于寄存器,但是小于内存,访问速度高于主内存器,根据程序局部性原理,将主存中一些经常访问的信息存放在高速缓存中,减少访问主存储器的次数,可以大幅度的提高程序的执行速。通常,进程的程序和数据存放在主存,每当使用时,被临时复制到高速缓存中,如果已存在,就直接取出使用,否则从主存中读取信息,有的计算机系统设置了两级或者多级高速缓存,一级缓存速度最高,容量小,二级缓存容量较大,速度稍慢。

磁盘缓存

磁盘的IO速度远远低于对主存的访问速度,因此将频繁调用的一部分磁盘数据和信息暂时存放在磁盘缓存中,可以减少访问磁盘的次数,磁盘缓存本身并不是一种实际存在的存储介质,它依托于固定磁盘,提供对主存储器空间的扩充,即“利用主存中的存储空间,来暂存从磁盘中读写或者写入的信息”,主存可以看作是辅存的高速缓存,因为辅存中的数据必须复制到主存方能使用,反之数据也必须先存在主存中,才能输出到辅存。

程序的装入和链接

为了使程序能够正常云习惯,必须先为之创建进程,而创建进程的第一件事,就是将程序和数据装入内存,如何将一个用户源文件变为一个可在内存中执行的程序,通常要经过如下几步,

首先是“编译”:由编译程序将用户源代码编译成若干个目标模块

其次是“链接”:由链接程序将编译后的目标模块,以及模块所需要的库函数链接在一起,形成一个完整的装入模块

最后是“装入”:由装入程序将装入模块装入内存

过程图:

image.png

程序的装入

有源文件变为可执行文件的最后一部,装入,有三种方式,绝对装入,可重定位装入,动态运行时装入

绝对装入方式

如果在编译时知道程序驻留在内存的什么位置,那么编译程序将产生绝对地址的目标代码。绝对装入方式按照装入模块中的地址,将程序和数据装入内存中,装入模块被装入内存之后,由于程序中的逻辑地址与实际内存地址完全相同,故不需要对程序和数据的地址进行修改

可重定位的装入方式

绝对装入方式只能将装入模块装入到内存中事先指定的位置,在多道程序环境下,编译程序不可能事先知道装入模块在内存中的位置,因此绝对装入方式只适用于单道程序环境,在多道程序环境下,目标模块所得到的起始地址通常是0开始,程序中的其他地址也是相对于起始地址计算的,此时应采用可重定位装入方式,根据内存的当前情况,把装入模块装入到内存的适当的位置中,该装入方式回事转入模块中所有的逻辑地址与实际装入内存的物理地址不同,需要对数据地址和指令地址进行修改,通常再把装入时对目标程序中的指令和数据的修改过程称为重定位,又因为 地址变换通常是在装入时一次完成的,以后不再变化,故称为静态重定位。

动态运行时装入方式

可重定位装入方式允许装入模块装入到内存中的任何允许的位置,故可用于多道程序环境。但是这种装入方式不允许程序在运行的时候在内存中移动位置。

程序在内存中的移动意味着它的物理位置发生了变化,这就必须对程序和数据的地址进行修改,修养之后才能正常的运行。

然而在运行过程中,它在内存中的位置可能经常需要改变,此时就要使用动态运行时装入的方式。

动态运行时装入程序在把程序装入到内存之后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行的时候。因此,装入内存后的所有地址仍是相对地址,为了让地址转换不影响指令的执行速度,需要重定位寄存器的支持。

程序的链接

源码经过编译之后,可以得到一组目标模块,再利用链接程序把这组目标模块链接,形成装入模块,根据链接时间的不同,可把链接分为

静态链接:在程序运行之前,先将目标模块以及所需要的库函数,链接形成那个一个完整的装入模块,以后不再拆开

装入时动态链接:将目标模块以及所需要的库函数,链接形成那个一个完整的装入模块,在装入内存的时候,采用边装入边链接的链接方式

运行时动态链接:对某些目标模块的链接,是在程序执行中需要该模块的时候,才它进行链接.

静态链接

在将目标模块装配成一个装入模块的时候,需要对相应的地址进行修改。

这种先链接形成的完整的装入模块,又称为可执行文件,通常不再拆开它,要运行时可直接将它装入内存,这种事先链接,以后不再拆开的链接方式,称为静态链接。

image.png

装入时动态链接

用户源码编译之后得到目标模块,在装入内存时边装入边链接的,即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存,装入时动态链接有以下几个优点:

便于修改和更新(各目标模块是分开存放的,所以要修改或更新目标模块都非常容易)

便于实现对目标模块的共享(很容易将一个目标模块链接到几个应用模块上,实现多个应用程序对该模块的共享)

运行时动态链接

将某些模块的链接推迟到程序执行时才进行链接,即在执行过程中,当发现一个被调用模块尚未装入到内存中的时候,立即由OS去寻找该模块并将之装入内存,把它链接到调用模块上,凡在执行过程中为调用的模块,都不会被调入内存和被链接到装入模块上,这样不仅加快了程序的装入过程,同时也节省了大量的内存空间。

连续分配方式

为了能将用户程序装入内存,必须为它分配一定大小的内存空间。

连续分配方式是指为一个程序分配一个连续的内存空间,可以将连续分配方式分为 单一连续分配 固定分区分配 动态分区分配 动态重定位分区分配

单一连续分配

这是一种最简单的存储管理方式,但只能在单用户,单任务的操作系统中,将内存分为系统区和用户区u,系统区供OS使用,通常放在内存的低地址,用户区是指除系统区以外的全部内存空间,提供给用户使用。

固定分区分配

固定分区分配是一种最简单的可运行多道程序的存储管理方式,将内存用户空间划分为若干个固定大小的区域,在每个分区只装入一道作业,这样,便允许多道作业并发执行,当有空闲分区的时候,便可以从外村的后备作业队列中选择一个适当大小的作业装入该分区,当作业结束的时候,又从后备作业队列中找出另一作业调入该分区。

对于内存的用户空间的划分,有如下两种方法。

1.分区大小相等,即所有的内存分区大小相等。缺点是缺乏灵活性,即当程序太小时,会造成内存资源的浪费,程序太大时,一个分区又不足以装入该程序,导致该程序无法运行。

2.分区大小不等,把内存区划分成含有多个较小的分区,适量中等的分区和少量大分区,这样便可以根据程序的大小为之分配适当的分区

为了便于内存分配,将分区按照大小进行排队,并为之创建一张分区使用表,其中各表项包括,每个分区的起始地址,大小,状态(用来判断该分区是否已分配),当有程序需要装入内存时,由内存分配程序检索该表,从中找出一个能满足要求的,尚未分配的分区,将之分配给该程序,然后从该表项中的状态设置为已分配。若未能找到大小足够的分区,则拒绝为该用户分配内存。

image.png

动态分区分配

动态分区分配时根据进程 的实际需要,动态地为之分配内存,在实现可变分区分配时,将涉及到分区分配中所用的数据结构,分区分配算法,分区的分配和回收等。

首次适应算法(First Fit)

以空闲分区链为例,FF算法要求 空闲分区链以地址递增的次序链接,在分配内存时,从链首开始顺序查找,直到找到一个大小能满足要求的空闲分区为止,然后按照作业的大小,从该分区划分出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中,若从链首直到链尾都不能找到一个可以满足要求的分区,则此次内存分配失败,返回。

该算法倾向于优先利用内存中的低址部分的空闲分区,从而保留了高址部分的大空闲区,这给以后大作业分配大的内存空闲创造了条件,缺点在与低地址空间不断被划分,会留下许多难以利用的,很小的空闲分区,而每次查找又都是从低地址部分开始,这增加查找空闲分区的开销

循环首次适应算法(Next Fit)

与首次适应算法相比,循环首次适应算法不是每次都从链首开始查找。而是从上次找到的空闲分区的下一个空闲分区开始进行查找,直到查找到一个能够满足要求的空闲分区,从中划分出一块与请求大小相等的内存空间分配给作业。进行空闲分区分配的时候,会采用循环查找的方式,如果最后一个空闲分区的大小仍不能满足要求,则返回第一个空闲分区域。该算法能使内存中的空闲分区分布的更加均匀,从而减少了查找空闲分区时的开销,但是会缺乏大的空闲分区。

最佳适应算法(Best Fit)

该算法总是能把满足要求,又是最小的空闲分区分配给作业,避免大材小用,为了加速寻找,该算法要求把所有的空闲分区按照其容量从小到大的顺寻形成一个空闲分区链,这样,第一次就能找到满足要求的空闲区,必然是最佳的,孤立地看,最佳适应算法似乎是最佳的,在宏观上看却是不一定,因为每次分配后所切割下来的剩余部分总是最小的,会留下很多难以使用的小的空闲分区。

快速适应算法( Quick Fit)

快速适应算法又称为分类搜索法,是将空闲分区容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,这些,系统中存在多个空闲分区链表,同时在内存中设立一张管理索引表,该表的每一项对应一种空闲分区类型,并记录了该类型空闲分区链表表头的指针。

该算法的优点是查找效率高,仅根据进程的长度,寻找能容纳它的最小空闲分区链表,并且取下第一块进行分配即可。

该算法在进行空闲分区分配的时候,不会对任何分区产生分割,所以能保留大的分区,满足对大空间的需求,也不会产生内存碎片。但是在分区归还主存的时候算法复杂,系统开销大。

回收内存

当进程运行完毕释放内存的时候,系统根据回收区的首地址,从空闲分区链中找到响应的插入点,此时会出现4种情况

1.回收分区与插入点前一个空闲分区F1相邻接,此时将回收区与插入点的前一分区合并,不必为回收区分配新表项,只需要修改前一分区F1的大小。

2.回收分区与插入点前一个空闲分区F2相邻接,此时将两分区合并,形成新的空闲分区,用回收区的首址作为新空闲区的首址,大小为两者之和。

3.回收区同时与插入点的前、后两个分区邻接,此时将三个分区合并,使用F1的表项和F1的首址,取消F2的表项,大小为三者之和。

4.回收区既不与F1邻接,也不与F2邻接,这时为回收区单独建立一个新表项,填写回收区的首址和大小,并根据其首址插入到空闲链中的适当位置。

image.png

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注