内存管理(连续分配/重定位/碎片)
连续分配/动态分区/重定位/内部外部碎片,常考概念对比。
#resource / operating-system
#type / concept
#status / growing
内存管理
1) 内存管理要解决的问题
- 分配与回收:高效利用物理内存
- 地址转换:逻辑地址 → 物理地址
- 保护与共享:进程间隔离,同时允许共享库
- 扩充:虚拟内存,让程序可用空间 > 物理内存
2) 重定位(逻辑地址 → 物理地址)
- 静态重定位:装入时完成,装入后不能移动
- 动态重定位:运行时通过基址寄存器 + 限长寄存器完成(现代系统常见)
物理地址 = 基址寄存器值 + 逻辑地址
3) 连续分配
- 固定分区:预先划分 → 内部碎片
- 动态分区:按需分配 → 外部碎片
动态分区分配算法:
| 算法 | 策略 | 特点 |
|---|---|---|
| First Fit | 找到第一个够大的空闲块 | 快,但低地址碎片多 |
| Next Fit | 从上次位置继续找 | 分散碎片 |
| Best Fit | 找最小的够大块 | 碎片小但多 |
| Worst Fit | 找最大的空闲块 | 碎片大但少 |
4) 碎片(必背)
- 内部碎片:分配多了用不完(如固定分区、页式分配的最后一页)
- 外部碎片:空闲块总量足够,但不连续(如动态分区)
解决外部碎片:紧凑(compaction) 或改用非连续分配(分页/分段)。
5) 分页 vs 分段
| 维度 | 分页(Paging) | 分段(Segmentation) |
|---|---|---|
| 划分方式 | 固定大小(如 4KB) | 按逻辑单位(代码段、数据段等) |
| 碎片 | 内部碎片 | 外部碎片 |
| 地址结构 | 页号 + 页内偏移 | 段号 + 段内偏移 |
| 共享/保护 | 不方便(一页可能混合内容) | 方便(按段共享) |
| 现代系统 | 主流方案 | 通常与分页结合(段页式) |
分页地址转换
逻辑地址 = 页号 p + 页内偏移 d
页表[p] = 物理帧号 f
物理地址 = f × 页大小 + d
6) 虚拟内存
允许程序使用比物理内存更大的地址空间。核心思想:
- 只将活跃页面保留在内存中
- 不活跃页面放在磁盘交换区(swap)
- 访问不在内存的页面时触发缺页中断(page fault)
缺页处理流程
CPU 访问虚拟地址
→ MMU 查页表 → 该页不在内存(valid bit = 0)
→ 触发缺页中断
→ OS 选择牺牲页(若页框满)
→ 将牺牲页写回磁盘(若脏页)
→ 从磁盘加载目标页到空闲页框
→ 更新页表
→ 重新执行触发缺页的指令
7) TLB(Translation Lookaside Buffer)
页表在内存中,每次地址转换需两次内存访问(查页表 + 访问数据)。TLB 是页表的高速缓存:
CPU 产生虚拟地址
→ 查 TLB(硬件,极快)
→ 命中:直接得到物理帧号
→ 未命中:查内存页表,结果写入 TLB
TLB 命中率通常 > 99%,有效将地址转换开销降到接近一次内存访问。
8) 内存映射文件(Memory-Mapped Files)
将文件映射到进程的虚拟地址空间,通过内存操作实现文件读写:
void *addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);
// 之后直接通过 addr 指针读写,OS 负责 page-in/page-out
munmap(addr, length);
优点:避免显式 read/write 系统调用,利用 OS 的页面缓存机制。
9) 交换(Swapping)
将整个进程从内存移到外存(swap out),需要时再移入(swap in)。
- 代价高(进程的全部页面都要移动),现代系统更倾向于页面级别的虚拟内存。
- 仍用于内存极度紧张时回收物理页框。