重点
内存连续分配的三种方式,及可变分区分配的五种分配算法。
内存离散分配的三种方式,分页管理涉及的页表、地址变换和快表。
程序的执行步骤
源程序需要经过编译、链接和装入之后,才能被处理器运行。
在程序装入内存前,装入模块中给出的程序地址为程序的逻辑地址或相对地址。一个用户作业的所有装入模块的逻辑地址集合称为该作业的逻辑地址空间。
当用户作业被装入内存后,操作系统的存储管理器会通过名为地址变换/地址重定位的操作,将逻辑地址转换为物理地址。
Note:下面的“链接”和“装入”部分推荐先跳过,先看内存的连续分配和离散分配。
链接
- 静态链接
若干个模块被依次链接成一个大模块 - 装入时动态链接
说是在装入内存时,采用边装入边链接的链接方式。 那逻辑地址怎么确定呢?那如果装入时使用的静态重定位,那岂不是同时确定逻辑地址和物理地址?感觉怪怪的。 - 运行时动态链接
连接过程被推迟到程序运行时进行,使用到才链接。
Note:不是很能理解模块和链接这两个词的意思。也没有在网上找到比较深入的解释,基本都是照本宣科。
装入
- 绝对装入
直接将源程序放入内存即可,不对地址做任何变换。这要求程序员在编程时熟悉内存的使用状况。
使用环境是单道批处理系统(单个程序进入计算机系统,对磁带上的一批作业能自动的逐个作业,依次运行,无须人工干预;每次内存中只载入一个程序)。 - 静态重定位
链接后生成的逻辑地址从0开始,而在装入内存时简单地给每个逻辑地址加上一个偏移量即可,在目标代码装入内存的时完成。
实现简单,地址变换不需要专门的硬件。缺点是程序在执行过程中不能在内存中移动。存储空间必须连续。 - 动态重定位
在程序被装入内存后,系统并不着急将逻辑地址转化为物理地址。而是在每次访问内存单元前才将要访问的程序或数据地址变换成内存地址。
一个程序由若干个相对独立的目标模块组成时,每个目标模块各装入一个存储区域,这些存储区域可以不是顺序相邻的,只要各个模块有自己对应的定位寄存器(大概对应汇编里的段地址寄存器)就行。
分页分段一般采用动态地址重定位,固定分区、动态分区采用静态地址重定位。
内存的连续分配
1. 单一连续分配
在单道程序环境下,内存划分为系统区和用户区,而用户区中只有一个进程。
地址变换可以使用静态重定位和动态重定位两种方式,因为内存地址是连续的。
在动态重定位的方式下,系统使用一个基址寄存器来储存用户区的第一个字节的地址。程序的物理地址等于逻辑地址加上基址寄存器的偏移量。这样用户程序永远无法访问到操作区。
缺点是处理器和内存的利用率很低。
2. 固定分区分配
用户区被分割成很多大小固定的分区,每一个分区是连续的存储区域,且每个分区只能装入一个程序。
使用一张分区表来管理。表中分区的大小可以都相等,也可以分成大小不同的分区,从而让大程序用大分区,小程序用小分区。
这种方式下,地址变换可以使用静态重定位和动态重定位两种方式。而此时就不仅仅使用一个基址寄存器了,而是使用一对上限、下限寄存器。超过上限和小于下限的地址访问都会被拒绝。
优点是好于单一连续分配。缺点是内存利用率依然不高(极大进程和极小进程)。
3. 可变分区分配
在内存还没有被使用之前,用户区是一个大的分区。但随着进程的分配和回收,会形成间断的空闲分区。而通过数据结构的组织,我们可以继续利用这些空闲分区。
我们使用空闲分区表和空闲分区链来管理。空闲分区表记录每个空闲分区的情况,而空闲分区链用双向链表组织每个空闲分区的情况,组织方式由分配算法决定。
按道理来说可以只使用空闲分区链的,但将所有从数据都存在表里也行(不是很懂为什么空闲分区链的节点又记录了分区大小和分配情况,重复储存数据)。
下面这个图倒还有点靠谱。节点本身记录的位置、长度是可以计算下一个节点的。也不需要表了。唯一的缺点就是不能反向遍历,但好像也没有这个需求…
可变分区分配算法:
- 首次适应算法
将空闲分区按照地址从小到大递增的顺序排列,每次从头部开始寻找 - 循环首次适应算法
查找下一块满足的分区时不从起点重新开始,而是从上一次查找到的分区开始 - 最优适应算法
将空闲分区按照大小从小到大递增的顺序排列,每次从头部开始寻找 - 最坏适应算法
将空闲分区按照大小从大到小递减的顺序排列,每次从头部开始寻找 - 快速适应算法
将经常用到的分区,根据它们的长度,每个都用一个链表进行管理,这样就需要一个管理链表的表。
(我感觉类似快表优化了一下。不是很懂书上列的那些优点)
Note:固定分区分配算法和可变分区分配算法都需要已知进程大小(没有大小信息又能怎么优化呢…)。
分区的回收
正常相邻合并即可。动态分区需要根据算法修改表和链。
内存管理中使用的技术
- 覆盖技术
- 紧凑技术(JVM的标记-整理算法)
- 对换技术(对于不同进程而言)
内存的离散分配
因为碎片的存在,内存始终难以利用。所以人们想到直接将进程分割成碎片的大小——离散分配。
分页管理
在分页管理中,地址的变换采用动态重定位的方法。
页面
对于32位的逻辑地址空间,页面大小通常为4K,其中高20位为页号,低12位为页内偏移量。
分页后所有的页面,我们用页表来记录。物理块
对于进程和内存,我们都将它们分成碎片,且碎片大小也相同,只是对于内存而言,我们称之为分块。而物理地址和逻辑地址类似的,高20位为块号,低12位为块内偏移量。
对于一个逻辑地址对应的物理地址,它们的低12位是相同的。页表
页表是用于纪录进程的每个页面到内存中物理块的对应情况。系统产生从0开始连续的逻辑地址,并使用页表记录逻辑地址对应的物理块号。页表可以视作一个map<页号, 块号>。
每个进程的页表信息——第一个页面的起始物理地址和页表总数都用16位存储,在运行时储存在页表寄存器中,不在运行时储存在PBC中。所以一个处理器只需要一个页表寄存器。地址变换机构
i. 判断是否越界。
将逻辑地址的高位,即页号,和页表总数(从1开始计数)进行比较。如果页号大于等于页表总数,就会产生地址越界中断。
ii. 由逻辑地址计算块号
根据逻辑地址的高20位(页号)计算出页表中表项对应的块地址。具体指add(物理块) = Size(页面) * 页号 + 第一个页面的起始物理地址。
iii. 加上块内偏移量
物理地址 = add(物理块) + 页内偏移量快表
从地址变换过程可以看出,系统每次先根据逻辑地址访问内存中的页表,再访问对应的物理地址,这样需要两次对内存的读取。而通过将常用的页表项放到高速缓存中,我们可以节约一次对内存的访问,提高读写速度。这样,高速缓存也叫快表TLB。
当然快表的置换也可以分配一定的策略咯,可以使用虚拟内存的页面置换策略。我感觉快表和虚拟内存非常相似。多级页表
因为在32位系统下,一个页面大小为4K,所以页面有1M个。假设一个页表项需要4B,那么光储存页表都需要连续的4MB空间。这和普通的、仅为4K的连续空间相差巨大。
为了离散化页表,我们可以将每个页表也划分为4KB的二级页表(共1K的表项),再用一个4KB的一级页表(共1K的表项)来管理。反置页表
略
分页管理的缺陷
- 不能适应进程中长度的变化
- 不能反映页面之间的逻辑关系
分段管理
在正常情况下,程序员编制的程序,是以段为单位加载的,而段的长度是可变的,在程序中,分段可以显式或隐式地定义。
比如,一个程序在通过编译后大致可分为代码段(text)、数据段(data)、堆栈段和BSS段。
通过段号和段内偏移量,每个段都构成一个独立的地址空间,而且地址空间可以独立地增大和缩小。
具有分页的分段
在分段的基础上加上分页。即每个段内用分页。
参考文献
**这里是我的blog:有更多总结分享。排版可能也会更好看一点=v=
https://endlesslethe.com/operating-system-course-tutorial-1.html**