🧠 内存管理

地址转换 · 分页/分段/段页式 · 页面置换算法 · TLB快表
内存布局 分页地址转换 分段与段页式 TLB快表 页面置换
🗺️ 进程内存布局
从低地址到高地址:代码段 → 数据段 → 堆 → 共享库 → 栈
各段说明:
· 代码段(text):存放程序机器指令,只读、可共享
· 数据段(data):存放已初始化的全局变量和静态变量
· BSS段:未初始化的全局变量(初始化为0,不占磁盘空间)
· 堆(heap):动态分配内存(malloc/new),向高地址增长
· 共享库:动态链接库映射区域
· 栈(stack):函数调用栈帧,存放局部变量、返回地址,向低地址增长

⚡ 堆与栈的增长方向:堆↑(高地址)、栈↓(低地址),中间是"空洞"→ 可以动态扩大。
📦 连续分配管理(单一/固定/动态分区)
内存分为系统区(低地址)和用户区(高地址),用户区采用连续分配方式
📖 连续分配:每个进程占用内存中一块连续的区域。分为三种方式:

① 单一连续分配:内存只有一道用户程序(如DOS),简单但内存利用率极低
② 固定分区分配:把内存分成若干个大小固定的分区,每个分区放一个进程
③ 动态分区分配:分区大小不固定,根据进程需要动态分配(最常用)
动态分区分配算法(409-416页重点!)
① 首次适应(First Fit):低地址开始找,第一个足够大的空闲分区就分配
  · 优点:简单、高地址保留大空闲块
  · 缺点:低地址产生很多小碎片

② 最佳适应(Best Fit):大小最接近的空闲分区(需要把空闲区按大小排序)
  · 优点:避免大材小用
  · 缺点:产生很多难以利用的小碎片

③ 最坏适应(Worst Fit):最大的空闲分区(需要把空闲区按大小排序)
  · 优点:产生碎片较小
  · 缺点:大进程可能没有足够大的空闲区

④ 邻近适应(Next Fit):从上次查找的结束位置开始找(不是每次都从低地址开始)
碎片问题(连续分配的最大缺点)
外部碎片(External Fragmentation)
所有空闲分区的总和足够,但没有一个单独的分区足够大。

解决方法:
· 紧凑(Compaction):把已占用分区移到一起,腾出连续空间(需要动态重定位支持)
· 分区对换(Swapping):把暂时不运行的进程换出到外存
内部碎片(Internal Fragmentation)
分配给进程的内存区域比进程实际需要的大,多出来的部分就叫内部碎片。

典型场景:固定分区分配(分区大小固定,进程不一定用得完)

⚡ 分页/分段可以解决碎片问题:分页无外部碎片但有内部碎片;分段无内部碎片但有外部碎片。
📝 408高频考点:
① "首次适应 vs 最佳适应 vs 最坏适应" → 记住它们各自的优缺点
② "紧凑技术需要什么支持?" → 需要动态重定位(地址可以动态修改)
③ "内部碎片 vs 外部碎片" → 内部:分配给进程但进程用不完;外部:所有空闲区总和够,但没有单独一个够大
🔢 分页地址转换
逻辑地址 = 页号 + 页内偏移 · 通过页表转换为物理地址
地址转换演示
页表结构
转换步骤:
1. 逻辑地址拆分为 页号p页内偏移w(页大小2ⁿ,低n位为偏移)
2. 查页表:页号p → 内存块号f
3. 物理地址 = f × 页大小 + w
逻辑地址(十进制): (页大小=4KB)
页表(Page Table):每个进程一张,存放在内存中。
逻辑页号是索引,通过页表映射到物理内存中的内存块号。

多级页表:32位系统,页大小4KB → 页号占20位 → 需要2²⁰个页表项 → 页表太大!
解决:把页表再分页(二级页表),顶级页表常驻内存。
页号p内存块号f有效位
05
12
28
3❌ 缺页
46
53
6❌ 缺页
74
📐 分段与段页式
分段按逻辑段划分 · 段页式结合两者优点
分段存储管理:
逻辑地址 = 段号s + 段内偏移w
通过段表:段号s → 段基址b + 段长l
物理地址 = b + w(需检查 0≤w<l,否则越界中断)

优点:方便共享(共享段只需一段)、方便保护
缺点:外部碎片(类似动态分区)
段页式存储管理:
先分段、每段内再分页。
逻辑地址 = 段号s + 段内页号p + 页内偏移w

地址转换需要两次查表:
1. 查段表 → 该段的页表起始地址
2. 查页表 → 内存块号f
3. 物理地址 = f×页大小 + w

优点:无外部碎片 + 方便共享/保护
🔄 交互动画:
逻辑地址(段号, 段内偏移):
⚡ TLB快表(地址转换高速缓存)
TLB命中率直接影响有效访问时间EAT
TLB(Translation Lookaside Buffer):CPU内部的高速缓存,存放最近访问过的页表项(页号→内存块号的映射)。
访问内存耗时 ma(通常50~200ns),TLB查找耗时 ϵ(≈1ns)。

EAT = (1-h)×ma + h×ϵ + [(1-h)×(ma+ma)] + h×(ma)
简化(ϵ 可忽略):EAT ≈ (2-h)×ma(其中h为命中率)
命中率h = 0.95
内存访问ma = 100ns
EAT = 105ns
🔄 页面置换算法
缺页中断时选择换出哪个页面 · 对比FIFO/LRU/OPT/Clock/LFU
FIFO
LRU
OPT
Clock
LFU
🎭 虚拟内存(Virtual Memory)
让内存比实际大 —— 虚拟内存的核心思想
📖 什么是虚拟内存?
虚拟内存是指从逻辑上扩充内存容量的技术(用户感觉内存比实际大)。它利用局部性原理,把暂时不用的页面换出到外存,需要时再换入。

⚡ 局部性原理(408超高频考点!):
· 时间局部性:最近访问过的指令/数据,很快会再次被访问(如循环、变量复用)
· 空间局部性:最近访问过的地址,其邻近的地址也会很快被访问(如数组、顺序执行)
虚拟内存的三大实现方式
① 请求分页(Demand Paging):最常用,以为单位换入换出
② 请求分段(Demand Segmentation):为单位换入换出
③ 请求段页式:先按段换入,段内再按页换入
虚拟内存带来的好处
✅ 大程序可运行:程序可以比实际内存大(部分在内存,部分在外存)
✅ 内存利用率高:多个进程可以共享同一份代码(COP)
✅ 简化编程:程序员不需要考虑内存容量限制
📝 408综合题考点:
① "虚拟内存的容量由什么决定?" → 地址寄存器的位数(即CPU的寻址空间,如32位系统最多4GB虚拟内存)
② "虚拟内存需要什么硬件支持?" → 地址转换机构(页表/段表)+ 外存(存放换出的页面)
③ "虚拟内存一定比实际内存大吗?" → 是的,但受限于CPU寻址空间
🔃 请求分页流程(缺页中断处理)
当访问的页面不在内存时,触发缺页中断,OS负责换入页面
📖 请求分页 vs 纯分页:
· 纯分页(传统分页):进程的所有页面在运行前必须全部装入内存
· 请求分页(虚拟内存):进程运行时,只装入少部分页面,缺页时再动态装入

⚡ 缺页中断(Page Fault):访问的页面不在内存时,触发一个特殊的中断(不是在指令执行完毕时检查,而是在指令执行期间检查)
▲ 缺页中断处理完整流程(点击步进观看)
缺页中断处理详细步骤(408标准答案版)
Step 1:CPU执行指令,访问逻辑地址,通过页表查询发现有效位=0(页面不在内存)→ 触发缺页中断
Step 2:OS保存当前进程的CPU上下文(陷入内核态)
Step 3:查找外存中的页面位置(通常在PCB或页表的辅存地址字段)
Step 4:如果内存有空闲页框 → 直接装入;否则调用页面置换算法换出一个页面(如果脏则写回外存)
Step 5:把所需页面从外存读入内存页框
Step 6:更新页表(有效位设为1,内存块号填入)
Step 7:恢复进程CPU上下文,重新执行那条触发缺页的指令(这次页面在内存了,访问成功!)
📝 考点:"缺页中断和一般中断的区别?" → 缺页中断发生在指令执行期间(不是指令执行完毕),并且中断处理完后需要重新执行原指令(而一般中断处理完后执行下一条指令)。
🌪️ 工作集与抖动(Thrashing)
当缺页率太高时,CPU大部分时间都在换页,导致系统吞吐量骤降
📖 工作集(Working Set):
某进程在某段时间Δ内,实际访问的页面集合。如果把这些页面都放在内存,进程就几乎不会缺页。

⚡ 工作集模型:OS统计每个进程的工作集,保证所有进程的工作集之和 ≤ 内存容量,这样就不会抖动。
▲ 缺页率 vs 内存分配量(U型曲线)
抖动(Thrashing)现象
缺页率过高时,CPU大量时间用于页面换入换出,几乎不执行用户程序。

原因:进程数太多,每个进程分到的页框数 < 工作集大小
现象:CPU利用率断崖式下跌,磁盘I/O爆满
防止抖动的方法
① 工作集模型:保证每个进程的工作集都在内存
② 缺页率反馈调整:缺页率高 → 增加页框分配;缺页率低 → 减少页框分配
③ Lettice-Nagle算法:限制并发I/O的进程数(防止所有人都来抢磁盘)
📝 408综合题考点:
① "什么是抖动?如何防止?" → 缺页率过高导致CPU利用率暴跌;防止方法:工作集模型、缺页率反馈调整
② "缺页率达到多少时需要增加内存分配?" → 通常缺页率 > 50% 就需要增加页框分配
③ "Belady异常(Belady's Anomaly)" → FIFO页面置换算法可能出现:分配页框越多,缺页率反而越高(LRU/OPT没有这个异常)
📦 页框分配策略(如何给进程分配内存?)
多道程序系统中,如何把有限的内存页框分配给多个进程?
📖 分配策略的核心矛盾:
给每个进程多分配页框 → 缺页率低,但能并发的进程数少
给每个进程少分配页框 → 能并发的进程数多,但缺页率高(容易抖动)
① 平均分配
把所有页框平均分配给各个进程。

❌ 缺点:忽略了进程大小的差异(大进程还是容易缺页)
② 按比例分配
按进程的大小比例分配页框(大进程分得多)。

✅ 优点:比平均分配更公平
③ 优先权分配
重要进程多分配页框(如实时进程)。还可以设置保留页框(重要进程保留最低限度的页框,不会被全部换出)。

✅ 优点:保证关键进程的响应时间
LRU栈算法(计算不同页框数下的缺页次数)
思想:维护一个LRU栈(栈顶是最近访问的页号),当分配m个页框时,只需要看栈中前m个页面是否覆盖所有访问序列。

用法:对于给定的页面访问序列,建立LRU栈,然后对于任意页框数m,缺页次数 = 访问序列长度 - 不需要缺页的次数(即那些在栈中前m个位置的页面)。

📝 408计算题考点:给你一个页面访问序列,问"分配3个页框和4个页框,缺页次数分别是多少?" → 用LRU栈算法可以一次性算出所有m的缺页次数(不需要对每个m重新模拟)。
📝 408高频考点总结:
① "页框分配需要考虑哪些因素?" → 进程总页数、进程优先级、内存总量、工作集大小
② "什么是保留页框(Reservation)?" → 给重要进程保留最低限度的页框,防止被全部换出
③ "分配页框数 < 工作集大小时会发生什么?" → 抖动