操作系统——存储器管理
程序的装入和链接
用户程序想要在系统中运行,必须先将他装入内存,然后再将其转变为一个可执行的程序,步骤:
编译——>链接——>装入
程序的装入:
- 绝对装入方式:(只适用于单道程序环境)
在编译的时候就知道了程序驻留在内存的什么位置了;装入模块装入内存后,程序中的逻辑地址与实际内存地址完全相同,不用修改程序和数据的地址 - 可重定位装入方式:
在装入时对目标程序中指令和数据的修改过程称为重定位。地址变换在装入时一次完成,以后不再改变,称为静态重定位。
如下图:地址1000装入后,地址就变为了1000+10000=11000了。
- 动态运行时的装入方式:
动态运行时的装入程序,在把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都仍是相对地址。
程序的链接
根据链接时间的不同,把链接分成三种:
-
静态链接:在程序运行前,将目标模块及所需的库函数链接成一个完整的装配模块,以后不再拆开。
-
装入时动态链接:指将用户源程序编译后所得的一组目标模块,在装入内存时,采用边装入边链接的链接方式。
-
运行时动态链接:指对某些目标模块的链接,是在程序执行中需要该目标模块时,才对它进行链接。
将目标模块装配成装入模块时需解决的两个问题:
(1) 对相对地址进行修改
(2) 变换外部调用符号
连续分配存储器管理方式
单一连续分配
最简单的一种存储管理方式,但只能用于单用户、单任务的操作系统中。
采用这种存储管理方式时,可把内存分为系统区和用户区两部分,系统区仅提供给OS使用,通常放在内存低址部分,用户区是指除系统区以外的全部内存空间,提供给用户使用。
固定分区分配
- 原理
将内存用户空间划分为若干个固定大小的区域,在每个分区中只装入一道作业,这样把用户空间划分为几个分区,便允许有几道作业并发执行。当有一空闲分区时,便可以再从外存的后备作业队列中,选择一个适当大小的作业装入该分区,当该作业结束时,可再从后备作业队列中找出另一作业调入该分区。 - 划分分区的方法
可用两种方法将内存的用户空间划分为若干个固定大小的分区:
(1) 分区大小相等:缺乏灵活性,用于一台计算机控制多个相同对象的场合
(2) 分区大小不等:把内存区划分成含有多个较小的分区、适量的中等分区及少量的大分区,可根据程序的大小为之分配适当的分区。 - 实现
为便于内存分配,通常将分区按大小进行排队,并为之建立一张分区使用表,其中各表项包括每个分区的起始地址、大小及状态(是否已分配)。
当有一用户程序要装入时,由内存分配程序检索该表,从中找出一个能满足要求的、尚未分配的分区,将之分配给该程序,然后将该表项中的状态置为“已分配”;若未找到大小足够的分区,则拒绝为该用户程序分配内存。
动态分区分配
- 原理
动态分区分配是根据进程的实际需要,动态地为之分配内存空间。作业装入内存时,把可用内存分出一个连续区域给作业,且分区的大小正好适合作业大小的需要。分区的大小和个数依装入作业的需要而定。 - 实现
在实现过程中涉及如下问题:
分区分配中的数据结构
分区分配算法
分区分配及回收操作
1) 分区分配中的数据结构
(1)空闲分区表示
空闲分区表:记录每个空闲分区的情况。每个空闲分区占一个表目。表目中包括:分区序号、分区始址、分区的大小等。
空闲分区链:在每个分区的起始部分,设置一些用于控制分区分配的信息,以及用于链接各分区所用的前向指针;在分区尾部则设置一后向指针,在分区末尾重复设置状态位和分区大小表目。
(2)已占分区说明表
结构:作业号;起始地址;大小
2)分区分配算法
为把一个新作业装入内存,需按照一定的分配算法,从空闲分区表或空闲分区链中选出一分区分配给该作业。
常用的分配算法:
(1) 首次适应算法FF
(2) 循环首次适应算法
(3) 最佳适应算法
(1) 首次适应算法FF
FF算法要求空闲分区表以地址递增的次序排列。在分配内存时,从表首开始顺序查找,直至找到一个大小能满足要求的空闲分区为止;然后按照作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲分区表中。若从头到尾不存在满足要求的分区,则分配失败。
优点:优先利用内存低址部分的内存空间
缺点:低址部分不断划分,产生小碎片(内存碎块、内存碎片、零 头);每次查找从低址部分开始,增加了查找的开销
(2) 循环首次适应算法
在分配内存空间时,从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业。
为实现算法,需要:
设置一起始查寻指针
采用循环查找方式
优点:使内存空闲分区分布均匀,减少查找的开销
缺点:缺乏大的空闲分区
(3) 最佳适应算法
所谓“最佳”是指每次为作业分配内存时,总是把能满足要求、又是最小的空闲分区分配给作业,避免“大材小用”。
要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。
缺点:产生许多难以利用的小空闲区
分区分配及回收操作
回收内存
当进程运行完毕释放内存时,系统根据回收区首址,在空闲分区链(表)中找到相应插入点,此时可能有四种情况:
(1) 回收区与插入点的前一个分区F1邻接:将回收区与F1合并,修改F1的表项的分区大小
(2) 回收区与插入点的后一个分区F2邻接:将回收区与F2合并,修改F2的表项的首址、分区大小
(3) 回收区与插入点的前后两个分区F1、F2邻接:将三个分区合并,使用F1的表项和F1的首址,取消F2的表项,大小为三者之和
(4) 回收区既不与F1邻接,又不与F2邻接:为回收区单独建立新表项,填写回收区的首址与大小,根据其首址插到空闲链中的适当位置
可重定位分区分配
- 动态重定位的引入
在连续分配方式中,必须把系统或用户程序装入一连续的内存空间。如果在系统中只有若干个小分区,即使它们的容量总和大于要装入的程序,但由于这些分区不相邻,所以无法将程序装入内存。
解决方法:将内存中的所有作业进行移动,使它们全部邻接,这样可把原来分散的小分区拼接成大分区,这种方法称为“拼接”或“紧凑”。
缺点:用户程序在内存中的地址发生变化,必须重定位。 - 动态重定位的实现
在动态运行时装入的方式时,将相对地址转换为物理地址的工作在程序指令真正要执行时才进行。地址转换需要重定位寄存器的支持。程序执行时访问的内存地址是相对地址与重定位寄存器中的地址相加而成。
地址变换过程是在程序执行过程期间,随着对每条指令的访问自动进行的,称为动态重定位。
3. 可重定位分区分配算法
对换(Swapping)
- 对换的引入
多道程序环境下存在的问题:
阻塞进程占据大量内存空间
许多作业在外存而不能进入内存运行
对换:把内存中暂时不能运行的进程或者暂时不用的程序和数据,调到外存上,以便腾出足够的内存空间,再把已具备运行条件的进程和进程所需要的程序和数据,调入内存。
对换的分类:
- 整体对换(或进程对换):以整个进程为单位
- 页面对换或分段对换:以页或段为单位
实现进程对换,系统必须具备的功能: - 对换空间的管理
- 进程的换出
- 进程的换入
进程的换出与换入
- 进程的换出
换出过程:系统首先选择处于阻塞状态且优先级最低的进程作为换出进程,然后启动盘块,将该进程的程序和数据传送到磁盘的对换区上。 - 进程的换入
换入过程:系统应定时查看所有进程的状态,从中找出“就绪”状态但已换出的进程,将换出进程最久的进程作为换入进程,将之换入,直至已无可换入的进程或无可换出的进程为止。
分页存储管理方式
- 分页存储管理方式:离散分配的基本单位是页
- 分段存储管理方式:离散分配的基本单位是段
基本的分页存储管理方式(或纯分页存储管理方式):不具备页面对换功能,不具有支持实现虚拟存储器的功能,要求把每个作业全部装入内存后方能运行
页面与页表
- 页面
- 分页式存储管理的原理
分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片称为页面或页,并为各页加以编号,从0开始。同时把内存空间分成与页面相同大小的若干个存储块,称为块或页框。在为进程分配内存时,以块为单位将进程的若干个页分别装入到多个可以不相邻的物理块中。
进程的最后一页经常装不满而形成“页内碎片”。 - 基本分页式存储管理(简单页式存储管理)的原理
系统若能满足一个作业所要求的全部块数,此作业才能被装入内存,否则不为它分配任何内存。 - 请求分页式存储管理的原理
运行一个作业时,并不要求把该作业的全部程序和数据都装入内存,可以只把目前要执行的几页调入内存的空闲块中,其余的仍保存在外存中,以后根据作业运行的需要再调入内存。 - 页面大小的选择
由机器的地址结构所决定的,即由硬件所决定。
某一种机器只能采用一种大小的页面。
通常是:几KB到几十KB。
小:内碎片小,内存利用率高,但页面数目多,使页表过长,占大量内存,管理开销大;
大:页表短,管理开销小,内碎片大,内存利用率低 - 页面大小
页面大小应选地适中,应是2的幂,通常是512B~8KB。
- 地址结构
地址长度32位:
0~11位为位移量(页内地址),即每页的大小为4KB
12 ~31位为页号,地址空间最多允许有1M页
若给定一个逻辑地址空间中的地址为A,页面大小为L,则
页号P = INT[A/L]
页内地址d = [A] MOD L
例如:系统页面大小为1KB,设A=2170D,则
P=2,d=122
- 基本分页式存储管理(简单页式存储管理)的实现
在分页系统中,允许进程的每一页离散地存储在内存的任一存储块中,为方便查找,系统为每一进程建立一张页面映像表,简称页表。页表实现了从页号到物理块号的地址映射。 在页表表项中常设置一存取控制字段,对存储块内容加以保护。
地址变换机构
地址变换机构实现从逻辑地址到物理地址的转换,其任务是借助于页表,将逻辑地址中的页号转换为内存中的物理块号。
- 基本的地址变换机构
页表的功能可以由一组专门的寄存器来实现,一个页表项用一个寄存器。但寄存器成本高,系统页表可能很大,所以页表大多常驻内存。
在系统中只设置一个页表寄存器PTR,在其中存放页表在内存中的始址和页表的长度。
- 具有快表的地址变换机构
CPU在每存取一个数据时,需要两次访问内存:
第一次:访问页表,找到指定页的物理块号,将块号与页内偏移量拼接形成物理地址。
第二次:从第一次所得地址中获得所需数据,或向此地址中写入数据。
存储器利用率提高,处理器处理速度降低。
解决方法:在地址变换机构中,增设一个具有并行查寻能力的特殊高速缓冲寄存器,称为“联想存储器”或“快表”。
两级和多级页表
- 两级页表
将页表分页,并离散地将各个页面分别存放在不同的物理块中,同时为离散分配的页表在建立一张页表,称为外层页表,其每个页表项记录了页表页面的物理块号。
例如: 32位逻辑地址空间,页面大小为4KB(即12位),若采用一级页表机构,应有20位页号,即页表项应有1M个;在采用两级页表机构时,再对页表进行分页,使每页包含210(即1024)个页表项,最多允许有210个页表分页。即
2. 多级页表
两级页表对32位机器适用,64位呢?
页面大小为4KB即212B,还剩52位,按物理块大小212位来划分页表,则剩余40位用于外层页号,此时外层页表可能有1024G个页表项,要占用4096GB的连续存储空间
解决方法:采用多级页表,将外层页表再进行分页。
分段存储管理方式
- 便于编程:
用户常把自己的作业按逻辑关系划分成若干个段,每段都有自己的名字,且都从零开始编址,这样,用户程序在执行中可用段名和段内地址进行访问。 例如:LOAD 1,[A] | 。 - 分段共享:
在实现程序和数据的共享时,常常以信息的逻辑单位为基础,而分页系统中的每一页只是存放信息的物理单位,其本身没有完整的意义,因而不便于实现信息的共享,而段却是信息的逻辑单位,有利于信息的共享。 - 分段保护: 信息保护是对相对完整意义的逻辑单位(段)进行保护。
- 动态链接:当运行过程中又需要调用某段时,再将该段(目标程序)调入内存并链接起来。所以,动态链接是以段为基础的。
- 动态增长:
在实际系统中,有些数据段会不断地增长,而事先却无法知道数据段会增长到多大,分段存储管理方式能较好地解决这个问题。
分段系统的基本原理
- 分段
在分段存储管理方式中,作业地址空间被划分为若干个段,每个段定义了一组逻辑信息,都有自己的名字。通常用段号代替段名,每段从0开始编址,并采用一段连续地址空间。段长由逻辑信息组的长度决定。整个作业的地址空间分成多个段,逻辑地址由段号(段名)和段内地址所组成。
该地址结构允许一个作业最长有64K个段,每段的最大长度为64KB。
- 分段式存储管理的原理
作业分为若干个段。每段分配一个连续的内存区,由于各段的长度不等,这些区域也就大小不一。作业各段间不要求连续。 - 基本分段式存储管理的原理
在段式存储管理原理的基础上,要求将整个作业的全部段装入内存。 - 请求分段式存储管理的原理
在段式存储管理原理的基础上,不要求将整个作业的全部段装入内存。只装入作业的几段即可运行,其余段可根据运行的需要再装入内存。
- 基本分段式存储管理的实现
1)段表
在分段式存储管理系统中,系统为每个分段分配一个连续的分区,而进程中的各个段可以离散地移入内存中不同的分区中。
为使程序正常运行,须在系统中为每个进程建立一张段映射表,简称“段表”。每个段在表中占有一个表项。
段表结构:段号;段在内存中的起始地址(基址);段长。
段表可以存放在寄存器中,但更多的是存放在内存中。
段表用于实现从逻辑段到物理内存区的映射。
2)地址变换机构
在系统中设置段表寄存器,用于存放段表始址和段表长度,以实现从进程的逻辑地址到物理地址的变换。
当段表存放在内存中时,每访问一个数据,都需访问两次内存,降低了计算机的速率。
解决方法:设置联想寄存器,用于保存最近常用的段表项。
- 分页和分段的主要区别
- 相似点:
采用离散分配方式,通过地址映射机构实现地址变换 - 不同点:
- 页是信息的物理单位,分页是为了满足系统的需要;段是信息的逻辑单位,含有一组意义相对完整的信息,分段是为了满足用户的需要。
- 页的大小固定且由系统确定,由系统把逻辑地址分为页号和页内地址,由机器硬件实现;段的长度不固定,取决于用户程序,编译程序对源程序编译时根据信息的性质划分。
- 分页的作业地址空间是一维的;分段的作业地址空间是二维的。
信息共享
分段系统的一个突出优点是易于实现段的共享和保护,允许若干个进程共享一个或多个分段,且对段的保护十分简单易行。
分页系统中虽然也能实现程序和数据的共享,但远不如分段系统方便。
在分段系统中,实现共享十分容易,只需在每个进程的段表中为共享程序设置一个段表项。
可重入代码又称为纯代码,是一种允许多个进程同时访问的代码,可重入代码不允许任何进程对它进行修改。
可重入代码(Reentrant Code):
又称为“纯代码”(Pure Code),在实现段共享时,需要用到可重入代码(Reentrant Code) 。它是一种允许多个进程同时访问的代码,是一种不允许任何进程对其进行修改的代码。
在每个进程中,配以局部数据区,将在执行中可能改变的部分,拷贝到该数据区,这样,程序在执行时,只对该数据区(属于该进程私有)中的内容进行修改,而不去改变共享的代码,这时的可共享代码即成为可重入代码。
段页式存储管理方式
分段和分页存储管理方式各有优缺点。
把两者结合成一种新的存储管理方式——段页式存储管理方式,具有两者的长处。
- 基本原理
先将用户程序分成若干段,再把每个段分成若干页,并为每个段赋予一个段名。
基本段页式存储管理:把作业的所有段装入内存方可运行。
请求段页式存储管理:没必要把整个作业装入内存,可把作业
的几段或几页装入内存即可运行。
在段页式系统中,地址结构:段号;段内页号;页内地址。 - 实现
在段页式系统中,为了实现地址变换,增加一个段表寄存器,用来存放段表始址和段长。
在段页式系统中,为了获得一条指令或数据,需访问三次内存:
第一次:访问内存中的段表,取得页表始址
第二次:访问内存中的页表,取得该页所在的物理块号,将块号
与页内地址形成物理地址
第三次:访问第二次所得的地址,取出指令或数据
缺点:访存次数增加两倍
解决方法:增设高速缓冲寄存器