第4章 存储器管理
4.1 存储器管理概述
4.1.1 存储器管理功能
存储器管理应该具有以下功能:
- 内存分配(内存分配数据结构、内存分配功能算法、内存回收功能)
- 内存保护(硬件检查越界、软件中断处理)
- 地址映射(逻辑、物理地址、硬件支持)
- 内存扩充(虚拟存储技术)
详述:
-
内存分配
内存分配是操作系统从空闲空间中划分出适当的空间分配给进程的过程,该过程决定了多道程序如何共享内存。
根据程序占用内存空间是否连续,可以将内存分配分为连续内存分配(给程序分配一块连续的内存空间以存储程序的所有指令和数据)和离散内存分配(以页或段为单位给程序分配内存空间,页或段不必是连续的)。 -
地址映射
内存每一个存储单元都有一个与之对应的物理地址。程序编译时采用逻辑地址。运行程序时,需要将程序逻辑地址转换为内存物理地址。
逻辑地址:目标代码的相对编址
物理地址:内存存储单元的编址
重定位:逻辑地址转换为物理地址的操作(过程)
为什么要进行重定位?
如果不进行地址转换,将会发生运行错误
-
内存扩充
当有一个比内存容量还要大的程序运行,或者同时在内存中运行更多的程序时,就需要操作系统利用外存对内存通容量进行扩充 -
内存保护
在多任务系统环境中,多个进程共享内存,为了避免进程间侵犯领地,尤其是为了防止用户进程侵犯系统进程占用的内存空间,必须采用内存保护措施。内存保护功能一般由硬件和软件配合实现。
4.1.2 存储器的层次结构
本章针对的是内存的管理
4.2 程序的装入和链接
用户源程序从编辑到在内存中执行需要经历:编译->链接->装入
4.2.1 程序的链接
-
静态链接
程序运行之前,多个目标模块及所需要的库函数链接成一个完整的装入模块存储于外存,以后不在拆分。
链接需要解决的问题:
(1):修改目标模块的相对地址
(2):变换外部调用符号
-
装入时动态链接
目标模块在装入内存时,边装入边链接,即在装入一个目标模块时,如果发生其他模块调用,将引起装入程序查找相应的目标模块,并将此目标模块装入内存并进行链接。
装入时动态链接方式有两个优点:
(1)便于目标模块的修改和更新
(2)有利于实现目标模块的共享 -
运行时动态链接
运行时动态链接是在程序执行中,当发现某一个被调用目标模块尚未链接,立即由操作系统去找该目标模块并将之装入内存,再把它链接到调用者模块上。
4.2.2 程序的装入
- 绝对装入方式:绝对装入方式根据程序在内存中将要驻留的起始地址,选择该地址作为目标模块的链接起始地址,产生与驻留内存物理地址一致的装入模块,即程序逻辑地址与内存物理地址完全相同
- 可重定位装入方式:在装入程序时,根据操作系统为其分配的内存空间起始地址,将程序指令中的逻辑地址转换为与之对应的内存物理地址。
- 动态运行时装入方式:在把装入模块装入内存时,并不立即把装入模块中的逻辑地址转换为绝对地址(物理地址),而是把这种地址转换推迟到程序真正运行时才进行。
可重定位装入方式可以将程序装入到内存的任何位置,但是不允许在内存中移动位置。因为,程序如果在内存中移动,意味着程序在内存中的起始物理地址发生改动,必须再次修正程序指令中的逻辑地址。但是,多任务环境往往出于某种原因,需要程序在内存中移动位置,此时就应该采用动态运行时装入方式。
采用动态重定位装入方式,程序运行时,逻辑地址到内存物理地址的转换靠硬件地址转换机构来实现。
硬件系统中通常是设置一个重定位寄存器,加载操作系统为程序分配的内存空间的起始物理地址。
程序的装入:
- 绝对装入:目标代码与装入位置地址一致;
- 静态重定位装入:一边装入,一边实现地址转换;
- 动态重定位装入:原代码装入,待执行时再进行地址转换。
4.2.3 存储分配(管理)方式
无论采用何种方式装入,均涉及到内存共享与分配问题。
分配方式:
- 连续分配:为作业(进程)分配地址连续的存储空间
- 离散分配:为作业(进程)分配地址不连续的存储空间
4.3 连续分配方式
是指一个用户程序分配一个连续的内存空间,又称为分区管理方式
分区——是指内存中的一个连续区域
分区分配方式可以分为:单一连续分配、固定分区分配、动态分区分配、动态重定位分区分配等。
4.3.1 单一连续分配
- 单一连续分配管理方式是将内存分为系统区和用户区两个区域。系统区提供给操作系统使用,用户区提供给用户程序使用,一次只能装入一个程序运行。(适用于早期单用户单任务OS)
- |-------系统区--------|------------用户区-----------------|
- 低地址 高地址
4.3.2 固定分区分配
最简单的可运行多道程序的存储器管理方式
-
算法思想:内存可用区域划分成若干个大小固定的存区,每个存区分别装入一道作业的代码(数据)
-
内存分配:为方便内存分配,通常将分区按大小排队,建立一张分区使用表。表项:分区起始地址、大小、状态(是否已分配),如图:
-
固定分区方式的优缺点:
- 内存可同时装入多道作业代码,算法实现简单;
- 存在浪费(分区一次性全部分配出去),分区内 存在 内碎片
4.3.3 动态分区分配(可变分区分配)
- 算法思想:事先不划分分区,带作业需要分配内存时,再按照需求划分分区(分区的大小和个数不固定)
- 算法实现:建立相关数据结构,根据分配策略(或算法)设定算法程序。
动态分区分配是根据进程的实际需要,动态地为之分配内存空间。在实现时,涉及到三个问题:数据结构、分配算法、分配和回收操作
- 分区分配中的数据结构:两种形式——空闲分区表、空闲分区链
-
分区分配算法——基于顺序搜索的动态分区分配算法
-
首次适应算法(FF):空闲分区链按地址递增排序。分配内存时,从链首开始顺序查找,知道找到一个大小能够满足需要的空闲分区。然后按照作业大小从该分区划出一块内存空间分配给请求者,剩余的空闲分区仍留在链中。
该算法倾向于优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区,为后面大作业的分配创造条件。但是缺点是:低址部分不断被划分,会留下许多难以利用的、很小的空闲区,称为碎片。
-
循环首次适应算法(NF):在为进程分配内存空间时,不再是每次从链首开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个满足要求的空闲分区,划分出大小合适的空闲分区分配。为实现该算法,应该设置一个起始查询指针。
-
最佳适应算法(BF):该算法要求将所有的空闲分区按其容量从小到大的顺序形成一个空闲分区链,每次分配时总是将能满足要求的最小分区分配给请求者。
-
最坏适应算法(WF):每次分配时,总是将能满足要求的最大分区分配给请求者。将空闲分区按其容量从大到小顺序排列——加快查找
-
-
分区分配算法——基于索引搜索的动态分区分配算法
- 快速适应算法(quick fit):又称分类搜索法,是将空间分区根据其容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表。同时在内存中设立一张管理索引表,其中的每一个索引表项对应一种空闲分区类型,并记录该类型空闲分区链表表头的指针。分配时查找索引表,找到合适的链表,然后摘下链表的第一块即可
- 伙伴系统(buddy system):该算法规定,无论已分配分区或空闲分区,其大小均为2的k次幂(k为整数,/≤k≤m)。对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表。要为进程分配一个长度为n的存储区域是,计算i值,使得2i-1<n≤2i然后在大小为2i的空闲分区链中查找。
- 哈希算法:利用哈希快速查找的优点以及空闲分区在空闲分区表中的规律,建立哈希函数,构造以空闲分区大小为关键字的哈希表,该表每个表项记录一个对应的空闲分区链表表头指针。进行空闲分区分配时,根据空闲分区大小,通过哈希函数计算,得到在哈希表中的位置,从而得到空闲分区链表的指针。
-
分区分配操作
4.3.4可重定位分区分配
-
动态重定位的引入
目的:为了解决内存“碎片”问题——外部碎片
方法:移动作业,称为“拼凑”或者“紧凑”——需要动态重定位
紧凑:- 通过作业移动将原来分散的小分区拼接成一个大分区
- 每次紧凑之后,都必须对移动的程序或者数据进行重定位。
-
动态重定位的实现
为了使地址转换不会影响到指令的执行速度,必须有硬件地址变换机构——专设两个控制寄存器:基址寄存器和限长寄存器
物理地址 = 逻辑地址 + 基址寄存器中的地址值
程序从内存某一处移动到另一处时,只需要修改基址寄存器中的值即可
- 动态重定位分区分配算法
算法思想:在可变分区分配算法的基础上,采用动态重定位方式装入程序(数据)。当没有足够大的分区供分配时,若总空闲存储容量够用,再将各分区中的内容向内存一端移动(紧凑),使另一端形成一个大的空闲分区,然后再分配
与动态分区分配算法基本相同,只是增加了紧凑功能:当找不到满足请求的空闲区时,但是空闲区总和大于等于请求数时,进行紧凑操作。
4.4 对换
4.4.1 多道程序环境下的对换技术
- 对换的引入
- 阻塞进程占用大量资源
- 外存中的等待作业因无内存而不能调入运行
会浪费资源
对换:把内存中暂时不能运行的进程或进程所需要的程序和数据,调出到外存上,以便腾出足够的内存空间,再把已具有运行条件的进程或者进程所需要的程序和数据调入内存。
- 对换的类型
整体对换(进程对换/中级调度,目的是为了解决内存紧张):
- 对换以整个进程为单位
- 广泛应用于分时系统
部分对换:(目的是为了支持虚存)
- 对换以“页”为单位——页面对换
- 对换以“段”为单位——分段对换
要实现进程对换,需要实现三方面功能:
- 对换空间的管理
- 进程的换出
- 进程的换入
4.4.2 对换空间的管理
在具有对换功能的OS中,磁盘空间分为文件区和对换区
文件区:
- 存放文件
- 管理主要目标:提高文件存储空间利用率
- 为此,采用离散分配方式
对换区:
- 存放换出的进程
- 管理主要目标:提高进程换入和换出的速度
- 为此,采用连续分配方式
数据结构:对换区的空闲盘块管理的数据结构与动态区分配方式采用的数据结构相似——空闲区表或空闲区链
空闲分区表中应包含两个表项:对换区的首址及其大小,它们的单位是盘块号和盘块数。
分配算法:对换区的分配与回收,与动态分区分配雷同,其分配算法可以是首次适应、循环首次适应或最佳适应分配算法
。
4.4.3 进程的换出和换入
- 进程的换出:当一个进程由于创建子进程而需要更多的内存空间,但是没有足够的内存空间等情况发生时,系统应将某个进程换出。
换出过程:- 选择处于阻塞状态且优先级别最低的进程作为换出进程
- 启动磁盘,将该进程的程序和数据传送到磁盘的对换区上
- 如果传送过程未出现错误,回收该进程所占用的内存空间,并对该进程的PCB进行相应的修改
- 进程的换入:系统应该定时查看所有进程的状态,从中找出“就绪”状态但是已经换出内存的进程,将其中换出时间最久的进程换入,直至已无可以换入的进程或者无可换出的进程为止。
4.5 分页存储管理方式(重点)
连续分配的方式会形成“碎片”,而“紧凑”要付出很大的开销——>产生了离散分配方式
4.5.1 分页存储管理的基本方法
页面和页表:
逻辑地址->页;物理地址->块; 块和页的大小相等
- 页面:
- 将逻辑地址空间分为若干大小相等的片,称为页面或者页,页号从0开始
- 内存空间分为页大小相等的若干存储块,称为块或页框,也是从0开始编号
- 最后一页装不满一块,称为“页内碎片”
- 页面的大小:应是2的幂,通常大小512B~8KB——不宜过大,也不宜过小
地址结构:
分页的地址结构:31|--------页号P------12|11-------位移量d------------|0
位移量d也称为页内地址。图中地址长度为32位,每一页的大小为4KB,地址空间最多为 2 20 2^{20} 220(1M)个页。
对于特定的机器,其地址结构是一定的。
若是逻辑地址为A,页面大小为L,则页号和页内地址可以按照下式求得:
P
=
i
n
t
(
A
/
L
)
P = int(A/L)
P=int(A/L)
d
=
A
m
o
d
L
d = A mod L
d=AmodL
页面大小为4KB,逻辑地址为7800及5F86,分别求页号和页内偏移。
4.5.2 地址变换机构(硬件实况)
为了将用户地址空间中的逻辑地址转换为内存地址空间中的物理地址。
- 基本的地址转换机构
页表驻留在内存中,设置一个页表寄存器存放页表在内存中的始址。地址转换的原理如下图所示:
进程未执行时,页表的始址和页表长度存放在本进程的PCB中。
进程访问某个逻辑地址中的数据时,分页地址变换机构会自动将有效地址(相对地址)分为页号和页内地址两部分,再以页号为索引去检索页表。在执行检索之前,先把页号与页表长度进行对比,如果页号大于或等于页表长度,则表示本次访问的地址已超越进程的地址空间,此错误被发现将产生一地址越界中断。
逻辑地址->物理地址:页号替换为块号,偏移量不变。
工作原理:
考察逻辑地址2056,假定一页的大小为1K(1024B),其中0号页放在3号块中,1号页放在5号块中,2号页放在8号块中,求此逻辑地址对应的物理地址。
再来一个例题:
4.5.3 两级和多级页表
现在的大多数计算机系统,都支持非常大的逻辑地址空间(
2
32
2
64
2^{32} ~ 2^{64}
232 264)
考虑到具有32位逻辑地址空间的分页系统:
若是页面大小为4KB(即
2
12
2^{12}
212B),则每一个进程页表中的页表项可达1兆个,因每一个页表项占用4字节,故而每一个进程仅仅页表就要占用4MB的内存空间,而且要求是连续的——这显然是不现实的。
解决方案:
- 采用离散分配的方式来解决难于找到一块连续的大内存空间的问题;
办法:两级和多级页表 - 只将当前需要的部分页表项调入内存,其余的页表项驻留在磁盘上,需要时再调入。
1. 两级流表
采用离散的方式来解决难于找到一块连续的大内存空间的问题;
解决方法:
- 将页表分页
- 将各个页面离散地存放在不同的物理块中
- 为离散分配的页表再建立一张页表,称为外层(外部)页表
分页分配不足之处
分割分配并存放,使逻辑上完整的模块物理上不完整
->不利于内存信息(数据)共享和保护
4.6 基本分段存储管理方式
OS:单道到多道->存储管理方式:单一连续分配->固定分区分配
为适应不同大小的用户程序需求:存储方式固定分区分配->动态分区分配
为了提高内存利用率:连续分配->离散分配方式——分页存储管理
为了满足用户(程序员)编程和使用上多方面的要求:引入分段存储管理
4.6.1 分段存储管理方式的引入
- 方便编程
- 信息共享:实现程序和数据共享时,以信息的逻辑单位为基础
- 信息保护:对信息的逻辑单位进行保护
- 动态增长
- 动态链接
4.6.2 分段系统的基本原理
1. 分段
-
作业地址空间划分为若干段,每段定义了一组逻辑信息。如
- 主程序段MAIN;
- 子程序段X
- 数据段D
- 栈段S
-
每一段都有自己的名字,为了实现简单,常用段号代替段名(段号从0开始)
-
每一段内都从0开始编址,采用一段连续的地址空间。由于分多个段,所以地址是二维的,
2. 段表
- 每一个段占一个连续的内存空间,各个段之间不要求连续(可以在不同的分区中)——但和连续分配不同
- 需要利用段表来进行地址变换(动态重定位)
- 段表结构:段长、基址 —— 逻辑地址->物理地址的映射
3. 地址变换机构
每访问一个数据,要访问两次内存,速度减慢1/2.但是段表存于来联想存储器中时,比没有地址变换的常规存储器存取速度仅减慢10%~15%
分页和分段的主要区别:
- 信息单位不同:页是信息的物理单位,段是信息的逻辑单位
- 大小不同:页的大小固定,段的大小不固定
- 维数不同,分页存储管理方式的程序逻辑地址空间是一维的,而分段存储管理方式的程序逻辑地址空间是二维的
4.6.3 信息共享
4.6.4 段页式存储管理方式
- 分页系统能有效地提高内存利用率
- 分段系统能很好地满足用户的需求
取长补短——段页式存储管理方式
1. 基本原理
先将用户程序分为若干段,再把每段分成若干页
逻辑地址由3部分组成:段号、段内页号、页内地址。
|-----段号S--------|------段内页号P-------|-----------页内地址W-----|
利用段表和页表实现地址映射,如下图所示:
2. 地址变换过程
需要配置一个段表寄存器,其中存放段表始址、段表长度。原理图如下:
三次访存:
1)访问内存中的段表,从中取得页表始址
2)访问内存中的页表,取出该页的物理块号
3)真正的从第二次访问所得的地址中取出指令或数据