北航OS课程笔记--三、内存管理
期末必考地址转换和自映射大题。
内存管理
链接与装载
ELF中的部分段落:
在ELF头之外:

注意程序中的局部变量不在ELF中,在栈上。
存储管理基础
存储硬件:
- SRAM:读写速度快,生产成本高,多用于Cache
- DRAM:读写速度较慢,生产成本低,多用于主存
- ROM:非易失性
存储结构:
- 寄存器—内存—外存
- 寄存器—缓存—内存—外存
单道程序&多道程序
单道程序:
- 单道程序环境下,整个内存里只有两个程序:一个用户程序和操作系统;操作系统所占的空间是固定的。
- 因此,可以将用户程序永远加载到一个地址,即用户程序永远从同一个地方开始运行。
- 静态地址翻译:在程序运行之前就计算出所有物理地址。
- 有地址独立,且有地址保护。
多道程序:
- 分区式分配空间:把内存分为一些大小相等或不等的分区,每个应用程序占用一个或几个分区。操作系统占一个分区。
- 适合多道程序系统和分时系统,支持多个程序并发执行,但难以进行内存分区的共享。
- 分区分配方法 ↓
固定式分区&可变式分区
固定(静态)式分区:
- 把内存划分为若干个固定大小的连续分区
- 易于实现,但有内碎片
可变(动态)式分区:
- 分区的边界看移动,即分区的大小可变
- 没有内碎片,有外碎片。
闲置空间的管理
管理内存时OS需要知道内存空间有多少空闲,跟踪方法有:
-
位图表示法
- 给每个分配单元赋予一个二进制数位,用于记录该分配单元是否闲置。
- 空间开销固定,不依赖于内存中的程序数量;
- 时间开销低
- 没有容错能力:如果一个分配单元的标志位为1 ,不能确定是否因错误变成1
-
链表表示法
- 将分配单元按是否闲置链接起来。
-
- 空间开销取决于内存中的程序数量
- 时间开销高
- 有一定容错能力:链表中有被占空间和闲置空间的表项,可以相互验证。
内存管理算法
-
首次适应算法
每个空闲区按其在存储空间中地址递增的顺序连在一起,在为作业分配存储区域时,从这个空闲区域链的始端开始查找,选择第一个满足请求的空白块。
-
下次适应算法
把空闲区构成一个循环链表,每次查找时从上次查找结束的地方开始,找一个满足请求的空白块。
-
最佳适应算法
分配时总是选择大小最接近于作业所要求的存储区域。
-
最坏适应算法
总是选择最大的空闲区,效率略高于最佳适应算法。
上述均为基于顺序搜索的动态分区分配算法,一般只适合于较小的系统。
大中型系统采用了基于索引搜索的动态分区分配算法。
-
快速适应算法
-
伙伴系统(Linux使用)
- 从一个大分区分裂而来的两个分区称为一对伙伴;
- 每次分配空闲分区的大小必为2的幂次;
- 某一块被释放时,只和它原先的伙伴块进行合并;如果两个存储块大小相同、地址也相邻,但不是同一个大块分裂出来的,就不会被合并。
系统中的碎片
内存中无法被利用的存储空间被称为碎片。
-
内部碎片:指分配给作业的存储空间中未被利用的部分。(已分配未利用)
-
外部碎片:系统中无法利用的空闲分区。是造成内存系统性能下降的主要原因,可以被整理后清除。
-
紧凑技术:消除外部碎片。
-
通过移动作业,把多个分散的小分区拼接成一个大分区。
-
紧凑时机:找不到足够大的空闲分区,且总空闲分区容量可以满足作业要求时。
-
实现支撑:动态重定位,作业在内存中的位置发生变化时,对其地址加以修改。
动态重定位指硬件在每次内存访问时动态实现逻辑地址到物理地址的转化(需要额外的硬件支持);
静态重定位则是在进程装入时,一次完成所有地址的转化(不需要额外的硬件支持);
-
分区分配
- 可重定位分区分配
- 多重分区分配
覆盖与交换
在多道程序环境下用来扩充内存的两种方法,可以解决在小的内存空间运行大作业的问题。
-
覆盖:把一个程序划分为一系列功能相对独立的程序段,让执行时不要求同时装入内存的程序段组成一组(称为覆盖段),共享主存的同一个区域。
程序段先保存在磁盘上,当有关程序段的前一部分执行结束,把后续程序段调入内存,覆盖前面的程序段。
缺点:对用户不透明,增加用户负担
不透明:可见的,需要关心的
透明:不可见的,不需要关心的
-
交换:把暂时不用的某些程序及数据部分或全部从主存移到辅存中去;接着把指定程序或数据从辅存中读到相应的主存中,让其在系统上运行。
缺点:增加处理机开销。
-
两者的区别:
-
覆盖可以减少一个程序运行所需的空间。交换可让整个程序暂存于外存中,让出内存空间。
-
覆盖是由程序员实现的,操作系统根据程序员提供的覆盖结构来完成程序段之间的覆盖;
交换技术不要求程序员给出程序段之间的覆盖结构。
-
覆盖技术主要对同一个作业或程序进行;交换主要在作业和程序之间进行。
-
页式内存管理
基本思想:把一个逻辑地址连续的程序分散存放到若干不连续的内存区域内,并保证程序的正确执行;
这样既可以充分利用内存空间,又可减少移动带来的开销。
属于固定式分区。
纯分页系统
不具备页面对换功能,不支持虚拟存储器功能。
又称基本分页存储管理方式。
在调度一个作业时,必须把它的所有页一次装到主存的页框里;如果页框数不足则必须等待。
优点:
- 没有外碎片,每个内碎片不超过页大小
- 程序不必连续存放
- 便于改变程序占用空间的大小
缺点:
- 程序全部装入内存;有内碎片。
基本概念
-
页面:把每个作业的地址空间分成一些大小相等的片,称之为页面(Page)或页,各页从0开始编号。
虚拟地址分页→页面/页
-
页框:物理地址分片,与页面大小相同,这些片称为存储块,或称页框,同样从0开始编号。
物理地址分页→页框/存储块
-
页表:使页面对应到页框,每个进程一张;每一页在页表中都对应有一个页表项。页表项中存储页框号、标记位、脏位、访问保护等。
-
页面大小计算:略,同计组计算。
-
地址变换——页表查找
将“页表始地址”与“页号和页表项长度的乘积”相加,得到该页表项在页表中的位置;于是可从页表项中得到该页的物理块号,将之装入物理地址寄存器中。
-
MMU:地址翻译器。
二级页表
当逻辑地址空间很大时,划分的页比较多,页表很大,占用的存储空间大;所以有了多级页表。
二级页表将页表再分页,离散地将各个页表页面存放在不同的物理块中,同时建立一张外部页表(页表的页表)用以记录页表页面对应的物理块号。
正在运行的进程,必须把外部页表调入内存,同时动态导入内部页表。只将当前所需的一些内部页表装入内存,其余部分再根据需要陆续调入。
二级页表大小最优情况:二级页表的页表项能正好填满一个页面

每次调入一个内部页表时,其大小应该正好是一个页框大小=页面大小=8字节;
页表项1字节,页面大小8字节→一个页面(页框)存储8个页表项;
内部页表的页号:2^3,3位
外部页表的页号:6-3=3位
一级页表:外部
二级页表:内部
虚拟页号位数=内部页表页号位数+外部页表页号位数
最佳情况:内部页表大小=页面大小,
→内部页表存储的页表项 = 页面大小/页表项大小
内部页表页号位数=
内部页表存储的页表项
快表TLB
页表机制带来的严重问题:内存访问效率的严重下降。

有效内存访问时间(一级页表)
=TLB命中率*访问TLB时间+(1-TLB命中率)*(访问TLB时间+访问内存时间)+访问内存时间(访问所需字节)
哈希页表
即杂凑页表。
常用于处理超过32位地址空间。
根据虚拟页号的哈希值来访问页表。哈希页表的每一表项都包括一个链表的元素,这些元素哈希成同一位置(要处理碰撞)。
每个元素包括:虚拟页号、所映射的页框号、指向链表中下一个元素的指针。
反置页表
一般的页表是根据进程的逻辑页号来组织的;
反置页表则依据该进程在内存中的页框号来组织(按页框号排列)。
表项内容:逻辑页号P和隶属进程标识符pid。
原来:根据逻辑页号找到对应的页框号
反置页表查询流程:用进程标识符和页号去检索反置页表。
反置页表的大小只与物理内存的大小相关,与逻辑空间大小和进程数无关。
采用反置页表的系统很难共享内存,因为每个物理帧只对应一个虚拟页条目。
段式内存管理
基本原理
方便编程:通常一个作业是由多个程序段和数据段组成的,用户一般按逻辑关系对作业分段,并能根据名字来访问程序段和数据段。
信息共享:共享是以信息的逻辑单位为基础的。页是存储信息的物理单位,段是信息的逻辑地址。要求:代码是可重入的。
可重入代码
又称为纯代码,指多次并发调用时能安全运行的代码。
要求:不能使用全局/静态变量;代码不能修改代码本身;不能调用其他不可重入代码。
一个段可以定义为一组逻辑信息,每个信息的地址空间是由一些分段组成的,每段由段号,且都是一段连续的地址空间。
地址变换
段表:
-
段表记录了段与内存位置的对应关系
-
段表保存在内存中。
-
段表的基址和长度由段表寄存器给出
|段表始址|段表长度|
-
访问一个字节的数据/指令需要访问内存两次:段表一次,内存一次。
-
逻辑地址由段和段内地址组成。
|段号|段内地址|
地址变换过程:
- 逻辑地址段号与段表长度比较:
- 段号>段表长(段表项个数),越界中断
- 未越界,则根据段表始址和该段段号,计算出该段对应段表项的位置,再从段表项中读取该段在内存中的始址。
- 检查段内地址d,是否超过该段段长SL
- 超过则越界中断
- 未超过则将该段基址与段内地址相加,获得要访问的内存物理地址。

分段管理的优缺点
优点:
- 易于实现段的共享和段的保护
- 更好支持动态的内存需求
缺点:
- 处理机要为地址变换花费时间;要为段表提供附加的存储空间。
- 为满足分段的动态增长和减少外碎片,要采用内存紧凑 的技术手段。
- 在辅存中管理不定长度的分段比较困难(交换)。
- 分段的最大尺寸受到主存可用空间的限制。
分页与分段的比较
-
分页作业的地址空间是一维的线性地址空间;
分段作业的地址空间是二维的。
页式存储的虚拟地址是连续的,在获取地址时只需要虚拟地址就能自动分割出页号和页内偏移量;
但段式存储的虚拟地址不是连续的,需要给出段号,再给出段内偏移。
-
页是信息的物理单位,大小固定
段是信息的逻辑单位,长度不定
-
分页是系统对于主存的管理
分段是用户可见的,可以在编程时确定,也可以在编译时划分。
段页式管理
基本思想:用分段方法来分配和管理虚拟存储器,同时用分页方法来分配和管理实存储器。

-
先把用户程序分成若干段,赋段名;
-
再把每个段分成若干个页
-
地址结构:
| 段号S | 段内页号P | 页内地址W |
-
系统中设段表和页表:每个进程一张段表,每个段一张页表。读一次指令或数据需要访问内存三次。
-
段表含段号、页表始址、页表长度;
页表含页号、页框号。
地址映射:
-
比较段号和段表长(段表项个数),若段号大则产生越界中断。
-
段表始址+段号,获得该段表项在段表中位置。
段表中的“段”=段表项。
-
比较 段内页号 和 该段的页表长度,若页号大则产生越界中断。
-
利用页表始址与页号获得该页表项在页表中的位置。
-
取出该页的物理块号,与页内地址拼接得物理地址。
虚拟内存管理

局部性原理
-
空间局部性:
当前指令和邻近的几条指令,当前访问的数据和邻近的数据集中在较小区域内。(比如数组操作)
-
时间局部性:
一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一段较短时间内。(比如程序中的循环结构)
虚拟存储的特征
- 离散性:物理内存分配的不连续,虚拟地址空间使用的不连续
- 多次性:作业被分成多次调入内存,所以虚拟存储器具备了逻辑上扩大内存的功能,是虚拟存储器最重要的特征。
- 对换性:允许在作业运行过程中进行换进、换出。
- 虚拟性:允许程序从逻辑的角度访问存储器,而不考虑物理内存上可用的空间容量。
虚拟性以多次性和对换性为基础,而多次性和对换性以离散性为基础。
代价:牺牲了CPU处理时间以及内外存交换时间。
限制:虚拟内存的最大容量主要由计算机的地址结构决定。
请求分页式管理
请求分段式管理类似。
基本概念
- 在分页系统的基础上,增加 了请求调页功能、页面置换功能所形成的页式虚拟存储系统。
- 允许只装入若干页的用户程序和数据,就能启动运行,以后在硬件支持下通过调页功能和置换页功能,陆续将要运行的页面调入内存。
- 交换分区:一段连续的、按页划分的,并且对用户不可见的磁盘空间。功能是在物理内存不够的情况下,操作系统先把内存中暂时不用的数据,存到硬盘的交换空间,腾出物理内存来让别的程序运行。
常用调度策略
-
按需调页(请求式调页)
当且仅当需要某页时,才将其调入内存;
需要使用外存,保存不在内存中的页
磁盘IO启动频率高,系统开销大
-
预调页
当进程开始时,所有页都在磁盘上,每个页都要通过缺页异常来调入内存;
实际中可以给每个进程维护一个当前工作集合中的页的列表。
如果程序执行的局部性差,则反而会降低系统的效率。
缺页错误的处理机制
-
现场保护:陷入内核态,保存必要的信息
-
页面定位:查找出发生缺页中断的虚拟页面
-
权限检查:检查虚拟地址的有效性和安全保护位;如果发生保护错误,则杀死该进程。
-
新页面调入1:查找一个空闲的页框(物理页),如果没有空闲的则需要通过页面置换算法找到一个需要换出的页框。
-
旧页面写回:如果通过页面置换算法找到的内容被修改了,则需要将修改的内容保存在磁盘上。
-
新页面调入2:页框干净后,操作系统将保持在磁盘上的页面内容复制到该页框中。
-
更新页表:磁盘中页面内容全部装入页框后,向CPU发送一个中断,操作系统更新内存中的页表项,更新页表中的页框号,并将页框标记为正常状态。
-
恢复现场:恢复缺页中断发生前的状态,将程序指针重新指向引起缺页中断的指令。
-
继续执行:重新执行引发缺页中断的指令,进行存储访问。
替换问题
-
最优置换OPT:替换之后不会再用的,如果没有则替换最久之后使用的。实际不存在,常用于比较性研究,衡量其他页置换算法的效果。
-
先进先出FIFO:没有实现局部性,可能出现Belady异常现象。
-
Belady异常:在使用FIFO算法时,随着分配的页框增多,缺页率反而提高。
-
改进的FIFO:
-
-
最近最少使用LRU:选择最近一段时间最久不用的页面淘汰。
-
软件实现方法:链表法
设置一个链表,链表节点保存当前使用的页面的页号;
每次访问内存,根据相应的页面号查找链表,如果找到则把相应的节点移到链表头部;没有找到则创建一个新的节点,插入链表头部;
链表尾部就是最近最少使用的页面,即淘汰的目标
-
硬件实现方法:计数器
设置指令计数器,每个页面在被访问时读取计数器并记录数值;最后淘汰计数值最小的页面。
-
-
工作集算法
更新问题
目标:保持主存和辅存的一致性。
在虚拟存储系统中,主存作为辅存的高速缓存,保存了磁盘信息的副本。
- 若换出的页面是file backed类型,且未被修改,则直接 丢弃,因为磁盘上保存有相同的副本。
- 若换出页面是file backed的类型,但已被修改,则直 接写回原有位置。
- 若换出页面是anonymous类型,若是第一次换出或已被 修改,则写入swap区;若非第一次换出且未被修改,则 丢弃。
工作集与驻留集
- 进程的工作集:当前正在使用的页面的集合
- 进程的驻留集:虚拟存储系统中,每个进程驻留在内存的页面集合,或进程分到的物理页框集合。
抖动问题
- 随着驻留内存的进程数量增加,即进程并发程度的提高,处理器利用率先上升,后下降。
- 原因是当每个进程的驻留集不断减小,当驻留集小于工作集后,缺页率急剧上升,频繁调页使得调页开销增大。
抖动的消除与预防:
- 局部置换策略:如果一个进程出现抖动,它不能从另外的进程那里夺取内存块,从而不会引发其他进程出现抖动。但是并未消除抖动的发生。
- 引入工作集算法
- 预留部分页面
- 挂起若干进程
- 负载控制:控制多道程序系统的度,即系统应当保持多少个活动进程驻留在内存的问题。
写时复制技术COW
两个进程共享一块物理内存,每个页面都被标志成写时复制;
共享的物理内存中的每个页面都是只读的;
某个进程想要改变某个页面时,会在内存中复制出一个页框,然后进行写操作。
内存映射文件
- 进程通过一个系统调用,将一个文件(或部分)映射到其虚拟地址空间的一部分,访问这个文件就像访问内存中的一个大数组,而不是丢文件进行读写。
- 映射共享的页面不会实际读入页面的内容,而是在访问页面时,页面才会被每次一页地读入。
- 解除文件映射或进程退出时,所有被修改页面会写回文件
- 可以方便地让多个进程共享一个文件。
存储保护
- 界限保护:所有访问地址必须在上下界之间
- 用户态与内核态
- 存取控制检查
- 环保护:不同访问级别分为不同环,编号越小级别越高。
页目录自映射
对于32位二级页表结构:
-
存储页表的4MB地址空间是整个4GB虚拟地址空间中的一部分,OS设计者可规定其所在的位置(4MB对齐)
-
4KB页目录实际上是一个特殊的页表
-
页表页实际上也是一个页面
所以存储页目录和页表一共只需要4MB,而非4KB+4MB。
一些结论:

一些理解的关键点:
“页表”实际上是由若干页组成的连续空间。
“页目录”也是一个页表。
页目录的首项一定指向页表的第一张表的起始地址,即页表基址。
由页表基址可以算出首张页表页在整个地址空间中的页号=页表基址/页面大小。
算出的页号,必然与指向首张页表页的页表项(即页目录首项)在整个页表空间中的编号相同。
由此可以算出页目录首项的位置,即页目录基址。
自映射目录表项同理。
使用页目录自映射可以节省4K(虚拟地址)空间。
页目录自映射是虚拟地址空间内的映射,与虚拟地址到物理地址的映射无关;表项里的内容才与物理地址有关!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)