OS2️⃣内存管理:虚拟内存
内存(RAM):计算机的重要资源,需要重点管理。
- 理想情况:私有、容量无限大、速度无限快、价格低廉。
- 目前技术无法同时满足以上特点,因此诞生了分层存储器体系。
1、无存储器抽象
早期的存储器没有抽象,程序直接访问物理内存
通常,同一时刻只允许一个进程运行。
- 用户输入命令后,OS 把需要的程序从磁盘复制到内存中执行;
- 进程运行结束后,OS 等待新的命令;
- 收到新的命令后,OS 载入新的程序(覆盖上一个程序)。
没有抽象,如何运行多个程序?
- 交换(swap):OS 将当前内存的程序保存到磁盘文件中,将新的程序读入内存中执行。
- 分块:将内存划分为多个同等大小的块,基于绝对物理地址进行内存寻址。
2、地址空间
内存管理需要解决的问题:保护、重定位。
2.1、概念
地址空间:每个线程独有的,独立于其它线程的,可用于寻址内存的一套地址集合。
- 物理地址空间:硬件支持的地址空间。
- 逻辑地址空间:进程所拥有的内存范围。
如何实现每个进程独有地址空间?
动态重定位:将进程的地址空间映射到物理内存的不同部分。
- 经典实现:每个 CPU 配置基址寄存器和界限寄存器。
- 过程:当进程访问一个内存地址(x)时,CPU 硬件会将基址值加到内存地址上,并检查是否超过界限寄存器的值,是则产生异常。
- 缺点:每次访问内存都要进行加法、比较运算。
2.2、内存使用管理
通常,OS 有两种方法跟踪内存使用情况。
- 位图:将内存划分为多个同等大小的分配单元,每个单元对应位图中的一位。
- 链表:定义一个链表节点(包含空闲区/进程标志,起始地址,长度,next 域),按照地址顺序形成链表。
2.3、内存碎片
内存碎片(空闲区):无法被利用的内存空间,由于内存分配或交换导致。
- 外部碎片:分配单元之间。
- 内部碎片:分配单元内部。
2.4、内存分配方法
假设
- 进程和空闲区按照地址顺序记录在链表。
- 存储管理器已知要为进程分配的内存大小。
常见的内存分配方法
过程 | 优点 | 缺点 | |
---|---|---|---|
首次适配(first fit) | 沿着链表从头搜索,找到首个合适的空闲区(足以容纳当前程序) | 最简单,速度快 | 外部碎片 |
下次适配(next fit) | 在首次适配的基础上,记录当前位置。之后每次从记录位置开始搜索 | 优化 first fit 的查找效率 | 同上 |
最佳适配(best fit) | 沿着链表从头搜索,找到能够容纳进程的最小空闲区 | 内存需求小则高效 | 更小的外部碎片,重分配慢 |
最差适配(worst fit) | 沿着链表从头搜索,找到能够容纳进程的最大空闲区。 | 内存需求适中则搞笑 | 外部碎片,重分配慢 |
快速适配(quick fit) | 为常用大小的空闲区维护单独的链表 |
2.5、碎片整理方法
内存紧缩:重置程序以将进程尽可能向内存的一侧移动,合并碎片。
- 要求:所有程序动态可重置。
- 问题:只能在程序处于等待状态时进行,CPU 开销大。
2.6、技术
在内存无法满足进程使用的情况下,可考虑覆盖和交换技术。
2.6.1、覆盖(overlay)
场景:程序太大
思路:将程序按照逻辑结构,划分为功能独立的若干个模块,不会同时执行的模块共享同一块内存空间。
- 常用功能(必要)的代码和数据需要常驻内存;不常用功能(可选)可存放在外存中,需要时调入内存。
- 存在相互调用关系的模块才需要同时装入内存;不存在相互调用关系的模块可以按时间顺序执行,相互覆盖。
缺点:
- 编程复杂:需要确定模块之间的覆盖关系,手动划分为若干个功能模块。
- 耗时:覆盖的本质是将模块从外存读入内存,牺牲时间换取空间。
2.6.2、交换(swapping)
场景:程序太多
- 思路:将内存中暂时无法运行的程序整个保存到外存中(swap out),将外存中某个进程的地址空间读入到内存(swap in)
- 问题:
- 交换时机:内存不足。
- 交换区大小:足以存放所有用户进程的所有内存映像。
- 换入时的重定位:换出后再换入后未必是原来的内存位置,最好采用动态地址映射。
3、虚拟内存
内存管理的另一个问题:软件的膨胀。
软件大小的增长速度,远远超过了存储器容量的增长速度。
3.1、非连续内存分配
- 连续内存分配:OS 为进程分配内存时,分配一块连续的地址空间。
- 缺点:容易产生内存碎片,内存利用率低。
- 非连续内存分配:为进程分配的地址空间是非连续的。
- 优点:提高内存利用率,允许共享代码和数据,支持动态加载、动态链接。
- 缺点:建立虚拟地址和物理地址的映射难度大。
3.2、虚拟内存
3.2.1、内存映射
进程执行指令时会产生虚拟地址
-
没有虚拟内存:虚拟地址被 OS 直接送到内存总线上访问相应的物理内存。
(虚拟地址 = 物理地址)
-
使用虚拟内存:虚拟地址被 OS 送到 MMU(内存管理单元),MMU 将虚拟地址映射为物理内存地址,再将映射之后的地址送到内存总线。
3.2.2、基本思想
每个程序拥有独立的虚拟地址空间,地址空间被分割成多个页(page)。
- 每个 page 有连续的地址范围。
- page 被映射到物理内存。
- 并非程序的所有 page 都在内存中才可运行程序。
当程序访问地址空间时,根据其是否在物理内存中:
- 是:执行内存映射并访问。
- 否:产生缺页中断,由 OS 将缺失的页装入物理内存,重新执行导致缺页中断的失败的指令。
3.3、分页(Paging)
- 分页
- page(页):虚拟地址空间被划分成大小相等的 page。
- page frame(页帧,页框):物理内存空间被划分成大小相等的 Page frame。
- 关系
- 虚拟内存的 page 映射到物理内存中的 page frame。
- page 和 page frame 的大小通常相等。
- page 是连续的,page frame 是非连续的(减少碎片产生)。
- 虚拟内存空间 > 物理内存空间,并非所有 page 都有对应的 page frame(物理内存有限)。
3.4、页表(Page Table)
页表
- 维护 page 和 page frame 的映射关系。
- 每个进程有一个独立的页表,属于程序运行状态,会动态变化。
3.4.1、映射过程
- 将虚拟地址划分为虚拟页号(pageNo)和偏移量(offset)。
- 通过 pageNo 计算页表项(也可直接将虚拟页号作为页表的 index)
- 通过页表项确定 pageNo 对应的页帧号(frameNo),得出真实的物理地址(offset + pageNo)。
3.4.2、页表项结构
说明 | 备注 | |
---|---|---|
页帧号 | 虚拟页号映射的结果 | |
驻留位 | 标识页表项是否有效(即对应的页面是否在内存中) | 0-无效:访问时会引起缺页中断 |
保护位 | 页面允许的访问类型 | 使用一位:0-可读写,1-只读 使用三位:读、写、执行 |
访问位 | 标识页面是否被读/写过 | 作为缺页中断时页面置换的辅助 |
修改位 | 标识页面是否被修改过(脏页) | 1-脏:必须写回磁盘 |
3.5、分页性能
分页机制需要考虑的性能问题
- 时间:
- 每次访问内存需要进行至少 2 次内存访问(访问页表项、访问内存)
- 从虚拟地址到物理地址的映射速度必须快。
- 空间:
- 每个进程需要一个独立的页表。
- 虚拟地址空间增大会导致页表增大。
3.5.1、加速分页过程
① 转换检测缓冲区(TLB)
转换检测缓冲区(Translation Lookaside Buffer)
又称为 快表,相联存储器(associate memory)
- 说明:
- 在分页机制下,进程需要先访问页表才能实现内存访问,性能下降至少一半。
- 局部性访问:在进程运行期间,只访问少部分页面(而不是全部),因此只有少数页表项会被反复读取。
- TLB:为 CPU 设置的一个小型硬件设备(TLB),通常位于 MMU 中,包含少量表项。
- 表项结构:虚拟页号、页帧号(frameNo)、驻留位、修改位、保护位。
- 工作原理:
- 进程将虚拟地址发送给 MMU。
- MMU 将虚拟页号与 TLB 所有表项同时匹配(并行)
- 有效匹配且不违反保护位:将 frameNo 从 TLB 中取出,无需访问页表。
- 有效匹配但违反保护位:产生保护错误。
- 无效匹配:进行正常的页表查询,并将新找到的页表项取代 TLB 中的一个表项,TLB 中被淘汰的页表项的修改位会被写回内存。
② 软件 TLB 管理
- 过去:对 TLB 的管理和失效处理都由 MMU 硬件实现,只有当发生缺页中断时才会由软件(OS)处理。
- 现在:由软件实现几乎所有的页面管理。
- TLB 表项被 OS 装载。
- 发生 TLB 访问失效时,由 OS 取代 MMU 硬件完成失效处理(页表查询,淘汰一个 TLB 表项,装载新项,再次执行产生出错的指令)
失效
软失效 | 硬失效 | |
---|---|---|
含义 | 页面不在 TLB 中,但在内存中 | 页面不在 TLB 中,也不在内存中 |
解决 | 更新 TLB(无需产生磁盘 I/O) | 进行一次磁盘 I/O,装入页面 |
缺页错误
次要缺页 | 严重缺页 | 段错误 | |
---|---|---|---|
含义 | 页面在内存中,但未记录在该进程的页表中 | 页面不在内存中 | 进程访问了非法地址 |
性质 | 软失效的一种 | 硬失效 | 程序错误 |
3.5.2、针对大内存的页表
① 多级页表
将虚拟页号分为 n 个部分,每个部分对应一个页表(形成树结构)。
(虚拟地址 = n级页号 + 偏移量)
以二级页表为例,虚拟页号分为 2 个部分。
-
对应关系:一级页号对应一级页表,二级页号对应二级页表。
-
查找过程:
- 通过一级页号查一级页表,得到二级页表的起始地址。
- 通过二级页号查二级页表,得到页帧号。
- 页帧号 + 偏移量 = 物理地址。
-
好处:节省内存空间。
(若 k 级页表的某一项的 resident bit = 0,则以该项为根节点的子树都不需要存储页表项。)
② 倒排页表
页表:每个页(page)对应一个页表项,通过虚拟页号查找页帧号。
倒排页表:每个页帧(page frame)对应一个页表项,通过页帧号寻找虚拟页号。
- 优点:节省大量内存空间
- 缺点:从虚拟地址到物理地址的映射困难。
- 虚拟页号无法作为倒排页表的索引。
- 当进程 n 访问虚拟页面 p 时,必须搜索整个倒排页表。