3.1 内存管理基本概念

内存管理的主要功能包括:内存空间的分配与回收、地址转换(逻辑地址→物理地址)、内存扩充(虚拟存储)、存储保护。

逻辑地址 vs 物理地址
逻辑地址(相对地址/虚拟地址):程序生成的地址,从 0 开始编址。
物理地址(绝对地址):内存单元的实际地址。
地址转换由重定位完成:静态重定位(装入时)和动态重定位(运行时,靠重定位寄存器)。

3.2 连续分配方式

方式说明碎片
单一连续分配内存分为系统区和用户区,用户区一道程序无内部碎片
固定分区分配内存预先划分为固定大小分区内部碎片
动态分区分配按需分配,分区大小可变外部碎片

动态分区分配算法

碎片:内部碎片——分配区域内未使用的部分(已分配但不用);外部碎片——太小而无法分配的空闲区域。可通过紧凑(拼接)消除外部碎片,但需要动态重定位支持。

3.3 分页存储管理

将内存分为大小相等的物理块/页框 (Frame),将进程的逻辑地址空间分为与页框等大的页 (Page)。进程的各页可离散地装入不相邻的页框中。

页表
页表实现从页号到物理块号的映射。每个进程一张页表,每个表项记录逻辑页对应的物理块号。页表通常存放在内存中,页表基址寄存器 (PTBR) 存放页表起始地址。

地址转换(分页)

页大小为 2k,逻辑地址 = 页号 P + 页内偏移 W。物理地址 = 物理块号 F × 页大小 + 偏移 W。

快表 TLB:为加速地址转换,引入相联存储器(快表 TLB)缓存近期访问的页表项。访问流程:先查 TLB,命中则直接得物理块号;未命中则查内存页表,并将表项加入 TLB。有效访问时间 EAT = 命中率 × (TLB 时间 + 访存时间) + (1 − 命中率) × (TLB 时间 + 2 × 访存时间)。

两级页表与多级页表

单级页表占用连续内存过大,引入两级页表:页目录表 → 页表 → 物理块。32 位系统中,逻辑地址分为页目录号 (10 位) + 页表号 (10 位) + 页内偏移 (12 位)。避免了页表连续存放的要求。

注意:分页管理没有外部碎片(页框等大可任意分配),但有少量内部碎片(最后一页可能不满)。页大小越小,内部碎片越少,但页表越长。

3.4 页面置换算法

虚拟页式管理中,当访问的页不在内存时产生缺页中断,若内存已满需用页面置换算法选择一页换出。评价指标为缺页率

Belady 异常:仅 FIFO 算法可能出现——分配的物理块数增加,缺页率反而上升。LRU 和 OPT 不会出现 Belady 异常。

改进型 CLOCK 算法

基本 CLOCK 仅使用访问位 A(referenced bit)来决定是否淘汰。改进型 CLOCK 在此基础上增加修改位 M(modified bit / dirty bit),综合考虑替换代价:被修改过的页(脏页)换出时需要写回磁盘,开销更大。

改进型 CLOCK 将页面分为四个状态类(A, M 二元组):

类别(A, M)含义替换优先级
第 0 类(0, 0)最近未访问、未修改最佳淘汰(换出代价最小)
第 1 类(0, 1)最近未访问、但被修改过次之(换出需写磁盘)
第 2 类(1, 0)最近被访问、未修改可能再被访问
第 3 类(1, 1)最近被访问且被修改最不宜淘汰

两轮扫描机制:改进型 CLOCK 的扫描指针循环遍历页面,执行两轮扫描:

// 改进型 CLOCK(增加修改位 M)
int refBit[F], modBit[F];      // 访问位、修改位
int ptr = 0;                      // 扫描指针

void EnhancedCLOCK_Replace(int page){
  // 第一轮:找 (0,0)
  for(int round=0; round<4; round++){
    for(int i=0; iint idx = (ptr + i) % F;
      if(round==0 && refBit[idx]==0 && modBit[idx]==0){
        ptr = (idx+1)%F; goto replace;
      }
      if(round==1 && refBit[idx]==0 && modBit[idx]==1){
        ptr = (idx+1)%F; goto replace;
      }
      if(round==2) refBit[idx] = 0;  // 清访问位
    }
  }
  replace: frames[ptr] = page; refBit[ptr] = 1; modBit[ptr] = 0;
}
408 考点:改进型 CLOCK 通过增加修改位来降低换出开销(脏页写回)。在实际 OS(如 Linux)中广为使用的页面置换算法就是基于改进型 CLOCK 的变体。

5. LFU 最不经常使用置换

选择访问次数最少的页面换出。若有多页频率相同,可结合 LRU 选其中最久未访问的。LFU 对高频访问的页保护更好,对扫描型工作负载效果好。

算法核心思想实现开销适用场景
FIFO进入时间最早顺序访问
LRU最近最少使用时间局部性强
CLOCKLRU 近似(二次机会)通用场景
LFU访问频率最低高频热点访问
OPT未来最久不用(理论)不可实现性能评价基准

页面置换算法伪代码

// FIFO 先进先出
int frames[F];              // 物理块
int queue[F];              // FIFO 队列
int qh = 0;                  // 队头指针

void FIFO_Access(int page){
  if(page 在 frames 中) return;   // 命中
  if(frames 未满)
    frames[nextEmpty++] = page;
  else {                              // 缺页
    int victim = queue[qh];
    queue[qh] = page;
    qh = (qh+1) % F;
    replace(victim, page);
  }
}

// LRU 最近最久未使用
int lastAccess[F];          // 上次访问时间戳
int clock = 0;

void LRU_Access(int page){
  clock++;
  if(page 在 frames 中){
    lastAccess[idx[page]] = clock;      // 更新最近访问时间
    return;
  }
  if(frames 未满){
    frames[nextEmpty] = page;
    lastAccess[nextEmpty] = clock;
  } else {
    // 找 lastAccess 最小的页
    int victim = argmin(lastAccess);
    frames[victim] = page;
    lastAccess[victim] = clock;
  }
}

// CLOCK 时钟置换(二次机会)
int refBit[F];               // 访问位
int ptr = 0;

void CLOCK_Access(int page){
  if(page 在 frames 中){ refBit[idx[page]] = 1; return; }
  while(true){
    if(refBit[ptr]==0){
      frames[ptr] = page;            // 牺牲页
      refBit[ptr] = 1;              // 新页面访问位置 1
      ptr = (ptr+1) % F;           // 指针前进
      break;
    } else {
      refBit[ptr] = 0;            // 给二次机会
      ptr = (ptr+1) % F;
    }
  }
}

// LFU 最不经常使用
int freq[F];              // 访问次数
int last[F];              // 上次访问时间
int clock = 0;

void LFU_Access(int page){
  clock++;
  if(page 在 frames 中){
    freq[idx[page]]++;              // 命中:频率+1
    last[idx[page]] = clock;
    return;
  }
  if(frames 未满){
    frames[nextEmpty] = page;
    freq[nextEmpty] = 1;
    last[nextEmpty] = clock;
  } else {
    // 找频率最小;同频率选最久未用
    int victim = 0;
    for(int j=1; jif(freq[j] < freq[victim] ||
         (freq[j]==freq[victim] && last[j] < last[victim]))
        victim = j;
    }
    frames[victim] = page;
    freq[victim] = 1;
    last[victim] = clock;
  }
}

3.5 虚拟页式管理

虚拟存储器
基于局部性原理,将部分页装入内存,其余放在外存(盘交换区)。运行时若访问的页不在内存,产生缺页中断,从外存调入。逻辑上扩充了内存容量。

虚拟页式管理的工作流程:

局部性原理

页面分配与置换策略

策略说明
固定分配局部置换进程物理块数固定,缺页时从自身页面中置换
可变分配全局置换物理块数可变,缺页时可从其他进程抢夺空闲块
可变分配局部置换物理块数可变,缺页时从自身置换,根据缺页率动态调整
抖动 (Thrashing):频繁缺页,系统大部分时间用于页面换入换出,导致 CPU 利用率骤降。原因:物理块分配不足。可通过工作集模型、缺页率反馈控制来预防。

工作集模型 (Working Set Model)

工作集
进程在时间窗口 Δ(如最近 10,000 次访存)内访问的页面集合。记作 W(t, Δ),表示在时刻 t 前 Δ 时间窗口内被访问过的页的集合。

缺页率与工作集的关系

根据工作集模型,系统为每个进程分配的物理块数应 ≥ 该进程当前的工作集大小 WSS:

工作集的用途:① 预防抖动——监视各进程的 WSS,确保分配块数 ≥ WSS;② 指导页框分配——根据 WSS 动态调整每个进程的物理块数;③ 决定挂起哪个进程——当 WSS 总和超过内存总块数时,挂起一个进程释放其所有页框。

页框分配策略

多道程序环境下,如何将有限的内存页框分配给多个进程?常用的分配策略如下:

页面分配与置换策略详细对比

策略物理块数置换范围特点优缺点
固定分配局部置换进程期间固定仅自身页面进程启动时分配固定块数,缺页时从自身换出实现简单;但块数难确定,可能过多(浪费)或过少(高频缺页)
可变分配全局置换动态变化所有进程的页面缺页时可从全局空闲块或从其他进程抢占一页灵活,利用率高;但一个进程的行为可能影响其他进程
可变分配局部置换动态变化仅自身页面缺页时从自身换出,但 OS 会根据缺页率动态增减分配给该进程的块数兼顾灵活与隔离;实现复杂,需维护缺页率统计
决策依据:若进程缺页率高 → 增加其页框数;若缺页率低 → 可减少其页框数供给其他进程。这变相实现了工作集调整,是现代 OS 中的常见做法。

交互式动画 · 缺页中断处理流程

当访问的页面不在内存时,触发缺页中断,OS将页面从外存调入内存。步进观察完整流程。

3.6 进程内存布局与装入链接

程序从源代码到在内存中执行,需经历编译→汇编→链接→装入四个阶段。理解进程在内存中的布局是内存管理的基础。

进程内存布局

从低地址到高地址,进程的虚拟地址空间依次为:

进程虚拟地址空间布局 低地址 ↓ 代码段 (text) 程序指令,只读 数据段 (data) + BSS 已/未初始化全局变量 堆 (heap) ↑ malloc/new,向高地址增长 共享库映射区 ↓ 栈 (stack) 函数调用帧,向低地址增长 高地址 ↑ 堆与栈相向生长,中间为可用空洞
关键:代码段只读可共享;数据段含已初始化全局变量;BSS 段存未初始化全局变量(不占磁盘空间);堆向高地址增长(动态分配);栈向低地址增长(函数调用)。

装入与链接方式

装入方式时机原理特点
绝对装入编译时程序使用绝对地址,装入时直接放入内存指定位置简单,仅适用单道程序
可重定位装入(静态重定位)装入时装入时对指令中的逻辑地址加重定位因子(装入起始地址)装入后地址固定,不能移动
动态运行时装入(动态重定位)运行时地址转换延迟到运行时,靠重定位寄存器完成进程可移动,现代 OS 采用
链接方式时机原理
静态链接运行前编译链接时将库代码直接拷贝到可执行文件中
装入时动态链接装入时装入内存时找到库目标模块链接,便于更新
运行时动态链接运行时运行中需要时才链接,节省内存,DLL 采用

3.7 分段存储管理

分页管理对用户不可见,分段按程序的逻辑结构(如主程序、子程序、数据区)划分段,每段从 0 开始编址,方便编程、共享和保护。

分段地址结构
逻辑地址 = 段号 s + 段内偏移 w。段表项记录段基址 b + 段长 l。物理地址 = 段基址 b + 段内偏移 w(需检查 0 ≤ w < l,否则越界中断)。
对比项分页分段
划分依据物理等分(页大小固定)逻辑段(段大小可变)
地址维度一维(页号+偏移,对程序员透明)二维(段号+偏移,程序员可见)
碎片内部碎片外部碎片
共享/保护困难(页是物理单位)方便(段是逻辑单位)

3.8 段页式存储管理

段页式结合分段(方便共享保护)与分页(无外部碎片)的优点:先将程序分段,每段内再分页。

段页式地址结构
逻辑地址 = 段号 s + 段内页号 p + 页内偏移 w。地址转换需三次访存:查段表 → 查页表 → 取数据。
性能优化:引入 TLB 快表可减少访存次数。若 TLB 命中,只需 1 次访存(TLB 查找 + 取数据)。

3.9 TLB 快表与有效访问时间

快表 TLB (Translation Lookaside Buffer) 是 CPU 内部的高速相联存储器,缓存近期访问的页表项(页号→物理块号映射),用于加速分页/段页式的地址转换。

有效访问时间 EAT

设 TLB 命中率为 h,TLB 查找时间为 ε(约 1ns,常忽略),内存访问时间为 ma(约 50~200ns):

EAT 公式
EAT = h × (ma + ε) + (1 − h) × (2ma + ε) ≈ (2 − h) × ma(忽略 ε 时)

含义:命中时一次访存(查 TLB + 取数据),未命中时两次访存(查页表 + 取数据)。

3.10 内存映射文件 (mmap)

内存映射文件 (Memory-Mapped File)
将文件的内容映射到进程的虚拟地址空间,使文件像内存一样可以直接通过指针/地址访问。典型实现是 Unix/Linux 中的 mmap() 系统调用。

mmap 基本原理

mmap 在进程的虚拟地址空间中创建一段映射区域(通常在堆与栈之间),将磁盘文件的某些块映射到该区域:

mmap 与 read/write 的区别

对比项mmap (内存映射)read/write (系统调用)
数据路径磁盘 → 页缓存 → 用户空间(直接映射,零拷贝)磁盘 → 页缓存 → 用户缓冲区(需数据拷贝一次)
访问方式直接指针/数组访问(像内存操作)需调用 read/write 系统调用
系统调用次数仅 mmap/munmap 各一次,后续访问无系统调用每次读写都需系统调用
适用场景随机访问大文件、频繁读写、共享内存顺序读写、小文件、一次性读取
缺页开销首次访问每个页时触发缺页无缺页,但每次系统调用有上下文切换开销

内存映射文件的优点

两种映射类型私有映射 (MAP_PRIVATE)——修改不写回文件(Copy-on-Write);共享映射 (MAP_SHARED)——修改会写回文件,其他进程可见。408 考点:理解 mmap 的零拷贝原理,以及 mmap 与共享内存的关系。