操作系统第八章:内存管理 (Memory Management) 通关笔记
一、 内存管理基础:程序是怎么住进内存的?
你可以把内存想象成一家“大酒店”,程序是“客人”。客人必须先住进酒店,才能开始活动(被 CPU 执行)。
1. 基础硬件认知
存储层次:CPU 只能直接访问主存(Main memory)和寄存器(Registers) 。
速度差异:寄存器访问通常只需一个 CPU 时钟周期或更少,而主存可能需要多个周期 。为了解决速度差,在它们之间加入了缓存(Cache) 。
地址空间:
逻辑地址(虚拟地址):由 CPU 生成的地址(客人的房卡上写的房间号) 。
物理地址:内存单元看到的实际地址(酒店真实的物理房间号) 。
内存管理单元 (MMU):它是前台接待员,负责将逻辑(虚拟)地址映射为物理地址的硬件设备 。用户程序永远只和逻辑地址打交道,看不到真实的物理地址 。
2. 地址绑定(Address Binding):房号是什么时候定下来的?
指令和数据绑定到具体的内存地址,可能发生在三个阶段 :
编译时 (Compile time):如果你事先就知道程序要放在哪,就可以直接写死绝对地址。如果起始位置变了,必须重新编译代码 。
加载时 (Load time):如果编译时不知道在哪,编译器会生成“可重定位代码”。具体位置等程序加载进内存时再定 。
执行时 (Execution time):如果进程在运行期间还可能在内存里挪动,那就必须把绑定推迟到运行时。这需要硬件(如基址和界限寄存器)的支持 。
3. 内存空间优化技术(客房不够用怎么办?)
动态加载 (Dynamic Loading):子程序直到被调用时才加载 。好处是未使用的例程绝不会被加载,能更好地利用内存空间 。
动态链接 (Dynamic Linking):链接推迟到执行时进行 。使用一小段称为存根(stub)的代码来定位合适的内存驻留库例程 。这对系统库特别有用 。
覆盖 (Overlays):内存中只保留任何给定时间所需的指令和数据 。通常在进程比分配给它的内存还要大时使用 。
交换 (Swapping):进程可以暂时被换出(swapped out)到后备存储(Backing store),等需要时再换回(swapped in)内存继续执行 。后备存储是一个足够大且能提供直接访问的快速磁盘 。交换的主要时间开销是数据传输时间 。
二、 连续内存分配:一人独占一整块地
系统将内存分为两部分:操作系统住在低内存(带中断向量),用户进程住在高内存 。
1. 内存保护
使用重定位寄存器 (Relocation register)和界限寄存器 (Limit register)来保护用户进程不受彼此干扰,并保护操作系统 。
逻辑地址必须小于界限寄存器,重定位寄存器包含最小物理地址的值 。
2. 动态存储分配(怎么分配空闲地块?)
当一个进程到来时,操作系统会从足够大的孔(Hole,可用的空闲内存块)中为它分配空间 。有三种分配策略:
First-fit (首次适应):分配第一个足够大的孔 。
Best-fit (最佳适应):分配足够大中的最小孔 。必须搜索整个列表,这会产生最小的剩余孔 。
Worst-fit (最坏适应):分配最大的孔 。产生最大的剩余孔 。
注:在速度和存储利用率上,首次适应和最佳适应通常优于最坏适应 。
3. 内存碎片(Fragmentation)
外部碎片 (External Fragmentation):总内存空间足够满足请求,但它们不连续,碎了一地 。可以通过紧凑 (Compaction)技术(洗牌合并空闲内存)来减少外部碎片,但这要求地址重定位是动态的且在执行时进行 。
内部碎片 (Internal Fragmentation):分配给进程的内存可能略微大于请求的内存 。多出来的那一点就在分好的区域内部,没法给别人用 。
三、 分页机制 (Paging):“切蛋糕”式的系统管理
核心思想:分页允许进程的逻辑地址空间不连续 。就像把大蛋糕切成标准化的小块,哪里有空盘子就放哪。
1. 基本架构
物理内存被划分为固定大小的块,称为帧 (frames)(大小一般在 512 字节到 8192 字节之间) 。
逻辑内存被划分为相同大小的块,称为页 (pages) 。
分页机制会产生内部碎片 。
2. 地址转换
CPU 生成的地址被切分为两部分 :
页号 (p):作为页表的索引,页表里存着每一页对应的物理基址 。
页偏移 (d):和基址结合,生成最终发给内存的物理地址 。
3. 硬件支持与 TLB
页表保存在主存中 。页表基址寄存器 (PTBR) 指向页表,页表长度寄存器 (PRLR) 指示页表大小 。
TLB (翻译后备缓冲器):因为传统的页表机制访问一次数据需要两次内存访问(查一次表,取一次数据),所以引入了 TLB 这种快速查找硬件缓存(关联内存)来加速 。
有效访问时间 (EAT) 计算:
设命中率为
,关联查找时间为 ,内存周期时间为 1 。 。 化简:
。
4. 保护与共享
保护:通过在页表项中附加有效-无效位 (Valid-invalid bit)来实现 。“有效”表示页面在进程逻辑地址空间内,是合法的 。
共享页:多个进程可以共享只读(可重入)代码(如编译器、文本编辑器等) 。共享代码必须在所有进程的逻辑地址空间中的相同位置出现 。
5. 高级页表结构(应对大内存)
分级页表 (Hierarchical Paging):把逻辑地址空间拆分成多个页表 (如经典的 32 位机器两级页表)。
哈希页表 (Hashed Page Tables):虚拟页号被哈希到页表中,表项包含一个散列到同一位置的元素链表 。
反转页表 (Inverted Page Tables):内存中的每一个真实物理页只有一个表项 。包含物理内存中该页的虚拟地址和拥有它的进程信息 。优点是减少了存页表的内存,缺点是增加了搜索表的时间 。
四、 分段机制 (Segmentation):“切猪肉”式的逻辑管理
核心思想:分段是一种支持用户视角 (user view)的内存管理方案 。程序是按照逻辑单元(如主程序、子程序、函数、堆栈、数组等)来划分成一个个“段”的 。
1. 分段架构
逻辑地址:由二元组组成:
<段号, 偏移量>(<segment-number, offset>) 。段表 (Segment table):映射二维物理地址,包含 :
基址 (base):段驻留在内存中的起始物理地址 。
界限 (limit):指定该段的长度 。
硬件支持:段表基址寄存器 (STBR) 指向段表位置,段表长度寄存器 (STLR) 指示程序使用的段数(段号
才合法) 。
2. 分段的特性
保护:段表每个条目关联验证位(=0 为非法段)和读/写/执行权限 。这使得保护机制非常自然。
共享:具有相同段号的共享段可以实现代码共享,且共享发生在段级别 。
内存分配:因为每个段的长度都不一样,内存分配是一个动态存储分配问题(使用 First fit / Best fit) 。
缺点:分段会产生外部碎片 (external fragmentation) 。
五、 核心辨析:分页 vs 分段
| 对比维度 | 分页机制 (Paging) | 分段机制 (Segmentation) |
|---|---|---|
| 视角划分 | 系统视角:方便物理内存管理,对用户透明。 | 用户视角:反映程序的逻辑结构。 |
| 尺寸与碎片 | 固定大小:产生内部碎片。 | 可变大小:产生外部碎片。 |
| 地址维度 | 一维:硬件自动将单一地址拆分为页号和偏移。 | 二维:用户提供
<段号, 偏移量>。 |
| 映射表核心 | 页表只需记录物理基址(帧号)。 | 段表必须记录基址 (base) 和 界限 (limit)。 |
| 保护与共享 | 较难精确按逻辑进行保护和共享。 | 非常容易,直接按逻辑段设置读/写/执行权限。 |
如果您喜欢我的文章,可以考虑打赏以支持我继续创作.