目录
一、内存的基础知识
1 、逻辑地址和物理地址
2、从写程序道程序运行
将用户源程序变为可在内存中执行的程序,需要以下几个步骤:
- 编译:将高级语言翻译成机器语言(Java中将字节码编译成机器码,有解释执行和即时编译两种方式)。
- 链接:将目标模块生成装入模块,链接后形成完整的逻辑地址。
- 装入:由装入程序将装入模块装入内存运行。
3、链接的三种方式
- 静态链接:在程序运行前,将各自目标模块及它们所需库函数链接成一个完整的可执行程序(装入模块)。
- 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
- 运行时动态链接:在程序运行中需要该目标模块时,才对它进行链接。优点是便于修改和更新。
4、 装入的三种方式
- 绝对装入:编译时产生绝对地址 (单道程序阶段,无操作系统)
- 静态重定位:装入时将逻辑地址转化为物理地址(早期多道批处理系统)
- 动态重定位:运行时将逻辑地址转化为物理地址,需设置重定位寄存器。(现代操作系统用的方式)
二、内存管理的概念
1、概念
虽然计算机硬件和内存容量都在飞速发展,但是仍不能将所有用户进程和系统需要的全部程序和数据都放入内存,所以操作系统必须将内存空间进行合理地划分和有效地动态分配。即操作系统对内存的划分和动态分配,就是内存管理的概念
2、内存管理的功能(思维导图)
内存空间分配与回收 | 由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦 |
内存空间的扩充 | 利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存 |
地址转换 | 把逻辑地址转换成相应的物理地址(这个过程称为地址重定位即三种装入方式,上面有) |
存储保护 | 保证各进程在各自的存储空间运行,互不干扰 |
3 、存储保护
法一:在cpu中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,cpu检查是否越界。
法二:利用重定位寄存器、界地址寄存器进行越界检查
4、内存空间的扩充 --图
1、覆盖技术 (已称为历史)
1.1 目的:是用来解决 “程序大小超过物理内存总和”的问题
1.2 思想:
- 将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存
- 内存中分为一个固定区和若干个覆盖区
- 常驻内存位于固定区,调入后就不再调出;不常用的段位于覆盖区,需要时调入内存,用不到时调出内存
2、交换技术
三、内存空间分配与回收
1、连续分配管理方式 --图
连续分配方式,是指为一个用户程序分配一个连续的内存空间。主要包括单一连续分配、固定分区分配和动态分区分配
- 内部碎片:分配给某进程的内存区域中,如果有些部分没有用上
- 外部碎片:是指内存中的某些空闲分区由于太小而难以利用
1.1 单一连续分配(早期的单道程序)
1.2 固定分区分配(多道程序系统)
操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配与回收。每个表对应一个分区,通常按分区大小排序。每个表包括对应分区的大小、起始地址、状态(是否已分配)
当某用户程序需要装入内存,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”
优点: | 实现简单,无外部碎片 |
缺点: | 1、当用户程序太大,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但又会降低性能 2、会产生内部碎片,内存利用率低
|
1.3 动态分区分配
- 动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是再进程装入内存时,根据进程的大小动态地创立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。
- 动态分区分配没有内部碎片,但是有外部碎片 。
系统使用什么样的数据结构记录内存的使用情况?
当很多个空闲分区都能满足需求,应该选择哪个分区进行分配呢?是最大分区,还是最小分区?所以就有了动态分区分配算法
1.3.1 首次适应算法
- 算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区
- 如何实现:空闲分区以低地址递增的次序排列。每次分配内存时顺序查找空闲分区链(空闲分区表),找到第一个满足要求的空闲分区
1.3.2 最佳适应算法
- 算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须连续的一整块区域,为了保证当“大进程”到来时能有连续的大片空间,尽可能留下大片的空闲区,因此优先适应更小的空闲区
- 如何实现:空闲分区按容量递增次序链接,每次分配内存时顺序查找空闲分区链(空闲分区表),找到大小能满足要求的第一个空闲分区
- 缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此会产生很多的外部碎片
1.3.3 最坏适应算法(又称最大适应算法)
- 算法思想:为了解决最佳适应算法的问题——留下太多难以利用的小碎片,可以再每次分配时优先使用最大的连续空闲区,这样分配后剩余空闲区就不会太小了,方便利用
- 如何实现:空闲分区按容量递减次序链接,每次分配内存时顺序查找空闲分区链(空闲分区表),找到大小能满足要求的第一个空闲分区
- 缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,但是会导致大的连续空闲区被快速用完,如果之后还有“大进程”到达,就没有内存分区可用
1.3.4 临近适应算法
动态分区分配算法比较
2、非连续分配管理方式
考虑支持多道程序的两种连续分配方式:
- 固定分区分配:缺乏灵活性,会产生大量的内部碎片,内存利用率很低
- 动态分区分配:会产生很多外部碎片,虽然可以用 “紧凑” 技术处理,但是时间代价很高
如果可以将一个进程分散地装入到许多不相邻的分区中,便可充分地利用内存,而无需在进行 “紧凑”。基于这思想便产生了 “非连续分配方式”,又称为 "离散分配方式",就是为用户进程分配的可以是一些分散的内存空间。
非连续分配管理方式根据分区的大小是否固定分为分页存储管理方式和分段存储管理方式。
分页存储管理方式中,又根据运行作业时是否要把作业的所有页面都装入内存才能运行分为基本分页存储管理方式和请求分页存储管理方式。
2.1 基本分页存储管理方式 --图
2.1.1 采用分页技术,如何实现地址的转换
2.1.2 页号、页内偏移量的计算
法一:
页号 = 逻辑地址 / 页面长度 (取整) | 页号 = 80 / 50 = 1 |
页内偏移量 = 逻辑地址 % 页面长度 (取余) | 页内偏移量 = 80 % 50 = 30 |
页面在内存中的起始位置:操作系统需要用某种数据结构记录进程各个页面的起始位置 | 一号页在内存中存放的起始位置是450 |
法二:根据逻辑地址结构计算,逻辑地址=【页号P,页内偏移量W】
2.1.3 为了能知道每个进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表
2.1.4 为什么每个页表项的长度是相同的,页号是 “隐含的” ?
2.2 基本地址变换机构 --图
通常会在系统设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。进程未执行时,页表的起始地址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把他们放到页表寄存器
注意:页面大小是2的整数幂(在上面地址转换的方法二说了原因)
设页面大小为L,逻辑地址A到物理地址E的变换过程如下:
1、用上述两种方法都可以得出页号 P 和 页内偏移量 W (如果用十进制手算,P=A/L、W=A%L;但是在计算机实际运行时,逻辑地址结构时固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)
2、根据页表寄存器的页表长度M,判断页号P是否越界,若P>=M,则产生越界中断,否在继续执行(注意:页号时从0开始的,而页表长度至少时1,因此P=M也会越界)
3、页表中页号P对应的页表项地址=页表起始地址+页号P * 页表项长度,取出该页表项内容b,即为内存块号
4、计算E = b * L + W,得到物理地址E区访问。(内存块起始地址就是内存块号*内存块大小或页面大小)
如果内存块号、页面偏移量时用二进制表示的,那么把二者拼接起来就是最终的物理地址了
注意区分(重要):
- 页表长度:指的时这个页表中总共有几个页表项(由页号和块号组成),即总共由几个页
- 页表项长度:指的每个页表项占多大的存储空间
- 页面大小:指的时一个页面占多大的存储空间
例子:
进一步探讨:
2.3 具有快表的地址变换机构
2.3.1 什么是局部性原理?
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量循环)
- 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问(因为很多数据在内存都是连续存放的)
在基本地址变换机构中,每次要访问一个逻辑地址,需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项。既然如此,可以利用这个特性来减少访问页表的次数。
2.3.2 什么是快表
快表,又称联想寄存器(TLB),是一种访问速度比内存快很多的高速缓冲存储器,用来存放当前访问的若干页表项,以加速地址转换的过程。与此对应,内存中多大页表常称为慢表。
引入快表地址变换过程
2.4 比较
2.5 两级页表
单级页表存在的问题
两级页表的原理和结构
如何实现地址的转换
第二个问题解决方案:虚拟存储技术(后面来详细说)
需要注意的细节
2.6 基本分段存储管理方式 --图
- 分段:类似于分页管理中的 “分页”,段表:类似于分页管理中的 “页表”,与分页最大的区别就是——离散分配时所分配地址空间的基本单位不同
- 进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址
- 内存分配规则:以段位单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻
2.6.1 逻辑地址结构
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成
2.6.2 段表
程序分为多个段,各段离散地装入内存,为了保证程序正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需要为每个进程建立一张段映射表,简称 “段表”
2.6.3 地址转换
2.6.4 分段、分页管理对比
2.7 段页式管理方式 --图