程序的运行速度受计算机内存(RAM)大小的影响。但技术上并不能拥有一块容量无限大、速度无限快、永久性的存储器,所以提出了「分层存储体系」的概念。
存储器类型 | 特征 | 由什么管理 |
---|---|---|
高速缓存 | 以 MB 为单位、昂贵且易失 | 硬件负责管理 |
内存 | 以 GB 为单位、价格适中且易失 | 存储管理器 |
磁盘 | 以 GB、TB 为单位,价格低廉且非易失 | 文件系统负责管理 |
存储管理器负责内存管理部分,内存管理主要包含管理内存和虚拟内存两部分,本文主要介绍内存管理相关的知识,包括:
- 存储管理器——管理内存
- 1.1 存储器是必须的吗?
- 1.2 存储器的抽象
- 存储管理器——虚拟内存
- 2.1 概念
- 2.2 虚拟内存做了什么工作?
1. 存储管理器——管理内存
1.1 存储器是必须的吗?
尽管当前的计算机都包括了存储管理器,但不包括的存储管理器就不能运行程序了吗?
答案是否定的,最简单的存储器的抽象就是让程序直接访问物理内存。比如:
MOV REGISTER1 1000 //将位置为 1000 的物理内存内容移动到 REGISTER1
但这种访问方式,在当前的计算中已经消失不见了,说明它肯定存在着不能满足用户需求的缺点:
- 用户程序能够访问内存所有的字节,系统运行的操作系统也加载在内存中,意味着用户程序可以轻易的破坏操作系统。
- 同时运行多个程序比较困难
注意: 计算机世界的发展总是倾向于重复的历史。 直接引用物理地址的方式在嵌入式系统和智能卡系统中还是很常见的,因为这些系统中所运行的程序都是事先确定的。
1.2 存储器抽象
1.2.1 抽象地址空间概念
地址空间是一个进程可用于寻址的内存的一套地址集合。地址空间的提出解决了两个问题保护和重定位。
- 保护:每个进程都有一个自己的地址空间,并且这个地址空间是独立于其他进程的地址空间。
- 重定位: 使用基址寄存器 + 界限寄存器进行地址的动态重定位。
如下图所示,程序的起始物理地址装载在基址寄存器,程序的长度被装载在界限寄存器,在每次程序访问内存时,CPU 硬件会把基址值 + 进程的地址,然后发送到内存总线。使用这种访问方式在完成物理地址重定位的同时能够有效的隔离不同程序的运行空间。
1.2.2 如何处理内存超载
如果所有的进程都需要一直保存到内存,那么内存的大小是远远不够的。比如,在 Windows 或 Linux 系统中,计算机完成引导后,会启动 40 ~ 60 个,其中小的程序可能需要几十兆的内存,大的程序需要几百兆甚至更多。将系统需要运行的程序使用的内存总量大于 RAM 的大小的情况,称为内存超载。
存在两种解决内存超载的方法:
-
交换——把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存储在磁盘上所以当它们不运行的时候,就不会占用内存。
-
虚拟内存——使进程可以基于连续完整的地址空间寻址,但实际上,它被分隔成为多个物理内存碎片,还有部分暂存在外部磁盘存储器上,在需要时进行数据交换。(第二部分详细说明)
-
上图展示了 A 换入-> B 换入 -> C 换入 -> A 换出 -> D 换入 -> B 换出 -> A 换入过程,展示了进程A、B、C、D 在内存中换入换出的过程。
-
上图 e 中 A 换出 D 换入后在内存中产生了空洞,空洞的产生会严重影响内存的有效使用,虽然可以通过内存紧缩(移动进程,将小空闲区合并成一大块),但内存紧缩需要大量的 CPU 时间。
让我们继续思考一个问题,当进程被创建或者换入时应该为它分配多大的内存?(进程的数据段、堆栈段是可以增长的,没办法准确的按需分配大小)
解决方案: 采用预分配的策略,换入或移动进程时为它分配一些额外的内存,当进程被换出到磁盘时,只交换进程实际上使用的内存的内容。
1.2.3 如何管理内存分配
存储器需要管理内存的使用情况,即哪些内存是空闲的?哪些内存是正在被使用的?有两种跟踪内存使用的方式,位图和空闲链表。(如下图所示,一段有 5 个进程和 3 个空闲区的内存,b 用位图的方式 1 表示占用, 0 表示空闲,c 用链表的方式)
位图:
- 分配单元对应于位图中的一位,分配单元越小,位图越大。分配单元不是整数倍的时候,会产生一定数量的内存浪费。
- 每次在位图中找出 K 个连续 0 的都必须扫描整个位图。
链表
- 可将上图 c 的内存使用的一个单链表分割成两个双向链表,一个用于记录已分配的内存,另一个用于记录空闲的内存。
- 分配内存的时候直接从空闲链表遍历,释放的时候相邻的空闲内存可以合并为一个更大的空闲内存。
2.存储管理器——虚拟内存
程序大于内存的问题,是没办法用单纯的「内存管理」来解决的。在没有虚拟内存的年代里,人们使用覆盖来解决这个问题。
覆盖:将程序分割成许多小的片段。
覆盖管理: 程序运行时首先加载覆盖管理模块,然后覆盖管理模块按顺序加载带运行的覆盖。
- 覆盖 0 执行结束后,加载的覆盖 1 可以选择加载在新的内存空间中或者加载在覆盖 0 的内存空间
- 复杂的覆盖管理,允许同时加载多个覆盖在内存中
注意:把一个大程序分割成小的、模块化的片段时非常耗时且容易出错的,所以随着计算机的发展,人们让系统使用「虚拟内存」来完成这项工作
2.1 概念
让我们再复习一次虚拟内存的概念加深一下印象。
虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成为多个块,每块称为一页。每一页又有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。
- 引用到在内存中地址空间,由硬件立刻执行映射。
- 引用到不在内存中的地址空间,由操作系统负责将缺失的部分装入内存并重新执行失败的指令。
总结: 虚拟内存适合在多道程序设计系统中使用,多个程序的片段同时保存在内存中,一个程序等待读入内存的时候,CPU 可以执行另一个程序。
2.2 虚拟内存做了什么工作?
虚拟内存实现了包括分页管理、页面置换、页面分配策略等工作。
2.2.1 分页管理
虚拟地址空间按照固定大小划分若干页面单元,物理内存中对应的单元称之为页框。
注意: 系统中常用的页大小一般为 512 字节到 64KB,常用的为 4KB。
分页管理实现两个功能:
-
访问已在内存中页面的物理地址,物理地址 = MMU(虚拟地址)(MMU 内存管理单元,把虚拟地址映射到物理地址。
-
访问不在内存中的页面的物理地址,操作系统产生缺页中断。
-
下图展示了虚拟地址 8196 (0010000000000100)到物理地址的转换,页以 4KB为单位
-
16 位的虚拟地址 = 4 位的页号 + 12 位的偏移量(可索引 0 - 4096)
-
0010(2) -> 页表[2] (110 1) -> 代表虚拟页中的页框号,最后一位代表物理页面是否在内存中,0 会引起缺页中断,1 直接查到的页框(物理页面号)复制到寄存器的高 3 位,加上虚拟地址中的低 13 位,构成 15 位的物理地址
-
110 + 000000000100 => 24580
注意:页表可以理解为一个函数——物理页的页框号 = f (虚拟页号)
-
-
分页过程中还有两个问题需要解决:
- 虚拟地址到物理地址的映射必须非常快
- 引入转换检测缓冲区 (TLB, 又称为相联存储器) 加速上述转换过程
- 虚拟地址空间很大,页表也会很大
- 引入多级页表、倒排页表的方案
2.2.2 页面置换
页面管理会产生缺页中断,这时候就可能需要将部分不在使用的页面换出到磁盘上,为缺页中的页面腾出空间来。页面置换有以下几种策略:
- 最优页面置换算法
- 最近未使用页面置换算法
- 先进先出页面置换算法
- 第二次机会页面置换算法
- 时钟页面置换算法
- 最近最少未使用页面置换算法
- 用软件模拟 LRU
- 工作集页面置换算法
- 工作集时钟页面置换算法
各种页面置换策略的对比:
2.2.3 分页策略
- 全局分配策略和局部分配策略
- 发生页面置换的时候,是选择当前程序以前加载到内存的页面进行置换,还是从所有程序已经加载到内存中页面选择一个
- 负载控制
- 所有进程组合的工作集超出内存容量的时候,将一部分进程换到磁盘上
- 页面大小
- 操作系统的页面大小是一个可选择的参数
- 共享页面
- 不同的用户同时运行同一个程序的时候,可以共享只读部分的页面
- 共享库
- 参照共享页面的思想,共享大型的库
- 内存映射文件
- 将一个文件映射到虚拟地址空间的一部分
- 清除策略
- 大多数分页系统有分页守护进程,定期检查内存的状态,空闲页过少的时候,会选择部分页面换出内存
- 虚拟内存接口
- 程序员可以对内存映射进行控制,并可以通过非常规的方法来增强程序的行为
以上总结摘录于《现代操作系统》存储管理一章,其中加入了个人理解,如有不正确的部分欢迎指正,万分感谢。