绝对地址和逻辑地址
可见,我们写的代码要翻译成CPU能识别的指令。这些指令会告诉CPU应该去内存的哪个地址存/取数据,这个数据应该做什么样的处理。在这个例子中,指令中直接给出了变量x的实际存放地址(物理地址)。但实际在生成机器指令的时候并不知道该进程的数据会被放到什么位置。所以编译生成的指令中一般是使用逻辑地址(相对地址)
Eg∶编译时只需确定变量x存放的相对地址是100(也就是说相对于进程在内存中的起始地址而言的地址)。CPU 想要找到x在内存中的实际存放位置,只需要用进程的 起始地址+100 即可。
逻辑地址到物理地址的转换
逻辑地址到物理地址的转换有三种方法:
- 绝对装入
- 静态重定位
- 动态重定位
绝对装入
在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。
Eg:如果知道装入模块要从地址为100的地方开始存放…
绝对装入只适用于单道程序环境。 程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。通常情况下都是编译或汇编时再转换为绝对地址。
静态重定位
编译、链接后的装入模块的地址都是从0开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对地址进行"重定位",将逻辑地址变换为物理地址(地址变换是在装入时一次完成的)
静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。
动态重定位
动态重定位又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行,因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持。
采用动态重定位时允许程序在内存中发生移动。
动态重定位可将程序分配到不连续的存储区中,在程序运行前只需装入部分代码即可运行,然后在程序运行期间,根据需要动态申请分配内存。便于程序段的共享,可以向用户提供一个比存储空间大得多的地机空间 (分页存储,分段存储) 。
链接
再装入程序之前,还要经过链接这个步骤。
编译∶由编译程序将用户源代码编译成若干个目标模块(编译就是把高级语言翻译为机器语言)
链接∶由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块
装入(装载)∶由装入程序将装入模块装入内存运行
链接的三种方式
- 静态链接
静态链接
在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
装入时动态链接
将各目标模块装入内存时,边装入边链接的链接方式.
运行时动态链接
在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
覆盖与交换
早期的计算机内存很小,比如IBM推出的第一台PC机最大只支持1MB大小的内存。因此经常会出现内存大小不够的情况。
覆盖技术
覆盖技术的思想:将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存。
内存中分为一个"固定区"和若干个"覆盖区"。需要常驻内存的段放在"固定区"中,调入后就不再调出(除非运行结束) 不常用的段放在"覆盖区",需要用到时调入内存,用不到时调出内存
必须由程序员声明覆盖结构,操作系统完成自动覆盖。缺点∶对用户不透明,增加了用户编程负担。 覆盖技术只用于早期的操作系统中,现在已成为历史。
交换技术
交换(对换)技术的设计思想:内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中 某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)
暂时换出外存等待的进程状态为挂起状态(挂起态,suspend)
挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态
应该在外存(磁盘)的什么位置保存被换出的进程?
具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。
文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式。
对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。
由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式。总之,对换区的 I/O 速度比文件区的更快。
什么时候应该交换?
交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。
例如∶在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程。如果缺页率明显下降,就可以暂停换出。
应该换出哪些进程
可优先换出阻基进程。可换出优先级低的进程:为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间。 (注意∶PCB 会常驻内存,不会被换出外存)
动态分区分配算法
首次适应算法(First Fit)
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。 如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
最佳适应算法(Best Fit)
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
缺 点 : 每次都选最小的分区进行分配 ,会留下越来越多的 、 很 小 的 、 难 以 利 用 的 内 存 块 。 因 此 这 种 方 法会产生很多的外部碎片。
最坏/大适应算法(Worst/Largest Fit)
算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。 如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。 缺点:每次都选最大的分区进行分配 , 虽然可以让分配 后 留 下 的空 闲 区 更 大 , 更 可 用 , 但 是 这 种 方 式 会 导 致 较 大 的 连 续 空 闲 区 被 迅 速 用 完 。 如 果 之 后 有 “ 大 进 程 ” 到 达 , 就 没 有 内 存 分 区 可 用 了 。
邻近适应算法(Next Fit)
算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。 如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
内存的回收
当某一个用户作业完成释放所占分区时,系统应进行回收。在可变式分区中,应该检查回收区与内存中前后空闲区是否相邻。
在分区分配方案中,回收一个分区时有几种不同的邻接情况,在各种情况下应如何处理?
答:有四种:上邻,下邻,上下相邻,上下不相邻。
- 回收分区的上邻分区是空闲的,需要将两个相邻的空闲区合并成一个更大的空闲区,然后修改空闲区表。
- 回收分区的下邻分区是空闲的,需要将两个相邻的空闲区合并成一个更大的空闲区,然后修改空闲区表。
- 回收分区的上、下邻分区都是空闲的(空闲区个数为2),需要将三个空闲区合并成一个更大的空闲区(空闲区个数为1 ),然后修改空闲区表、
- 回收分区的上、下邻分区都不是空闲的,则直接将空闲区记录在空闲区表中。
存储管理
分页存储管理
将内存空间分为一个个大小相等的分区(比如∶每个分区4KB),每个分区就是一个"页框",或称"页帧"、"内存块"、"物理块"。每个页框有一个编号,即"页框号"(或者"内存块号"、"页帧号"、"物理块号")页框号从0开始。
将用户进程的地址空间也分为与页框大小相等的一个个区域,称为"页"或"页面"。每个页面也有一个编号,即"页号"页号也是从0开始。
(注∶进程的最后一个页面可能没有一个页框那么大。因此,页框不能太大,否则可能产生过大的内部碎片)
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。 各个页面不必连续存放,也不必按先后顺序来,可以放到不相邻的各个页框中。
逻辑地址结构
分页存储管理的逻辑地址结构如下所示∶
地址结构包含两个部分∶前一部分为页号,后一部分为页内偏移量 W。在上图所示的例子中,地址长度为32位,其中0~11位为“页内偏移量”,或称"页内地址";12~31位为"页号"。
为什么会这样子表示:
假设用32个二进制位表示逻辑地址,页面大小为 $2^{12}$B=4096B=4KB
0号页的逻辑地址空间应该是0~$(2^{12}-1)$,用二进制表示应该是∶
1号页的逻辑地址空间应该是 $2^{12}$~$(2^{13}-1)$,用二进制表示应该是∶
2号页的逻辑地址空间应该是$2^{13} $~$(2^{14}-1)$,用二进制表示应该是
逻辑地址2,用二进制表示应该是 000000000000000000 000000000010
若0号页在内存中的起始地址为X,则逻辑地址2对应的物理地址应该是 X+000000000010
逻辑地址4097,用二进制表示应该是00000000000000000001 000000000001
若1号页在内存中的起始地址为X,则逻辑地址4097对应的物理地址应该是 X+000000000001
页表
为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。
- 一个进程对应一张页表
- 进程的每一页对应一个页表项
- 每个页表项由"页号"和"块号"组成
- 页表记录进程页面和实际存放的内存块之间的对应关系
- 每个页表项的长度是相同的,页号是"隐含"的
为什么每个页表项的长度是相同的,页号是"隐含"的?
Eg∶假设某系统物理内存大小为4GB,页面大小为4KB,则每个页表项至少应该为多少字节?
4GB=$2^{32}$B ,4KB=$2^{12}$B
因此4GB的内存总共会被分为 $2^{32} / 2^{12} = 2^{20} $ 个内存块,因此内存块号的范围应该是0~$2^{20}-1$ 因此至少要20个二进制位才能表示这么多的内存块号,因此至少要3个字节才够(每个字节 8个二进制位,3个字节共24个二进制位)。
各页表项会按顺序连续地存放在内存中,如果该页表在内存中存放的起始地址为 X,内存块号用3个字节表示,则M号页对应的页表项一定存放在初始内存地址为X+3*M 的内存空间。
分段存储管理
进程的地址空间∶按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址
内存分配规则∶以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。
如下图,进程 A 按照逻辑功能被划分为三个段,每个段大小不一,最后再被分配到内存中不连续的各个空间中:
段式存储的逻辑地址结构
在引入分段存储管理后,逻辑地址的含义也不同了。开发者通过 段名称和偏移来描述逻辑地址
段表
程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称"段表"。
每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称 "基址")和段的长度。
各个段表项的长度是相同的。因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为M,段表项的长度是 6 byte,则K号段对应的段表项存放的地址为M+K*6
段页式管理
| | 优点 | 缺点 | | ---- | ---- | ---- | | 分页存储 | 内存利用率高,不会产生外部碎片,仅会产生少量内部碎片; | 不方便按照逻辑模块实现信息的共享和保护 | | 分段存储 | 很方便按照逻辑模块实现信息的共享和保护, | 但是若逻辑过多则会导致段过长,
另外,这种方式也会产生外部碎片 |
结合二者之长,出现了段页式存储管理方式。
如下图,段页存储管理会首先将进程按照逻辑模块划分为多个段,针对每个段再划分为多个页;同时也把内存划分为多个页框。分配内存的时候,一个页面就对应了一个页框。
段页式存储的逻辑地址结构
段页式系统的逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。如∶
分段对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。 因此段页式管理的地址结构 和 段式存储一样,都是二维的。
- 段号的位数决定了每个进程最多可以分几个
- 段页号位数决定了每个段最大有多少页
- 页内偏移量决定了页面大小、内存块大小是多少
段表结构
我们是将程序划分为多个段,相当于划分为多个子程序。对于每一个子程序而言,它会再次被划分为多个页面,因此每一个段(每一个子程序),它都维护着属于自己的一张页表。 段表,它需要记录的就是段号与段号对应段的页表之间的映射关系。
每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的(因为段表项的长度都是一样的)。
地址转换
虚拟内存
时间局部性∶如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行∶如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)
空间局部性∶一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)
基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。 在程序执行过程中,
当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序, 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。 在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存
虚拟内存的实现 方式
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此,虚拟内存的实现需要建立在离散分配的内存管理方式基础上。
请求分页管理方式
与基本分页管理相比,请求分页管理中,为了实现"请求调页",操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存 中存放的位置。