操作系统(三)- 存储管理
- [1. 内存的基础知识](#1. 内存的基础知识)
-
- [1.1 存储单元与内存地址](#1.1 存储单元与内存地址)
- [1.2 按字节编址和按字编址](#1.2 按字节编址和按字编址)
- [1.3 指令](#1.3 指令)
- [1.4 物理地址和逻辑地址](#1.4 物理地址和逻辑地址)
- [1.5 从写程序到程序运行](#1.5 从写程序到程序运行)
- [1.6 链接](#1.6 链接)
-
- [1.6.1 静态链接](#1.6.1 静态链接)
- [1.6.2 装入时动态链接](#1.6.2 装入时动态链接)
- [1.6.3 运行时动态链接](#1.6.3 运行时动态链接)
- [1.7 装入](#1.7 装入)
-
- [1.7.1 概念](#1.7.1 概念)
- [1.7.2 绝对装入](#1.7.2 绝对装入)
- [1.7.3 静态重定位](#1.7.3 静态重定位)
- [1.7.4 动态重定位](#1.7.4 动态重定位)
- [2. 内存管理的概念](#2. 内存管理的概念)
- [3. 内存空间分配 - 连续分配](#3. 内存空间分配 - 连续分配)
-
- [3.1 单一连续分配](#3.1 单一连续分配)
- [3.2 固定分区分配](#3.2 固定分区分配)
- [3.3 动态分区分配](#3.3 动态分区分配)
-
- [3.3.1 首次适应](#3.3.1 首次适应)
- [3.3.2 最佳适应](#3.3.2 最佳适应)
- [3.3.3 最坏适应](#3.3.3 最坏适应)
- [3.3.4 邻近适应](#3.3.4 邻近适应)
- [4. 内存空间分配 - 非连续分配](#4. 内存空间分配 - 非连续分配)
-
- [4.1 基本分页存储管理](#4.1 基本分页存储管理)
-
- [4.1.1 例题](#4.1.1 例题)
- [4.1.2 基本地址变换机构](#4.1.2 基本地址变换机构)
- [4.1.3 快表](#4.1.3 快表)
- [4.1.4 两级页表](#4.1.4 两级页表)
- [4.2 基本分段存储管理](#4.2 基本分段存储管理)
-
- [4.2.1 段表](#4.2.1 段表)
- [4.2.2 分段、分页管理的对比](#4.2.2 分段、分页管理的对比)
- [4.3 段页式存储管理](#4.3 段页式存储管理)
-
- [4.3.1 分页和分段的优缺点分析](#4.3.1 分页和分段的优缺点分析)
- [5. 内存空间的扩充](#5. 内存空间的扩充)
-
- [5.1 覆盖技术](#5.1 覆盖技术)
- [5.2 交换技术](#5.2 交换技术)
- [5.3 虚拟内存技术](#5.3 虚拟内存技术)
-
- [5.3.1 传统存储管理方式的特征、缺点](#5.3.1 传统存储管理方式的特征、缺点)
- [5.3.2 局部性原理](#5.3.2 局部性原理)
- [5.3.3 虚拟内存的定义和特征](#5.3.3 虚拟内存的定义和特征)
- [5.3.4 如何实现虚拟内存技术](#5.3.4 如何实现虚拟内存技术)
- [5.3.5 请求分页管理方式](#5.3.5 请求分页管理方式)
-
- [**① 请求分页与基本分页的区别**](#① 请求分页与基本分页的区别)
- [② 请求页表机制](#② 请求页表机制)
- [③ 缺页中断机构](#③ 缺页中断机构)
- [④ 地址变换机构](#④ 地址变换机构)
- [5.3.6 页面置换算法](#5.3.6 页面置换算法)
- [5.3.7 页面分配策略、抖动、工作集](#5.3.7 页面分配策略、抖动、工作集)
- [5.3.8 内存映射文件](#5.3.8 内存映射文件)
1. 内存的基础知识
内存(Memory),也称为主存储器或随机存取存储器(RAM, Random Access Memory),是计算机系统中用于临时存储数据和程序的硬件。内存是计算机系统的核心组件之一,其主要作用是在处理器执行任务时为其提供快速访问的数据存储空间。意思就是,我们写好的程序在运行时必定会被先加载到内存中,然后CPU从内存中拿取数据。
作用
- 存储正在使用的数据和程序 :
- 内存用于存储计算机当前正在运行的操作系统、应用程序以及这些程序所需要的数据。处理器可以快速访问这些数据,从而实现高效的程序执行。
- 提高数据访问速度 :
- 相比硬盘等外部存储设备,内存的读写速度要快得多。将数据从硬盘加载到内存后,处理器可以更快速地访问这些数据,减少延迟,提高系统性能。
- 支持多任务处理 :
- 通过内存,计算机可以同时运行多个程序(如浏览器、文字处理器、音乐播放器等)。操作系统会将每个程序的数据和状态存储在内存中,以便快速切换和处理不同任务。
- 缓存数据 :
- 内存还可以用作缓存,存储经常使用的数据和指令,减少处理器从较慢的存储设备读取数据的次数,从而进一步提升性能。
- 临时存储 :
- 内存用于存储临时数据,像是正在编辑的文档、未保存的文件、以及中间计算结果等。当程序关闭或计算机关机时,内存中的数据会被清空(因为内存是易失性存储器)。
特性
- 易失性(Volatile): 内存是易失性存储器,这意味着当电源关闭时,存储在内存中的数据将会丢失。这与硬盘等非易失性存储设备不同。
- 高速性: 内存的访问速度远快于硬盘等存储设备,使得处理器可以更快地获取和处理数据,从而提高整个系统的运行速度。
1.1 存储单元与内存地址
存储单元
- 概念:存储单元是计算机内存中最小的存储单位,用于存储一小部分数据。通常,一个存储单元可以存储1个字节(8位)的数据,但具体大小取决于计算机系统的设计。
- 作用:存储单元是内存的基本构成部分,每个单元保存一个小的数据块。计算机通过访问这些存储单元来读写数据,执行程序。
内存地址
- 概念:内存地址是用于标识和定位特定存储单元的唯一编号。每个存储单元都有一个唯一的内存地址,使得计算机能够精确地找到并访问存储在该单元中的数据。
- 作用:内存地址是访问内存中存储单元的桥梁。通过指定内存地址,计算机能够读取或写入对应的存储单元中的数据。
存储单元与内存地址之间的联系
- 一一对应关系:在内存中,每个存储单元都有一个唯一的内存地址与之对应。这意味着一个存储单元的地址是它在内存中的位置标识符。内存可以被视为一组连续排列的存储单元,每个单元按顺序编号,每个编号就是内存地址。
- 数据访问:当计算机需要访问某个数据时,CPU会通过内存地址找到对应的存储单元,然后进行数据的读取或写入操作。内存地址的唯一性和存储单元的固定性使得数据访问过程准确无误。
举例说明
假设计算机内存有4个存储单元,每个存储单元能够存储1个字节的数据:
- 存储单元 :
- 单元0:存储了字节数据
10101010
- 单元1:存储了字节数据
11001100
- 单元2:存储了字节数据
11110000
- 单元3:存储了字节数据
00001111
- 单元0:存储了字节数据
- 内存地址 :
- 单元0的地址为
0000
- 单元1的地址为
0001
- 单元2的地址为
0010
- 单元3的地址为
0011
- 单元0的地址为
如果CPU需要读取存储在单元2中的数据,它会使用地址 0010
来定位该存储单元,从而读取到数据 11110000
。
总结
- 存储单元是内存中实际保存数据的最小单元。
- 内存地址是定位和访问存储单元的唯一标识符。
两者的联系在于:每个存储单元都有一个对应的内存地址,计算机通过内存地址访问存储单元中的数据,这一机制使得数据存储和检索过程得以准确和高效地进行。
1.2 按字节编址和按字编址
按字节编址 和按字编址是两种不同的内存地址表示方式,它们决定了如何在内存中访问数据。下面是对这两个概念的详细解释:
按字节编址(Byte Addressing)
-
概念:按字节编址是指内存地址以字节为单位进行编号。每个内存地址对应一个字节的数据存储单元。
-
特点
- 每个内存地址只指向一个字节。
- 内存地址从0开始,依次递增,且每个地址增加1表示访问下一个字节。
- 这种方式常用于大多数现代计算机系统,因为它支持对内存中的任意字节进行精确访问。
-
举例
-
假设有一个32位系统,其中每个内存地址可以存储1个字节(8位)。如果有一个整型变量(占4字节)存储在内存地址
0x0000
,则其存储在内存中的位置如下:- 地址
0x0000
:存储第1个字节 - 地址
0x0001
:存储第2个字节 - 地址
0x0002
:存储第3个字节 - 地址
0x0003
:存储第4个字节
- 地址
-
按字编址(Word Addressing)
-
概念:按字编址是指内存地址以字(Word)为单位进行编号。一个"字"通常由多个字节组成(例如2个字节、4个字节或8个字节),具体取决于系统的架构。
-
特点
- 每个内存地址指向一个"字",而不是一个字节。
- 内存地址从0开始,依次递增,但每个地址增加1表示访问下一个"字"。
- 这种方式在一些早期计算机系统或特定的嵌入式系统中使用较多。
-
举例
-
假设有一个16位系统,其中每个内存地址存储1个"字"(即2个字节)。如果有两个字分别存储在内存地址
0x0000
和0x0001
,则其存储在内存中的位置如下:- 地址
0x0000
:存储第1个字(由2个字节组成) - 地址
0x0001
:存储第2个字(由2个字节组成)
- 地址
-
按字节编址与按字编址的区别
-
地址单位:
- 按字节编址:每个地址单位是1个字节。
- 按字编址:每个地址单位是1个"字"(多个字节)。
-
数据访问的粒度:
- 按字节编址:可以精确访问到每个字节,适合处理较小的数据或字节级别的操作。
- 按字编址:只能访问到"字"级别,适合于系统设计时将数据以"字"为基本操作单位的场景。
-
内存空间的表示:
-
在按字节编址的系统中,地址范围较大,因为每个字节都有一个独立的地址。
-
在按字编址的系统中,地址范围相对较小,因为每个地址代表一个较大的数据块。
-
应用场景
- 按字节编址:广泛应用于现代计算机系统中,包括PC、服务器、智能手机等,允许更灵活和精确的数据访问。
- 按字编址:通常应用在一些嵌入式系统或早期的计算机系统中,在特定情况下可以简化系统设计和操作。
总结
按字节编址和按字编址是两种不同的内存寻址方式,前者以字节为单位编址,后者以"字"为单位编址。现代计算机通常采用按字节编址方式,因为它更灵活、精确,适应性更强。
1.3 指令
CPU指令通常以二进制格式表示,可以由操作码和地址码组成。
操作码(Opcode):
- 操作码是指令中的部分,用于指定CPU要执行的操作类型,如算术运算、逻辑运算、数据传输等。操作码是指令中的一个字段,通常是固定长度的二进制值,代表特定的操作。
地址码(Address Code):
- 地址码是指令中的部分,用于指定操作数所在的存储位置。地址码可能直接表示内存地址,也可能是间接寻址模式下需要进一步计算的地址。在一些指令集架构中,地址码也可能指定寄存器或其他存储位置。
可以看以下代码示例
c
int a =2,b=3,c=1,y=0;
void main(){
y = a*b+c
}
以上代码在编译成机器码后可以假设为以下表格, 其中ACC为CPU中的寄存器,不要太过于纠结。
可见,我们写的代码要翻译成CPU能识别的指令。这些指令会告诉CPU应该去内存的哪个地址读/写数据这个数据应该做什么样的处理。在这个例子中,我们默认让这个进程的相关内容从地址#0
开始连续存放,指令中的地址参数直接给出了各个变量在主存中实际的物理存储地址。
1.4 物理地址和逻辑地址
物理地址(Physical Address)
- 定义:物理地址是实际的内存地址,用于标识内存中的具体位置。这是物理内存芯片上的地址,用于访问内存中的字节、字或其他数据单元。
逻辑地址(Logical Address)
- 定义:逻辑地址,也称为虚拟地址,是由CPU生成的地址,用于访问内存中的数据。这个地址是程序看到的内存地址空间。
在上一小节简单了解了指令的工作原理,有一个问题需要想想,我们假设相关进程的内容在主存上是从#0
开始存储的,但是一台计算机上往往不会只运行一个进程,如果是在分配以上程序的内存地址之前就有程序占据了#0
到#8
内存地址,且此时程序已经编译好了(意思就是指令就是上个例子中表格的指令了,这些指令操作的内存地址我们称为逻辑地址),那程序按照以上指令执行的话,必然是不正确的。
怎么解决以上问题的,我们继续往下看。
1.5 从写程序到程序运行
编译:由编译程序将用户源代码编译成若干个目标模块(编译就是把高级语言翻译为机器语言)
链接:由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块
装入(装载):由装入程序将装入模块装入内存运行
1.6 链接
1.6.1 静态链接
在程序运行之前,先将各个目标模块以及它们所需的库函数链接成一个完整的可执行模块,之后不再分开。
1.6.2 装入时动态链接
将各个目标模块装入内存时,边装入边链接的链接方式。
1.6.3 运行时动态链接
运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
1.7 装入
1.7.1 概念
在操作系统中,装入(Loading) 是指将程序从外部存储设备(如硬盘)加载到内存中准备执行的过程。装入是程序从静态文件变为可执行进程的关键步骤。
1.7.2 绝对装入
绝对装入(Absolute Loading)是操作系统的一种早期的装入技术。在绝对装入中,程序的每条指令和数据都具有一个固定的、绝对的内存地址。装入器在装入程序时,不需要进行地址重定位(relocation),直接将程序加载到预先指定的内存地址上。
特点
- 固定的内存地址 :
- 在绝对装入中,程序在编译或汇编时已经确定了每条指令和数据的内存地址。这些地址在程序运行时保持不变。
- 无地址重定位 :
- 由于内存地址是固定的,装入器不需要对程序进行地址重定位,装入过程相对简单。
- 依赖特定的内存位置 :
- 绝对装入要求程序在一个特定的内存地址运行。如果内存中该地址已经被其他程序占用,那么该程序无法被装入并执行。
- 编程复杂性 :
- 编写绝对装入程序需要对内存布局非常了解,因为程序员需要手动指定每个部分的内存地址,这使得程序开发和维护变得复杂。
缺点
- 灵活性差 :
- 绝对装入严重依赖程序的特定内存位置,这导致程序无法在不同的内存位置运行,限制了多任务操作系统的实现。
- 资源浪费 :
- 如果多个程序需要运行,操作系统需要确保每个程序使用不同的内存地址,这可能导致内存资源的浪费。
- 不适合现代系统 :
- 在现代操作系统中,内存管理需要动态分配、地址重定位和虚拟内存技术,绝对装入的固定地址机制不适合多任务、多用户和大内存环境。
使用场景
虽然绝对装入在现代操作系统中已经很少使用,但它在一些特定场景下仍然可能出现:
- 嵌入式系统 :
- 一些简单的嵌入式系统,内存资源有限且不需要复杂的内存管理功能,可能仍然使用绝对装入技术。
- 引导加载器(Boot Loader) :
- 操作系统的引导加载器在启动时通常会使用绝对装入方式,因为它需要在系统非常早期阶段将操作系统内核加载到内存中的特定位置。
- 早期计算机系统 :
- 在早期的计算机系统中,绝对装入是主要的装入方式,因为这些系统通常运行单一程序且内存资源有限。
1.7.3 静态重定位
静态重定位(Static Relocation)是一种在程序装入内存时进行地址重定位的技术。在静态重定位中,程序的内存地址在装入时被一次性确定,并在整个程序执行过程中保持不变。该技术通常用于早期的操作系统以及一些简单的嵌入式系统中。
工作原理
- 编译时生成相对地址 :
- 程序在编译或汇编时,所有的指令和数据地址都是相对于程序起始地址(相对地址)的。这些相对地址在程序中被标记出来,以便在装入时进行重定位。
- 装入时的地址重定位 :
- 当操作系统决定将程序装入内存时,首先为程序选择一个合适的基地址(即程序在内存中的起始位置)。
- 装入器根据这个基地址,将程序中的所有相对地址与基地址相加,生成实际的物理地址。这一过程称为静态地址重定位。
- 程序装入内存 :
- 完成重定位后,操作系统将程序的代码和数据装入内存中相应的位置。由于地址已经固定,程序在运行时无需再进行任何地址转换。
特点
-
一次性地址重定位:
- 静态重定位在程序装入时只进行一次地址转换。所有相对地址在装入后都变成了绝对地址,程序在运行过程中不再进行任何额外的地址转换。
-
内存地址固定:
- 程序一旦装入,内存地址在整个执行过程中保持不变。这意味着程序在同一时间内只能在一个特定的内存位置运行。
-
低运行时开销:
- 由于地址转换仅在装入时进行,静态重定位不会在程序运行过程中引入额外的开销。这使得静态重定位适用于对性能要求较高的系统。
优点:
- 简单:静态重定位技术相对简单,容易实现和管理。
- 低开销:在程序运行时没有额外的地址转换开销,执行效率高。
- 适合小型系统:对于不需要复杂内存管理的小型或嵌入式系统,静态重定位是一个有效的选择。
缺点:
- 灵活性差:程序在装入后地址固定,无法在不同内存位置运行,限制了内存的利用效率。
- 不支持动态内存分配:静态重定位不适用于需要动态内存管理的复杂系统,因为它不支持程序在运行时改变其内存地址。
- 内存浪费:当程序地址被固定后,如果内存中的其他部分无法利用,这可能导致内存浪费。
应用场景
- 早期操作系统 :
- 在早期的计算机操作系统中,静态重定位是一种常见的技术,因为当时的计算环境比较简单,内存管理功能有限。
- 嵌入式系统 :
- 一些嵌入式系统由于硬件资源有限且不需要复杂的内存管理,可能依然采用静态重定位。
- 单任务系统 :
- 在只需要运行单一任务的系统中,静态重定位可以简化操作系统的设计和实现。
1.7.4 动态重定位
动态重定位(Dynamic Relocation)是一种在程序执行过程中进行地址重定位的技术,允许程序在内存中随时移动,并且支持更灵活的内存管理。动态重定位通过硬件(如内存管理单元,MMU)和操作系统的协同作用来实现,使得程序能够在不同的物理内存位置执行,而无需进行重新装入。
工作原理
- 逻辑地址与物理地址的分离 :
- 程序使用逻辑地址(或虚拟地址)来引用内存位置,这些地址在编译时生成。逻辑地址与实际的物理内存地址无关。
- 动态重定位通过硬件设备(如MMU)将逻辑地址动态转换为物理地址,使得程序可以在任何物理内存位置执行。
- 地址转换(Address Translation) :
- 当程序访问内存时,CPU首先生成逻辑地址,然后通过动态重定位机制(通常是分页或分段机制)将逻辑地址映射到物理地址。
- 这种映射是通过一个叫做页表(Page Table)或段表(Segment Table)的数据结构来完成的,页表或段表记录了逻辑地址到物理地址的映射关系。
- 内存管理单元(MMU) :
- MMU是实现动态重定位的关键硬件组件,它在每次内存访问时,将逻辑地址动态地转换为物理地址。这种转换是实时的,并且对程序员透明。
- MMU支持分页、分段等机制,使得内存管理更加灵活和高效。
- 支持虚拟内存 :
- 动态重定位通常与虚拟内存结合使用。虚拟内存允许程序的逻辑地址空间大于物理内存,通过将不活跃的页面暂时交换到硬盘(即页面置换)来扩展内存容量。
- 操作系统根据需要将页面调入和调出内存,而动态重定位机制确保程序始终能正确访问其数据。
优点
- 灵活性高 :
- 程序可以在内存的任何位置运行,操作系统可以根据当前内存使用情况动态分配和调整内存区域,提高了内存的利用率。
- 支持多任务处理 :
- 多个程序可以共享相同的物理内存区域,而每个程序都认为自己占有独立的地址空间。这使得操作系统能够高效地管理多任务环境。
- 支持虚拟内存 :
- 动态重定位与虚拟内存机制相结合,可以使程序的地址空间大于实际物理内存,从而支持运行更大的程序和更复杂的应用。
- 程序位置独立性 :
- 程序员不需要关心程序在内存中的具体位置,操作系统会自动管理地址转换。这简化了编程和程序管理。
实现技术
- 分页(Paging) :
- 分页是一种将内存分成固定大小的块(页面)的技术,逻辑地址空间和物理地址空间都分成大小相等的页面。
- 页面表维护着逻辑页面到物理页面的映射,MMU根据页面表实时将逻辑地址转换为物理地址。
- 分段(Segmentation) :
- 分段将程序分为多个段,每个段表示程序的不同部分(如代码段、数据段、堆栈段)。每个段可以有不同的大小,并且独立地映射到物理内存。
- 段表记录每个段的基地址和大小,MMU根据段表将逻辑地址转换为物理地址。
- 分页与分段结合 :
- 在复杂的内存管理系统中,分页和分段可以结合使用,既提供了段的逻辑分组,又利用了分页的内存利用效率。
应用场景
- 现代操作系统 :
- 动态重定位是现代操作系统(如Windows、Linux、macOS)的核心技术,支持多任务处理、虚拟内存管理等高级功能。
- 虚拟机和容器 :
- 虚拟机和容器技术利用动态重定位来隔离和管理虚拟环境中的内存,使得多个虚拟机或容器能够共享物理硬件资源。
- 大规模分布式系统 :
- 在大规模分布式系统中,动态重定位允许操作系统在不同的物理机器之间动态分配内存,提高系统的灵活性和资源利用率。
2. 内存管理的概念
- 内存空间的分配与回收
内存空间的分配与回收是指在计算机程序运行过程中,如何为数据提供存储空间(分配)以及在数据不再需要时释放这些存储空间(回收)的过程。这个过程是计算机系统管理内存资源的核心部分,确保系统能够高效地使用内存,并避免内存泄漏或耗尽内存资源。
- 内存空间的扩充
内存空间扩充的概念是指在计算机程序运行过程中,当现有内存资源不够用时,通过增加可用内存来满足程序的需求。内存扩充通常涉及操作系统和应用程序层面的机制,以确保程序能够继续运行,而不会因为内存不足而崩溃。内存空间扩充的常见方法:覆盖技术、交换技术、虚拟内存技术
- 地址转换
地址转换通常指的是将虚拟地址转换为物理地址的过程。
- 内存保护
是操作系统提供的一项关键功能,旨在防止一个进程访问、修改或破坏另一个进程的内存空间,以及保护操作系统本身的内存空间。它确保每个进程只能访问自己分配的内存区域,从而提高系统的安全性和稳定性。
3. 内存空间分配 - 连续分配
3.1 单一连续分配
单一连续分配(Single Contiguous Allocation)是早期操作系统内存管理的一种简单方法,主要用于没有虚拟内存的计算机系统中。它的基本思想是将整个可用的内存分为两个部分:一部分给操作系统,另一部分给用户进程。用户进程的内存区域是连续的、单一的。
关键特点:
- 单一用户进程 :
- 在这种分配方式下,内存中一次只能有一个用户进程运行。这个进程会占用除操作系统保留区域之外的所有剩余内存。
- 内存分区 :
- 内存通常被分为两个部分:操作系统驻留部分和用户程序驻留部分。操作系统部分通常位于内存的低地址区域,而用户进程占用剩余的连续内存空间。
- 简单的内存管理 :
- 内存分配和管理非常简单,因为操作系统只需要处理一个用户进程的内存分配问题。内存分配的开始和结束都是连续的,没有内存碎片问题。
- 缺点 :
- 低效的内存利用: 由于一次只能有一个用户进程驻留在内存中,这种方法无法充分利用系统的内存资源,特别是在多任务系统中,显得非常低效。
- 内存浪费: 如果用户进程所需的内存远小于可用内存的大部分,那么剩余的内存就会被浪费。
- 没有保护机制: 在这种模型下,用户进程可能会不小心或恶意地覆盖操作系统的内存区域,导致系统崩溃。
实际应用:
单一连续分配通常用于简单、早期的计算机系统,例如一些早期的操作系统或嵌入式系统。这种方法几乎不再使用,因为它不能支持现代操作系统的多任务和内存保护要求。
示例:
假设系统内存为64KB,其中8KB用于操作系统,剩余的56KB用于单一的用户进程。当一个用户进程加载到内存时,它会占用从地址8KB到64KB的所有空间。该进程运行时,如果它需要更多的内存且超出56KB,可能会导致内存溢出。
3.2 固定分区分配
固定分区分配(Fixed Partition Allocation)是早期内存管理的一种方法,用于支持多任务环境中的内存分配。在这种方法中,物理内存被划分成若干个固定大小的分区,每个分区可以容纳一个进程。该方法的目标是通过允许多个进程同时驻留在内存中来提高系统的利用率。
操作系统需要建立一个数据结构--分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为"已分配"。
关键特点:
- 内存分区 :
- 物理内存被划分为若干个固定大小的分区,每个分区大小是预先定义的。这些分区可以是相同大小,也可以是不同大小。
- 单进程/分区 :
- 每个分区一次只能容纳一个进程,即一个进程分配到一个固定分区。当进程终止或被换出时,分区会被释放,可以用于其他进程。
- 内存分配的简单性 :
- 由于分区大小固定,内存分配比较简单。当一个进程需要加载到内存中时,操作系统会在可用的分区中找到一个大小合适的分区分配给它。
- 内存浪费(内部碎片) :
- 由于分区大小固定,如果进程所需的内存小于分区大小,剩余的内存就会被浪费,这种情况称为"内部碎片"。例如,一个100KB的分区中,如果进程只需要80KB,那么剩下的20KB将无法使用,造成内存浪费。
- 分区数量的限制 :
- 分区数量是固定的,意味着系统能同时驻留的进程数也是有限的。如果所有分区都被占用,新进程只能等待,直到有分区被释放。
- 不灵活性 :
- 固定分区分配的灵活性较差,因为分区大小和数量在系统启动时就已经确定,一旦设置就无法改变。这使得系统难以应对动态变化的负载需求。
示例:
假设有一台计算机的内存大小为1024KB,操作系统预留了128KB,剩下的896KB被划分为4个固定分区,每个分区大小为224KB。
- 分区1: 224KB
- 分区2: 224KB
- 分区3: 224KB
- 分区4: 224KB
当一个进程A需要加载时,如果它的大小为180KB,它会被分配到一个224KB的分区中,剩余的44KB将形成内部碎片,不能用于其他进程。
优缺点总结:
- 优点 :
- 分区管理简单,容易实现。
- 允许多个进程并发运行,提高了内存利用率。
- 缺点 :
- 内部碎片问题可能会导致内存浪费。
- 不灵活,无法动态调整分区大小,可能无法有效利用内存资源。
- 分区数量固定,限制了同时运行的进程数量。
3.3 动态分区分配
这种分配方式不会预先给进程分配内存,而是根据进程所需内存的大小来建立分区,使得分区的大小正好适合进程的需要。因此系统的分区的大小和多少是可变的。(eg:假设某计算机内存大小为64MB,系统区 8MB,用户区共 56MB...)
一、 系统要用什么样的数据结构记录内存的使用情况?
两种常用的数据结构:空闲分区表和空闲分区链
-
空闲分区表
每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息
-
空闲分区链
主要分以下部分组成
分区起始地址:
- 每个节点或条目通常包含一个指向空闲分区起始地址的指针。这使得操作系统能够快速找到和访问空闲内存块。
分区大小:
- 除了起始地址,链表中的每个节点还可能包含分区的大小信息。这有助于操作系统决定哪个空闲块可以满足当前的内存请求。
链表节点:
- 每个节点包含指向下一个空闲分区的指针。通过这些指针,操作系统可以遍历整个链表,找到合适的空闲分区。
二、当很多个空闲分区都能满足需求时应该选择哪个分区进行分配?
假如现在有一个进程5需要4MB的内存空间。应该用最大的分区进行分配?还是用最小的分区进行分配?又或是用地址最低的部分进行分配?
把一个新作业装入内存时,须按照一定的动态分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。由于分配算法算法对系统性能有很大的影响,因此人们对它进行了广泛的研究。分为四种分区算法:首次适应、最佳适应、最坏适应、邻近适应,具体是怎样实现的可以继续往下看。
三、如何进行分区的分配与回收操作?
-
分配
假设我们现在使用的是空闲分区表来记录内存的空闲情况
假设现在有一个新的进程需要4MB, 且使用的是首次适应的算法那结果将会如下。
假设现在有一个新的进程需要4MB, 且使用的是最佳适应的算法那结果将会如下。
-
回收(如下四种情况)
3.3.1 首次适应
工作原理
- 内存空闲块列表: 操作系统维护一个内存空闲块列表,这些空闲块代表可以分配的连续内存区域。空闲块通常按地址顺序排列。
- 分配内存 :
- 当一个新进程请求内存时,操作系统从空闲块列表的开始位置遍历,寻找第一个能够容纳该进程的内存块。
- 一旦找到合适的空闲块,操作系统就将这个块划分为两个部分:一部分分配给进程,另一部分保留为新的空闲块(如果分配后有剩余空间)。
- 该进程占用的内存块从空闲块列表中移除,或调整其大小以反映新的空闲区域。
- 释放内存 :
- 当进程结束时,操作系统将其占用的内存块重新标记为空闲,并将其合并到空闲块列表中。如果释放的内存块与现有的空闲块相邻,则系统会将这些块合并成一个更大的空闲块。
优点
- 简单性: 首次适应算法容易实现,因为它只需要顺序遍历空闲块列表即可找到适合的内存块。
- 快速响应: 因为它通常会很快找到合适的内存块,因此能够快速响应内存请求。
缺点
- 外部碎片: 首次适应可能会导致外部碎片(即许多小的、无法使用的内存空洞),因为它倾向于选择靠前的空闲块。
- 性能问题: 由于每次分配内存时都需要从头遍历空闲块列表,在空闲块列表很长时可能会导致性能下降。
在动态分区分配的首次适应算法中,适应性指的是系统对内存需求变化的响应能力。由于算法总是从头开始寻找可用块,因此它能够快速适应简单的需求变化,但在长期运行时,可能需要配合其他碎片整理策略,以减少外部碎片对系统性能的影响。
3.3.2 最佳适应
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当"大进程"到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区即,优先使用更小的空闲区。
如何实现:
工作原理
- 内存空闲块列表: 操作系统维护一个按大小排序(通常是按升序)的内存空闲块列表。每个空闲块代表一个可供分配的连续内存区域。
- 分配内存 :
- 当一个进程请求内存时,操作系统会遍历整个空闲块列表,寻找能够容纳该进程的最小空闲块。
- 找到最适合的空闲块后,将该块分配给进程,并更新空闲块列表。分配后的剩余空间(如果有)将作为一个新的空闲块继续保留在列表中。
- 释放内存 :
- 当进程释放内存时,操作系统将其占用的内存重新标记为空闲,并将其合并到空闲块列表中。如果释放的内存块与相邻的空闲块接壤,则操作系统会将它们合并成一个更大的空闲块。
优点
- 减少外部碎片: 通过选择最适合的空闲块,最佳适应算法能够最大限度地减少未使用的小内存空洞,减少外部碎片的产生。
- 提高内存利用率: 该算法有助于在内存使用时更加精确,避免较大的空闲块被小进程占用,从而提高整体内存的利用率。
缺点
- 算法复杂度较高: 由于需要遍历整个空闲块列表以找到最适合的块,最佳适应算法比首次适应算法的开销更大,尤其是在内存请求频繁的情况下。
- 分配效率较低: 在进行内存分配时,可能会花费较多时间在查找最适合的空闲块上,从而导致分配效率较低。
- 小碎片积累: 尽管最佳适应算法旨在减少外部碎片,但它可能会导致很多小碎片积累在内存的不同区域,这些碎片可能在将来无法有效利用。
适用场景
最佳适应算法适用于内存资源紧张且碎片管理要求较高的场景。在这些场景中,通过精确分配内存块,可以有效减少碎片,提高内存利用率,但同时需要接受分配和释放内存时可能产生的性能开销。
3.3.3 最坏适应
工作原理
- 内存空闲块列表: 操作系统维护一个内存空闲块列表,通常按块大小降序排列。列表中的每个条目代表一个连续的空闲内存区域。
- 分配内存 :
- 当进程请求内存时,操作系统会遍历空闲块列表,选择其中最大的空闲块来分配给进程。
- 将该空闲块分成两部分,一部分分配给进程,另一部分(如果有剩余空间)作为新的空闲块保留在列表中。
- 释放内存 :
- 当进程结束并释放其占用的内存时,操作系统将该内存块标记为空闲,并将其插入到空闲块列表中。如果释放的内存块与相邻的空闲块接壤,操作系统会将它们合并成一个更大的空闲块。
优点
- 减少小碎片: 最坏适应算法将内存请求分配到最大的空闲块中,这样剩余的空闲块更大,减少了产生无法使用的小碎片的可能性。
- 简单实现: 算法的实现较为简单,只需要找到最大的空闲块进行分配,适用于列表较短的情况。
缺点
- 可能导致较大的内存碎片: 因为它总是将进程分配到最大的空闲块,剩余的部分可能会很大,且在之后的内存分配过程中可能无法被有效利用。
- 降低内存利用率: 在高负载情况下,大块内存被分割成多个小块,可能导致内存利用率的降低。
适用场景
最坏适应算法通常适用于需要将小碎片集中到少数大块中的场景。它的目标是避免频繁产生的小碎片,但在内存需求复杂或多变的场景中,可能不太理想。
3.3.4 邻近适应
邻近适应算法(Next Fit)是动态分区分配内存管理中的一种算法,类似于首次适应算法(First Fit),但它在处理连续的内存分配请求时有一个关键的不同之处:它不是从空闲块列表的开头开始搜索,而是从上一次分配结束的位置继续向前搜索。
工作原理
- 内存空闲块列表: 操作系统维护一个空闲块列表,这些空闲块代表可供分配的连续内存区域。
- 分配内存 :
- 当进程请求内存时,邻近适应算法从上一次内存分配结束的位置开始搜索下一个适合的空闲块。
- 它会沿着空闲块列表依次检查,直到找到一个足够大的空闲块来容纳进程。如果找到,则分配内存块;如果列表走到末尾而未找到合适的空闲块,则从头开始继续搜索。
- 一旦找到合适的空闲块,将其分配给进程,剩余部分(如果有)将作为新的空闲块保留在列表中。
- 释放内存 :
- 当进程结束并释放内存时,操作系统将其占用的内存重新标记为空闲,并尝试将其与相邻的空闲块合并,更新空闲块列表。
优点
- 减少碎片化: 由于邻近适应算法不会总是从列表的开头开始搜索,而是从上一次分配的位置继续,这可以有效避免反复分配和释放导致的集中碎片化问题。
- 提高分配效率: 与首次适应算法相比,邻近适应算法在分配连续内存请求时效率较高,因为它避免了每次分配都从头遍历列表的情况。
缺点
- 外部碎片问题: 尽管邻近适应算法能够避免部分碎片化问题,但随着时间推移,仍可能导致外部碎片,尤其是在内存请求频繁变化的情况下。
- 低效分配: 如果分配点处于列表末尾,且空闲块无法满足请求,则需要回到列表开头重新遍历,可能导致分配效率降低。
适用场景
邻近适应算法适用于需要频繁连续内存分配的场景,在这种情况下,它比首次适应算法能更有效地利用内存资源。它在一些特定的高频内存分配场景下表现优异。
4. 内存空间分配 - 非连续分配
为用户进程分配的是一些分散的内存空间。
4.1 基本分页存储管理
分页存储是一种内存管理技术,用于将计算机的物理内存和虚拟内存结合使用,以更有效地管理内存资源。它通过将程序的虚拟地址空间分割成固定大小的块,称为页(page),然后将这些页映射到物理内存中的物理页框(page frame)上。分页存储的主要目的是通过虚拟内存来支持程序所需的更大地址空间,并提高内存使用效率。
页框和页框号
将实际的物理内存空间
分为一个个大小相等的分区,每一个分区就位一个"页框",每个页框都会有一个编号,即页框号,页框号从0开始编号。
页(页面)和页号
将进程的逻辑地址空间
也分为和页框大小相等的小块,每一个小块我们称之为"页"或者"页面"。每个页面都会有一个编号,我们称之为页号。页号也是从0开始编号的。
页框号和页号
从逻辑角度来看,无论是物理内存空间还是进程的逻辑地址空间,都可以类比为一个数组结构。而页框号和页号就像是数组的下标。所以操作系统中的页框号和页号都是逻辑上的概念,而不是在实际物理内存中存储的值。
页表和页表项
操作系统以页框为单位为各个进程分配内存空间,进程的每个页面分别放入一个页框中。并且进程中各个页面在实际内存中连续存放,也就是说进程的页面与内存的页框是有一一对应的映射关系的,用来存储此关系的结构我们称之为页表。而其中一条条映射关系我们可以理解为一个个页表项。关于页表和页表项有以下需要注意的几点。
- 一个进程都会对应一张页表。
- 页表不是直接存储在PCB(进程控制块)中,而是存储在内存中的某个位置,PCB 中只保存页表的指针 或地址
- 一级页表 通常需要存储在连续的内存空间中,因为它通常实现为一个线性数组。
- 进程中的每个页号都会对应页表中的一个页表项。
- 一级页表中,虽然在逻辑意义每个页表项会有页号和页框号的映射关系,实际内存中的页表是一个线性数组,而页号恰巧可以使用数组的下标来表示,所以内存中页表中的页表项只存储一个线性的页框号就可以了。
- 在一个进程中页号通常都是从0开始的,但是并不代表一个进程的逻辑地址是从0开始的。在这里我们先不展开讲。
4.1.1 例题
问题1
假设操作系统按照字节编址,物理内存大小为4GB,页面大小为4KB,则每一个页表项占用多少字节?假设进程A占用的内存空间大小为16MB,则其页表占用的内存空间大小是多少?
- 先求出物理内存总共分为多少个页框,4GB ÷ 4KB =2^32^ / 2^12^ = 2^20^,即总共2^20^个页框。
- 要表示 2^20^页框,至少需要 20 个比特位 。因为 20 个比特位可以产生2^20^ 种不同的组合,对应于每个页框的唯一地址。
- 且页框和页面大小肯定是相等的,则每个页表项需要足够的位数来表示 20 位的物理页框号,且计算机是按照字节编址的。所以20除以8,向上取整就为3。即3个字节,所以每个页表项至少占用3个字节。
- 求出进程A总共分为多少个页表项。16MB ÷ 4KB =2^24^ / 2^12^ = 2^12^,即总共2^12^个页面。一个页面对应一个页表项,则进程A对应的页表项的数量为2^12^个
- 根据页表项的数量和一个页表项占用的字节可以求出页表大小。2^12^ x 3B = 12KB。
问题2
将进程逻辑地址空间分页后,操作系统如何实现逻辑地址到物理地址之间的转换呢?
比如,已知操作系统按照字节编址,进程的逻辑起始地址为1024,每个页面的大小为4KB,页表如下图,请计算逻辑地址为14565的实际内存地址为多少?
- 因为进程的逻辑起始地址为
1024
,所以,进程逻辑地址14565
在计算页号时候需要减去1024
,得到地址13541
,我们需要根据逻辑地址13541
计算页号。 - 系统按照字节编址,且页面大小为4KB,每个页面会有 4 x 2^10^ = 4096 个内存地址。那逻辑地址
13541
对应第13541/4096 = 3
个页面(即页号为2),页内偏移量为 13541 % 4096 = 1253; - 根据页表我们可以知道页号为2对应的页框号为4,那么物理内存地址 = 页框号 × 每个页面的有多少个内存地址 + 偏移量 ,即4×4096+1253 = 17637。
结论1
页号 = 逻辑地址/页面大小,即取整。
页面偏移量 = 逻辑地址 % 页面大小,即取整。
页框号 = 需根据页表和页号查找。
页框内偏移量 = 页内偏移量。
物理地址 = 页框号 * 页面大小 + 页内偏移量。
结论2
在计算机内部,地址是用二进制表示的,如果页面大小 刚好是2的整数幂,则计算机硬件可以很快速的把逻辑地址拆分成(页号,页内偏移量)。
假设某计算机用32 个二进制位表示逻辑地址,页面大小为4KB = 4096B。
0号页的逻辑地址范围应该是 0~4095,用二进制表示应该是:
00000000000000000000 000000000000 ~ 00000000000000000000 111111111111
1号页的逻辑地址范围应该是 4096~8191,用二进制表示应该是:
00000000000000000001 000000000000 ~ 00000000000000000001 111111111111
2号页的逻辑地址范围应该是 8192~12287,用二进制表示应该是:
00000000000000000010 000000000000 ~ 00000000000000000010 111111111111
例1
逻辑地址2,用二进制表示应该是 00000000000000000000 000000000010
页号:2/4096 = 0 = 00000000000000000000,页内偏移量 = 2%4096 = 2 = 000000000010
例2
逻辑地址4097,用二进制表示应该是 00000000000000000001 000000000001
页号:4097/4096 = 1 = 00000000000000000001,页内偏移量 = 4097%4096 = 1 = 000000000001
总结 :如果每个页面大小为2^K^B,用二进制数表示逻辑地址则末尾K位即为页内偏移量,其余部分就是页号。
结论3
假设物理内存的地址也是用32个二进制位表示,由于内存块的大小=页面大小,因此:
0号页框的起始地址是00000000000000000000 000000000000
1号页框的起始地址是00000000000000000001 000000000000
2号页框的起始地址是00000000000000000010 000000000000
3号页框的起始地址是00000000000000000011 000000000000
假设通过查询页表得知1号页面存放的内存块号是9(1001),则9号内存块的起始地址=9*4096=00000000000000001001000000000000 则逻辑地址4097对应的物理地址=页面在内存中存放的起始地址+页内偏移量=(00000000000000001001000000000001)。
总结:如果页面大小刚好是2的整数幂,则只需把页表中记录的物理块号拼接上页内偏移量就能得到对应的物理地址。
妈的,真是天才。
结论4
页面大小是2的整数幂的好处是什么?
逻辑地址拆分的就更加迅速 。如果页面大小为2^K^,用二进制位表示逻辑地址,那么用末尾的K位即可表示页内偏移量,其余的部分就是页号。这样就不必进行逻辑运算从而提升了运行速度。
物理地址计算的就更加迅速。在上面我们已经得到了页号和页内偏移量了,所以我们只需要根据页号在页表中找到对应的页框号,然后将页框号和页内偏移量拼接在一起就可以得到一个实际的物理地址了。
4.1.2 基本地址变换机构
基本地址变换机构(Basic Address Translation Mechanism)通常指的就是内存管理单元(MMU, Memory Management Unit)。
基本地址变换机构可以将进程的逻辑地址转换为内存中的物理地址。
通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。
进程未被调度时候,页表的起始地址和页表长度会被存储在进程的PCB中。当进程被调度时,操作系统内核会把它们放在页表寄存器中。
案例1
设页面大小是L,且是2的整数幂。逻辑地址A到物理地址E的变换过程如下。
①计算页号P和页内偏移量W( 如果用十进制数手算,则 P=A/L,W=A%L;但是在计算机实际运行时,逻辑地址结构是固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)。
②比较页号P和页表长度M,若P≥M,则产生越界中断,否则继续执行。(注意:页号是从0开始的,而页表长度至少是1,因此P=M 时也会越界)。
③页表中页号P对应的页表项地址 = 页表起始地址F + 页号P * 页表项长度,取出该页表项中的内容b,即为页框号。
④计算 E = b*L+W,用得到的物理地址E 去访存。
案例2
例:若页面大小为 1KB,页号2对应的页框号为8,将逻辑地址 2500 转换为物理地址。
①计算页号、页内偏移量。页号= 2500/1024=2;页内偏移量 = 2500%1024= 452
②根据题中条件可知,页号2没有越界,其存放的页框号为8
③物理地址 = 8 * 1024 + 425 = 8644
案例3
假设操作系统按照字节编址,物理内存大小为4GB,页面大小为4KB,总共有2^20^个页框,每个页表项的大小至少为3B。各个页表项会连续的存放在内存中,如果该页表在内存中的起始地址为X,则M号页对应的页表项是存放在内存地址为X+3*M。
一个页面的大小为4KB,则每一个页面可以存放4096/3 = 1365个页表项,但是这个页面会剩余4096%3 = 1B的内部内存碎片。
4.1.3 快表
快表,又称联想寄存器 (TLB, translation lookaside buffer),是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表。
虽然快表的访问速度很快,但是高速缓存很贵,所以是不能将整个快表都放在高速缓存中的。
案例1
- CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
- 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
- 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)。
案例2
例:某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时 1us,访问一次内存耗时 100us。 若快表的命中率为 90%,那么访问一个逻辑地址的平均耗时是多少?
(1+100) * 0.9 + (1+100+100) * 0.1 = 111us
有的系统支持快表和慢表同时查询,那就是另一种结果了
(1+100) * 0.9 + (100+100) * 0.1 = 110.9us
若未采用快表机制,访问内存中的数据需要
100 + 100 = 200us
以下是左图是不支持快表和慢表同时查询,右图是支持 快表和慢表同时查询
基本地址变换机构和具有快表的地址变换机构的区别
4.1.4 两级页表
单级页表中存在什么问题
假设某计算机系统按字节寻址,支持32位的逻辑地址,采用分页存储管理,页面大小为4KB,页表项长度为 4B。
- 因为页面大小为4KB,即2^12^B,所以页内地址需要用12位表示,剩下的20位可以用来可以表示页号。那么用户进程最多有2^20^个页面。
- 相应的,一个进程对应的页表中也会有2^20^个页表项,所以页表需要一块2^20^ * 4B = 2^22^ B的连续的内存来存储该页表,也就需要连续的2^22^B / 2^12^B = 2^10^ 个页框存储该页表。这显然违背了我们离散型存储的思想。
- 根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。
根据以上案例我们可以总结出以下问题:
问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。
问题一,我们可以考虑将长长的页表进行分组,使每个页框刚好可以放入一个分组。(因此每1K个连续的页表项为一组,每组刚好占一个内存块,再将各组离散地放到各个内存块中)
另外,要为离散的表再创建一张页表,称为页目录表,或称为外层页表。
例题
在多级页表中,当某一级的页表大小超过一个页面时,该级页表也会被分页管理,需要进一步进行分页处理。
某系统按照字节编址,采用40位逻辑地址,页面大小为4KB,页表项大小为4B,假设采用纯页式存储,则页内偏移量为多少位,需要采用几级页表?
-
因为页内偏移量表示的是一个页面内的地址范围,既然页面大小为 4KB,那么从0到 2^12^−1 的字节地址都在同一个页面内。用二进制来表示这个范围,需要 12 位。
-
40位逻辑地址可以代表2^40^B的内存空间,一个页面大小为4KB = 2^12^B,那么总共需要分为2^40^B / 2^12^B = 2^28^ 页,即页表中页表项总共有2^28^个,即页表总共需要28位表示。而当某一级的页表大小超过一个页面时,也需要进一步进行分页处理,则每个页面的页表项数量 = 页面大小 / 页表项大小 = 4KB / 4B = 2^10^ 个,则证明每级页表最多只能用10位表示。页表总共需要位数(28) ÷ 每级页表最多能使用位数(10) = 页表级数(2.8)。即共需要三级页表,一级为8位,二级为10位,三级为10位。大致如下图。
4.2 基本分段存储管理
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。
内存分配规则:以段为单位进行分配,每个段在内存中占据连续的内存空间,但各段之间可以不相邻。
优点:按照逻辑功能模块划分,用户编程更方便,程序的可读性高。
分段系统的逻辑地址结构(段名)由段号和段内地址(段内偏移量)组成。
段号的位数决定了每个进程最多可以分为多少段。
段内地址的位数决定了每个段的最大长度是多少。
在上图中,若系统是按照字节寻址的,则段号占16位,因此在该系统中,每个进程最多有2^16^ = 64K个段。
段内地址占16位,因此每个段的最大长度是2^16^ = 64KB。
4.2.1 段表
程序分为多个段,各段离散的装入内存,为了保证程序能够正常有序的执行,就必须能从物理内存中找到各个逻辑段的位置。为此,需要为每个进程建立一张段映射表,简称段表。
- 每个段对应一个段表项,其中记录了每个段的段长,每个段在内存中的起始位置(基址)。
- 各个段表项的长度是相同的。例如在某系统按照字节寻址,内存采用分段式管理。逻辑结构为段号占用16位,段内地址占用16位,因此需要用16位表示段长。若系统的物理内存为4GB,则需要用32位表示基址。因此每个段表项占用16+32 = 48位,即6B。由于段表项的长度是相同的,所以段号是可以隐藏的,不占用内存空间。若段表存放的起始地址为M,则K号段对应的段表项存放的地址为M+6*K。
寻址过程
4.2.2 分段、分页管理的对比
页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页管理是系统上的需要,完全是系统行为,对用户是不可见的。
段是信息的逻辑单位。分段的目的主要是为了满足用户的需求。一个段通常包含着一组属于一个逻辑模块的信息。分段是对用户可见的,用户编程时需要显示的给出段名。
页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序
分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
分页的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,又要给出段内地址。
分段比分页更容易实现内存共享和保护
不能被修改的代码称为纯代码或可重入代码。这样的代码是可以共享的。可以修改的代码或者可以说是资源是不能共享的(比如代码中有很多的变量,各个进程并发的同时访问可能会造成数据的不一致)。
需要几次访存?
分页(单级页表):第一次访存--查内存中的页表,第二次访存--访问目标内存单元。总共两次访存
分段:第一次访存,查内存中的段表,第二次访存--访问目标内存单元。总共两次访存
与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。
4.3 段页式存储管理
4.3.1 分页和分段的优缺点分析
段页式管理
段号的位数决定了进程最多可以分为多少段。
页号的位数决定了每个段最大有多少页。
页内偏移量决定了页面的大小,页框的大小。
在上述例子中,若系统是按照字节寻址的,段号占用16位,因此在该系统中,每个进程最多有2^16^个段。
页号占用4位,则每个段最多有2^4^个页面。
页内偏移量占用12位,因此每个页面的大小为2^12^ B=4KB。
5. 内存空间的扩充
内存空间扩充的概念是指在计算机程序运行过程中,当现有内存资源不够用时,通过增加可用内存来满足程序的需求。内存扩充通常涉及操作系统和应用程序层面的机制,以确保程序能够继续运行,而不会因为内存不足而崩溃。
内存空间扩充的常见方法:覆盖技术、交换技术、虚拟内存技术
5.1 覆盖技术
覆盖技术的核心思想是将程序分成多个模块(或段),这些模块在运行时根据需要加载到内存中,而不是一次性将整个程序加载到内存。通过在内存中动态地加载和卸载这些模块,操作系统可以在有限的内存空间中执行较大的程序。
早期的计算机内存很小,比如IBM推出的第一台PC机最大只支持1MB大小的内存。因此经常会出现内存大小不够的情况。
后来人们引入了覆盖技术,用来解决"程序大小超过物理内存总和"的问题。
覆盖技术的思想:将程序分为多个段,常用的段常驻内存,不常用的段在需要时调入内存。
需要常驻内存的段放在固定区,调入后就不再调出(除非运行结束)。
不常驻内存的段放在覆盖区,需要用到时调入内存,用不到时调出内存。
缺点
必须由程序员声明覆盖结构,操作系统完成自动覆盖,对用户不透明,增加了用户变成负担。覆盖技术只用于早期的操作系统,已经成为历史。
5.2 交换技术
内存空间紧张时,系统将内存中某些进程暂时换出到外村,把外存中某些已经具备运行条件的进程换入到内存(进程在内存和磁盘之间动态调度)。比如一些交互式的进程优先级要高于后台批处理进程,这种情况就可以把批处理进程暂时换出到外存并将其PCB放在挂起队列中,把外存中处于可运行的交互式进程换入内存。
-
应该在磁盘的什么位置保存被换出的进程
具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式(学过文件管理章节后即可理解)。总之,对换区的I/O速度比文件区的更快。
-
什么时候应该交换
交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程,如果缺页率明显下降,就可以暂停换出。
-
应该换出哪些进程
可优先换出阻塞进程;可换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间。
5.3 虚拟内存技术
虚拟内存技术是一种内存管理机制,使操作系统能够为每个进程提供一个连续的虚拟地址空间,而不必与物理内存的实际布局严格对应。虚拟内存技术允许程序运行时使用的内存地址与实际物理内存地址分离,从而提供了更高的内存管理灵活性和程序隔离性。虚拟内存技术为每个进程提供一个独立的虚拟地址空间。这些虚拟地址空间的大小通常远大于物理内存的实际容量。例如,一个程序可以有一个4GB的虚拟地址空间,即使系统的实际物理内存只有2GB。虚拟地址空间的大小取决于操作系统和硬件架构的支持。
5.3.1 传统存储管理方式的特征、缺点
一次性
作业必须一次性全部装入内存后才能开始运行。这会造成两个问题:1、作业很大时,不能全部装入内存,导致大作业无法运行。2、当大量作业要求运行时,由于内存无法容纳所有的作业,因此只有少量作业才能运行,导致多道程序并发度下降。
驻留性
一旦作业调入内存后,将一直驻留在内存,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行。这就导致了内存中会驻留大量的,暂时用不到数据。导致了内存空间的浪费。
而以上问题都是可以通过虚拟内存技术进行解决的。
5.3.2 局部性原理
-
时间局部性
如果系统执行了程序中的某段代码或者某条指令,那么在不久后这条指令或者这段代码很有可能会再次执行,如果数据被访问过,不久后该数据很可能会再次被访问。(因为代码中存在着大量的循环)
-
空间局部性
一旦程序访问了某个存储单元,在不久后,其附近的存储单元也很有可能被访问。(因为程序中很多数据在内存中都是连续存放的,并且程序的指令也是顺序的在内存中存放的。)
5.3.3 虚拟内存的定义和特征
基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分装到外存,这样并不需要将程序的所有的数据装入到内存才能执行。
在程序执行过程中,当访问的信息不再内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
若操作系统内存不够时,由操作系统将内存中暂时用不到的数据调出到外存。
在操作系统的管理下,在用户看来,程序可用的内存要比实际内存要大得多,这就是虚拟内存技术。
虚拟内存有一下三个主要特征:
多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。
5.3.4 如何实现虚拟内存技术
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此虚拟内存的实现需要建立在离散分配的内存管理方式基础上。
两者的主要区别
在程序执行过程中,传统的管理方式是一次性将数据全部加载到内存中。虚拟存储管理是需要时才将数据从外存中加载到内存中,内存不够用时,将暂时不需要的数据从内存换出到外存。
5.3.5 请求分页管理方式
① 请求分页与基本分页的区别
- 页面加载方式
- 基本分页存储管理:
- 在基本分页中,整个进程的所有页面都需要事先装入内存。如果进程有多个页面,则每一个页面必须先装入内存才能执行程序。
- 一旦内存装满,如果需要执行新的程序,系统可能会通过替换策略(如FIFO、LRU)将一些页面换出。
- 请求分页存储管理:
- 请求分页采用按需加载的方式。页面只有在进程实际需要时(即发生页面缺失时)才被加载到内存中,这有效节省了内存空间和减少不必要的页面装载。
- 当需要访问的页面不在内存时,会触发页面错误(Page Fault),然后系统会将缺失的页面从外存中加载到内存中。
- 内存使用效率
- 基本分页存储管理:
- 内存使用效率较低,因为每次进程执行时都需要将其所有页面加载到内存,即使有些页面不被使用,也会占据内存空间。
- 可能会浪费大量的内存资源,特别是当进程的页面很大时。
- 请求分页存储管理:
- 内存使用效率较高,因为只有真正需要的页面才会被加载到内存中。通过按需加载,减少了不必要的内存消耗。
- 提高了内存的利用率,特别适合内存资源有限的系统。
- 页面错误处理
- 基本分页存储管理:
- 在基本分页中,如果进程所需的页面不在内存中,会直接发生缺页错误,需要进行页面置换。
- 页面错误发生频率较低,因为一般情况下,所有页面已经在进程开始前加载完毕。
- 请求分页存储管理:
- 在请求分页中,页面错误是一种常见现象。因为只有当页面被访问时才会加载,因此当进程首次访问某个页面时,通常会发生页面错误。
- 页面错误会触发操作系统的页面调度程序,将缺失的页面从外存调入内存。
- 程序启动时间
- 基本分页存储管理
- 由于所有页面在进程开始时必须全部装入内存,因此启动时间较长。
- 请求分页存储管理
- 请求分页可以显著缩短进程的启动时间,因为进程启动时不需要所有页面都加载到内存中,进程可以在部分页面装入后立即开始执行。
- 外存空间的使用
- 基本分页存储管理
- 依赖操作系统的页面置换机制,当内存不足时将某些不常用的页面移到外存中,重新装入时需要额外的管理操作。
- 请求分页存储管理
- 请求分页广泛使用外存作为页面存储的扩展。当内存中没有某些页面时,可以从外存中请求加载。
- 更好地管理了外存和内存的交互,外存中的页面可以随时被加载和置换。
- 页面替换策略
- 基本分页存储管理:
- 页面替换的次数相对较少,因为在进程执行前,通常会尽量确保所需的页面都已装入内存。
- 请求分页存储管理:
- 页面替换策略非常重要,因为频繁的页面错误会导致系统经常需要从外存调入新的页面,因此请求分页通常结合页面替换算法(如 LRU、FIFO)来提高性能。
区别总结
特性 | 基本分页存储管理 | 请求分页存储管理 |
---|---|---|
页面加载方式 | 预先加载所有页面 | 按需加载页面 |
内存使用效率 | 较低,可能浪费内存 | 较高,按需分配内存 |
页面错误频率 | 较低 | 较高(首次访问页面时) |
程序启动时间 | 较长 | 较短(只加载部分页面) |
外存使用 | 可选用于替换页面 | 经常与外存交互,按需加载 |
页面替换策略 | 替换次数较少 | 页面替换频繁,需要高效策略 |
总的来说,请求分页管理通过按需加载页面和灵活的内存管理,优化了系统资源的使用效率,特别适合多任务或内存资源紧张的环境。
② 请求页表机制
请求分页与基本分页相比,请求分页需要知道要用的页面有没有调入内存,就算是没有调入内存,也需要知道页面在外存中的位置。
当内存不够时,操作系统需要实现页面置换。而在页面置换之前,操作系统需要确认内存中的页面有没有被修改过,如果没有修改过,自然也就不用再进行置换。如果修改过,需要进行置换。因此操作系统需要记录页面修改状态。
③ 缺页中断机构
- 如果内存中有空闲块
- 如果内存中没有空闲块
缺页中断是因为当前执行的执行想要访问的目标页面没有调入内存而产生的,因此属于内中断。
一条指令在执行的时候,有可能会产生多次中断。(例如copy A to B,就是将逻辑地址A中的数据复制到逻辑地址B中,而A,B属于不同的页面且这两个页面恰巧都不在内存中,这样的话就会产生两次中断。)
④ 地址变换机构
请求分页与基本分页的主要区别在于
在程序执行过程中,所访问的信息不在内存中时,操作系统会负责将所需信息从外存加载到内存。
当内存空间不够时,操作系统负责将暂时用不到的且已经修改的页面换出内存。
补充细节,在上图中有以下补充细节
①只有写指令访问内存时,才需要修改 "修改位"。并且修改时只需要修改快表中的数据,只有将快表项删除时才需要将数据写会慢表。
②和普通中断处理一样,缺页中断同样需要保存CPU现场。
③需要结合具体的页面置换算法决定换出哪一个页面。
④换入换出页面都需要启动慢速的I/O操作,可见如果换入换出太频繁,就会有很大的开销。
⑤页面调入内存后,需要修改慢表,同时将表项复制到快表中。
5.3.6 页面置换算法
-
最优页面置换算法(Optimal Replacement,OPT)
每次选择淘汰的是永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
例如:假设系统为某进程分配了三个内存块,并考虑到有以下页面号引用串
7,0,1,2,0, 3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
虽然最优页面置换算法可以保证最低的缺页率,但实际上,只有在进程执行过程中才能知道接下来会访问到的是哪一个页面。操作系统无法提前预判页面的访问序列,因此此算法无法实现。
-
先进先出算法(First In First Out,FIFO)
每次选择淘汰的页面是最早进入内存的页面。
实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时,选择对头页面即可,队列的最大长度取决于操系统为进程分配了多少个内存块。
例:假设系统为进程分配了三个内存块,而进程总共4个页面。且进程有以下引用串。
3,2,1,0,3,2,4,3,2,1,0,4
假设此时增加内存块为4个,正常应该完全满足内存需求。但是缺页次数反而增多了
这就是Belady异常
,为进程分配的物理内存块数变多时,缺页次数不减反增的异常现象。
只有FIFO算法才会产生被Belady异常。另外,虽然此算法实现简单,但是性能差。因为最先进入内存的页面也有可能经常访问。
-
最近最久未使用算法(Least Recently Used)
每次被淘汰的都是最近未使用的页面。
实现方法:使用页表项中的访问字段记录该页面自上次被访问以来的时间t。当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面。
此算法性能虽然很好,但是需要专门的硬件支持,算法开销大。
-
时钟页面置换算法
时钟置换算法是一种性能和开销较均衡的算法,又称CLOCK算法,或最近未用算法 (NRU, Not Recently Used)
简单的CLOCK算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链按指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检查页的访问位如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK 算法选择一个淘汰页面最多会经过两轮扫描)。
下图为时钟页面置换算法的一个例子。这里有 12个逻辑页面。以A到L,它们被组织成一个环形链表的形式,每个页面类似于钟面上的一个整点时间。每个页面旁边的数宇,表示其访问位的值。假设在当前状态下,指针所指向的是页面C。然后发生了一个缺页中断,需要把一个页面淘汰出局。根据时钟页面置换算法的思路,首先考察页面C的访问位,发现它的值等于1,这说明它在内存的这段时间内,曾经被访问过。当然,具体被访问了多少次,是什么时候被访问的,这些都不知道,无法获取这些信息。因为访问位只有一位,它的值要么是1要么是0。而对于 LFU 算法和 LRU 算法,这些信息是知道的。在LFU 算法中,有一个计数器,来记录页面的访问次数。而在 LRU 算法中,每当一个页面被访问时,就会调整它在队列当中的位置,把它排在最前面。
由于页面C的访问位的值为1,因此就不能把它淘汰出局,而是把它的访问位的值设置为0,然后往下移动一格,继续考察页面D。结果发现D 的访问位的值也等于1,所以不能把它淘汰出局,而是把它的访问位的值设置为0,然后再往下移动一格,继续考察页面E。结果发现E的访问位的值等于0,因此就选中它来作为被淘汰的页面,把新的页面 M 装人它所在的物理页面当中,然后把指针往下移动一格。
-
改进型的时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/O操作。这就是改进型的时钟置换算法的思想。修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过且被修改过。
算法规则:将所有可能被置换的页面排成一个循环队列
第一轮:从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
第四轮:若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描
总结来说,这种算法的基本原则就是优先空闲且没有被修改过的的内存块,然后是空闲但被修改后的内存块。
只需要一轮扫描的情况
需要两轮扫描的情况
第一轮扫描没有找到(0,0)的内存块。
第二轮扫描,会将扫描过的内存块的访问位设置为0。直到扫描到第一个(0,1)的内存块为止。
需要扫描三轮的情况
第一轮扫描没有找到(0,0)的内存块。
第二轮扫描,会将扫描过的内存块的访问位设置为0。且没有找到(0,1)的内存块。
第三轮扫描,找到(0,0)的内存块。
需要四轮扫描的情况
第一轮,循环找(0,0)的内存块,但是没有扫描到(0,0)的内存块。
第二轮,循环找(0,1)的内存块,但是没有找到。并且把扫描过的内存块的访问位设置为0。
第三轮,循环找(0,0)的内存块,但是没有扫描到(0,0)的内存块。
第四轮,查找第一个(0,1)的帧用于替换。
5.3.7 页面分配策略、抖动、工作集
-
驻留集
指请求分页存储管理中给进程分配的物理块的集合。在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。
考虑一个极端情況,若某进程共有100个页面,则该进程的驻留集大小为100时进程可以全部放入内存,运行期间不可能再发生缺页。若驻留集大小为1,则进程运行期间必定会极频繁地缺页。
若驻留集太小,就会导致频繁缺页,这样系统就会花费大量的时间处理缺页,实际用于进程推进的时间就会很少。
若驻留集太大,就会导致系统多道程序并发度降低,资源利用率变低。
所以应该为驻留集选择一个合适的大小。
-
页面分配、置换策略
页面分配
固定分配:系统为每个进程分配一组固定大小的内存块,在程序运行期间不再改变。即驻留集的大小不变。
可变分配:系统先为进程分配一定数目的内存块,在程序运行期间,可以根据情况做适当的增加或减少。即驻留集的大小可以改变。
置换策略
局部置换:发生缺页时,只选择进程自己的内存块进行置换。
全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别进程持有的内存块置换到外存后,再分配给缺页进程。
固定分配局部置换:
系统为每个进程分配一定数量的内存块,在程序整个运行期间都不在改变,当进程发生缺页现象时候,只能在分配给本进程的内存块中的页面选取一页换出到外存,然后再需要的页面从外存调入到内存。这种策略的缺点是:很难再刚开始运行时就确定内存所需驻留集大小。采用这种策略的系统可以根据进程大小,优先级,或者是根据程序员给出的参数来确定为一个进程分配的内存块数。
可变分配全局置换:
进程刚刚开始运行时,系统会分配一定数量的内存块,并且操作系统会维护一个空闲物理内存的队列。当进程发生缺页时,从空闲队列中选择一块空闲的物理内存进行分配。如果操作系统中没有空闲的物理内存块后,可以选择一个未锁定(有一些内存块有可能存储内核态的数据,这些数据是不能被置换出内存的,且这些内存块是被标识为锁定的状态)的页面换出到外存,然后再将此物理块换出到外存。采用这种策略时,只要进程发生缺页,都将获得新的物理块,仅当物理块用完时,系统才会选择一个未锁定的页面调出。被选择调出的页可能是任何进程的页面,这样被调出页面的进程的可用物理页面会减少,缺页率会增加。
可变分配局部置换:
进程刚刚开始运行时,系统会分配一定数量的内存块。当进程发生缺页现象时,只允许在进程自己的物理块中选择一个换出。如果在进程运行的过程中频繁发生缺页现象,系统会为该进程多分配几个内存块,直至缺页率适当降低为止。反之,如果进程的缺页率一直很低,系统可适当减少分配给该进程的物理块。
可变分配全局置换:只要进程发生缺页就为其分配新的物理块。
可变分配局部置换:要根据进程发生缺页的频率来动态地增加或者减少进程的物理块。
-
调入页面的时机
预调页策略:根据空间局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更加高效。但如果预先调入的页面是在大多数情况下都没有被调用过的页面,这种策略又是低效的。因此可以预测不久后可能访问到的页面,将他们预先调入到内存,但目前的预测成功率只有50%左右。因此这种策略主要用于进程的首次调入,由程序员指定应该先调入哪些页面。
请求调入策略: 进程在运行期间发现有缺页时,才将所缺页面调入内存。由这种策略调入内存的页面一定会被访问到。但是由于一次只能调入一页,而每次调入内存都要进行I/O操作,因此开销会比较大。
-
从何处调页
(1) 系统拥有足够的兑换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
(2) 系统缺少足够的兑换区空间:凡是不会被修改的数据都是从文件区直接调入到内存,这些数据在用完后不必被再写回到外存,下次再用到时,再从文件区调入即可。对于可能被修改的部分,换出时需写回到磁盘的对换区,下次需要时再从兑换区调入。
(3) UNIX方式:在进程运行之前,所有的数据都是存储在文件区的。在进程运行时会将需要的数据从文件区加载到内存,当这些数据暂时用不到时,会被换出到磁盘的对换区,需要时会再从对换区再载入数据到内存。
-
抖动现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)
-
工作集
指在某段时间间隔内,进程实际访问页面的集合。
工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如:窗口尺寸为5,经过一段时问的监测发现某进程的工作集最大为3,那么说明该进程有很好的局部性,可以给这个进程分配3个以上的内存块即可满足进程的运行需要。一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页。
拓展:基于局部性原理可知,进程在一段时间内访问的页面与不久之后会访问的页面是有相关性的。因此,可以根据进程近期访问的页面集合(工作集)来设计一种页面置换算法------选择一个不在工作集中的页面进行淘汰。
5.3.8 内存映射文件
内存映射文件是一种将文件或设备映射到进程的虚拟内存空间的技术,使得应用程序可以像访问内存一样直接访问文件内容。这种机制允许文件的部分或全部内容被映射到内存中,从而提供高效的文件读写操作。其原理和页表的原理很类似。
如果有一个100页的文件,内存映射文件通常会维护100个映射关系。每个映射关系对应文件中的一页,操作系统通过这些映射关系来管理文件的访问。
主要特点
- 直接内存访问:通过映射,程序可以直接读取和写入文件数据,无需显式的读写操作。
- 按需加载:只有在访问特定数据时,相关文件页才会被加载到内存。
- 共享内存:多个进程可以映射同一文件,实现在进程间共享数据。
- 性能优势:内存映射文件通常比传统的文件 I/O 操作更快,特别是在处理大文件时。
工作原理
- 操作系统为文件创建一个映射,允许文件的内容在内存中直接访问。
- 通过页表管理虚拟内存与物理内存之间的映射关系。
- 数据的读取和修改会反映在内存中,操作系统会在适当的时机将更改写回文件。
应用场景
- 大文件处理,如视频、数据库和日志文件。
- 进程间通信,通过共享映射区域进行数据交换。
- 高性能计算,需要频繁访问文件的场景。