本章目标:
1、详细讨论内存硬件的组织方法;
2、讨论各种内存管理技术,如分段、分页;
8.1 背景介绍
高速缓存: 由于CPU对寄存器的访问速率快于对内存的访问速率,导致在实际运行中,没有数据完成正在进行的操作,CPU通常需要暂停(stall), 由于内存的频繁访问,这种暂停是难以接受的,所以增加高速缓存,用于协调速度的差异。
程序空间: 要确保每个进程都有独立的内存空间,因此需要确定进程可访问的合法内存访问,这里使用两个寄存器来实现着这种保护:基地址寄存器(最小的合法物理内存地址)、界限地址寄存器(地址范围)。
地址绑定: 编译器通常需要将源程序中的地址绑定到可重定位的地址, 链接程序或加载程序再将这些可重定位的地址绑定成绝对地址,每次绑定都是从一个地址空间到另一个地址空间的映射。
通常,将指令和数据绑定到内存地址有以下几种情况:
编译时绑定:事先知道程序在内存空间的位置,就可以在编译时生成绝对代码,从该位置开始并向后扩展,如果后来开始地址发生变化,那么就必须重新编译代码。不同的人安排程序的位置可能冲突。
加载时绑定:如果在编译时不知道程序在内存空间的位置,那么编译时必须生成可重定位代码(可以使地址平移的代码称为可重定位代码,它通过加载过程中系统给定的物理地址,生成程序的物理地址),延迟到加载时绑定程序,称为静态地址重定位。
运行时绑定:如果进程在执行时可以从一个内存段转移到另一个内存段,那么绑定要延迟到执行时才进行,在每次CPU访问内存时,进行地址的转换,需要借助某种物理设备,称为动态地址重定位。
静态地址重定位:不需要硬件支持,不能移动代码,不能处理非连续代码;
动态地址重定位:需要重定位寄存器,重定位寄存器使用如下:
逻辑地址:CPU所产生的地址;
物理地址:内存单元使用的地址;
8.2 交换
交换(swap): 进程暂时从内存中交换到备份存储上,当需要再次执行时调回内存; 这种交换策略被用于基于优先级的调度算法,如果有一个更高优先级的进程需要服务,内存管理器可以交换出低优先级的进程,以便于执行更高优先级的进程,当高优先级进程执行完毕后,低优先级进程可以交换回内存以便继续执行;
这种交换有时被称为滚出(roll out),滚入(roll in);
通常,若程序使用编译时绑定或加载时绑定,那么交换后必须返回原来的地址空间; 若程序使用运行时绑定,则交换后可以移到不同的空间;
交换需要备份存储,备份存储通常使用快速磁盘,必须足够大以便容纳所有用户的内存镜像副本,也必须提供对这些内存镜像的直接访问; 假设用户进程的大小为10MB,备份存储是传输速度为40MBps,那么10MB进程传入或传出的速度为250ms,假定平均寻址时间是8ms,那么交换时间为258ms,由于涉及换入和换出,总的交换时间为516ms;
8.3 连续内存分配
内存映射与保护:采用重定位寄存器和界限地址寄存器,进行保护;重地址寄存器含有最小的物理地址值,界限地址寄存器含有逻辑地址的范围值; 进行映射时,内存管理单元要求逻辑地址必须小于界限地址寄存器的值,并把逻辑地址与重地址寄存器的值相加,映射出物理地址的值;
多分区方法: 最简单的内存分配方法是将内存分为多个固定大小的分区(partition),每个分区容纳一个进程,多道程序的程度也会受到分区数的限制,当一个分区空闲时,就从输入队列中选择一个进程调入空闲分区。
动态存储分配的常用方法:
首次适应(first-fit):分配第一个足够大的内存块给进程使用,一旦找到足够大的内存块,就立即停止扫描;
最佳适应(best-fit): 分配最小的足够大的内存块,除非列表按大小排列,那么要求必须扫描整个列表;
最差适应(worst-fit): 分配最大的内存块,除非列表按大小排列,那么要求必须扫描整个列表;
比较这三种方法,空间利用率方面:首次适应和最佳适应均好于最差适应; 执行时间方面:首次适应优于最佳适应优于最差适应, 但是,首次适应和最佳适应都会产生外部碎片问题;
外部碎片:还没有分配出去,但是由于太小而无法使用的内存;
内部碎片:已经分配给进程,但是无法使用的内存;
在首次适应和最佳适应的执行过程中,随着进程的不断装入和移出,就会出现外部碎片问题;
紧缩(compaction):紧缩是一种解决外部碎片的问题,通过移动内存内容,使得所有空闲的内存空间整合到一起; 紧缩仅在绑定方式为运行时绑定才可以运行;若使用编译时绑定和装入时绑定,那么就不能对进程进行移动; 最简单的紧缩方法是将所有的进程移到内存的一端,而将所有空闲内存块移到内存的另一端,这种方案开销较大;
另一种解决外部碎片的方法是允许物理地址空间非连续,使用分页、分段技术;
8.4 分页
实现分页的基本方法: 将物理内存分为固定大小的块,称为帧(frame),将逻辑内存分为同样大小的块,称为页(page),当需要执行进程时,将进程页从备份存储调入到可用的内存帧中; 帧的大小和页的大小相同;
地址生成: CPU产生的每个地址分为两个部分:页号(p),页偏移(d), 页号作为页表中的索引,从页表中提取出每页所在物理地址的基地址,这个基地址和之前的页偏移组合,形成物理地址; 页的大小通常为2的幂,由页的偏移量决定,分页操作如下:
分页也是一种动态重定位,每个逻辑地址由分页硬件绑定为一定的物理地址; 采用分页技术不会产生外部碎片,每个帧都可以分配给需要它的进程,但会产生内部碎片:分配是以帧为单位进行的,如果进程所要求的内存并不是帧的整数倍,那么最后一个帧可能用不完,这就会产生内部碎片;
当进程需要执行时,首先检测该进程的大小,进程的每页都需要一帧,如果进程需要n页,那么内存中应该有n个帧,如果有,就可以分配给新进程;
帧表: 操作系统在管理物理内存的过程中,需要知道哪些帧已被占用,哪些帧可用,总共有多少帧,等等; 这些信息被保存在被称为帧表的数据结构中;
保护: 在分页环境下,内存保护是通过与每一帧相关联的保护位来实现的,这些保护位存在于页表中,可以用一个位来定义一个页是可读还是可写;用另一个位作为有效-无效位,当该位有效,表示相关的页在进程的逻辑地址空间内,无效,则表示不在逻辑地址空间内;
共享:分页的优点在于可以共享公共代码,实现方式是在每个进程的页表中添加共享的数据页,如图,进程P1,P2,P3共享了ed1,ed2,ed3;
页表的常用技术: 层次页表、哈希页表、反向页表;
层次页表:现代计算机系统支持很大的逻辑地址空间,导致页表本身可以非常大,显然不利于在内存中连续分配一个大的页表,一个简单的方法是将页表划分为更小的部分; 一种方法是两级分页算法:
哈希页表: 处理超过32位地址空间的常用方法是使用哈希页表,以虚拟页码作为哈希值,然后对域内元素进行匹配,得到相应的帧号,形成物理地址:
反向页表:
8.5 分段
分段是支持用户视角的内存管理方案,类似于在写程序时,人们会认为程序是由主程序加上方法、过程、函数所构成,分段把逻辑地址空间视作一组段构成的,每个段都有名称和长度,用户通过段名称和段内偏移指定地址(在分页中,用户只提供一个地址,该地址通过硬件被划分为页码和偏移量);
例如,C编译器可能创建如下段:
1、代码
2、全局变量
3、堆
4、每个线程的栈
5、标准的C库函数
段表: 用于将二维的用户定义地址映射为一维物理地址,段表的每个条目都有段基地址和段界限,段基地址存放该段在内存的开始物理地址,而段界限存放该段的长度,段表的硬件实现如下:
分段实例:
以段0为例,段基地址和段界限表示它的起始地址是1400,范围是1000,所以实际的物理内存范围是1400-2400;