Skip to content

分段

1. 背景:简单内存管理的局限性

1.1 传统方式:基址/界限寄存器

  • 机制:将进程的整个地址空间加载到物理内存的一个连续区域。使用一对基址(Base)和界限(Bounds/Limit)寄存器进行地址转换和保护。
  • 优点:实现简单,支持进程重定位。
  • 缺点
    • 内存浪费:进程虚拟地址空间中未使用的部分(特别是堆和栈之间的大量“空闲”区域)仍然占用物理内存。
    • 灵活性差:如果物理内存没有足够大的 连续 空间来容纳整个地址空间,即使总空闲内存足够,进程也无法运行。
    • 不支持稀疏地址空间:对于需要很大虚拟地址范围(如 32 位/4GB)但实际只使用其中一小部分(几 MB)的程序,效率极低。

1.2 核心问题

  • 如何在物理内存有限的情况下,高效支持 大且稀疏 的虚拟地址空间?

2. 分段 (Segmentation) 概念

2.1 核心思想

  • 不再将整个地址空间视为一个整体,而是将其划分为多个逻辑上独立的 段 (Segment)
  • 典型的段包括:代码段 (Code)堆段 (Heap)栈段 (Stack)
  • 每个段是虚拟地址空间内的一个 连续 区域,但各段在物理内存中 不必连续 存放。

2.2 硬件支持

  • MMU (内存管理单元) 中包含 多对 基址/界限寄存器,每一对 对应地址空间中的一个逻辑段。
  • 每个段寄存器对至少记录:
    • 基址 (Base):该段在物理内存中的起始地址。
    • 界限/大小 (Limit/Size):该段的长度。

2.3 主要优势

  • 节省物理内存:只有被实际使用的段才需要分配物理内存,虚拟地址空间中未使用的区域(如堆栈间隙)不再浪费物理内存。
  • 支持稀疏地址空间:即使虚拟地址空间很大,只要各段的总大小能被容纳,进程就能运行。
  • 灵活性:可以将不同段放置在物理内存的不同空闲区域。

3. 分段机制下的地址转换

3.1 识别段与段内偏移

  • 虚拟地址结构:虚拟地址需要能被解析成 (段标识, 段内偏移)
  • 段标识方法
    • 显式方法 (Explicit):使用虚拟地址的高位比特来指定段。例如,14 位虚拟地址,前 2 位可以指定 4 个段(00=代码, 01=堆, 10=未使用, 11=栈)。
    • 隐式方法 (Implicit):根据地址产生的方式来判断段。例如,PC 产生的地址属于代码段,栈指针相关的地址属于栈段,其他属于堆段。

3.2 地址转换步骤 (以显式方法为例)

  1. 从虚拟地址中提取 段标识符 (Segment Identifier)
  2. 根据段标识符选择对应的 段寄存器对 (获取 BaseLimit)。
  3. 从虚拟地址中提取 段内偏移 (Offset)
  4. 边界检查:检查 Offset 是否小于该段的 Limit (0 <= Offset < Limit)。
    • 若越界,则触发 段错误 (Segmentation Fault / Violation) 异常,通常导致进程终止。
  5. 计算物理地址PhysicalAddress = Base[Segment] + Offset
  6. 访问计算出的物理地址。

3.3 栈段的特殊处理

  • 问题:栈通常向下(向低地址方向)增长。
  • 硬件支持:段描述符中增加一个 方向位 (Direction Bit),指示段是向上增长还是向下增长。
  • 地址转换调整
    • 对于向下增长的段(如栈),偏移量的计算和边界检查需要调整。
    • 示例:假设栈段虚拟地址范围 14KB-16KB,物理基址 28KB,大小 2KB,向下增长。访问虚拟地址 15KB (二进制 11 1100 0000 0000):
      1. 段标识 11 指向栈段。
      2. 原始偏移是 3KB (0x0C00)。
      3. 计算反向偏移:NegativeOffset = Offset - MaxSegmentSize = 3KB - 4KB = -1KB
      4. 边界检查:abs(NegativeOffset) <= Limit (即 1KB <= 2KB,合法)。
      5. 计算物理地址:PhysicalAddress = Base + NegativeOffset = 28KB + (-1KB) = 27KB

4. 分段的进阶特性

4.1 支持共享 (Sharing)

  • 动机:节省内存,允许多个进程共享相同的物理内存区域,特别是代码段。
  • 机制:为每个段增加 保护位 (Protection Bits),如:
    • 读 (Read)
    • 写 (Write)
    • 执行 (Execute)
  • 实现:将代码段标记为“只读/可执行”。操作系统可以让多个进程的虚拟地址空间中的代码段映射到 同一块 物理内存。硬件在每次访问时检查权限,防止进程修改共享代码,保持隔离性。

4.2 分段粒度 (Granularity)

  • 粗粒度 (Coarse-grained):地址空间划分为少数几个大段(如代码、堆、栈)。实现相对简单。
  • 细粒度 (Fine-grained):地址空间划分为大量小段。
    • 需要更复杂的硬件支持(如内存中的 段表 (Segment Table))。
    • 允许更精细的内存管理和共享。
    • 早期系统(如 Multics, Burroughs B5000)采用过,编译器可将代码和数据细分为多个段。

5. 操作系统对分段的支持与挑战

5.1 上下文切换 (Context Switching)

  • 操作系统需要在切换进程时 保存 即将暂停进程的 所有段寄存器 的状态,并 恢复 即将运行进程的段寄存器状态。

5.2 物理内存管理

  • 操作系统需要维护物理内存的空闲空间信息,并为新进程的段或现有段的增长找到合适的 空闲块

5.3 外部碎片 (External Fragmentation)

  • 定义:随着段的分配和释放,物理内存中会产生许多 不连续 的、大小不一 的小空闲块。
  • 问题:即使总的空闲内存足够,也可能找不到一个足够大的 连续 空闲块来满足新的段分配请求。
  • 解决方案
    • 内存紧缩 (Compaction):移动物理内存中的现有段,使它们连续存放,从而合并小的空闲块形成一个大的连续空闲区。代价高昂(需要停止进程、大量内存拷贝、更新段基址)。
    • 空闲列表管理算法:使用特定算法(如 First-Fit, Best-Fit, Worst-Fit, Buddy System 等)来管理空闲块,试图减少碎片的产生或影响。
      • 没有完美算法:所有算法都只能 缓解 而不能完全 消除 外部碎片。存在多种算法本身就说明了没有绝对最优解。

6. 分段的总结与局限

6.1 优点 (Pros)

  • 高效利用物理内存:避免为虚拟地址空间中的未使用部分分配物理内存。
  • 支持稀疏地址空间
  • 逻辑分段:代码、数据、栈的分离符合程序的逻辑结构。
  • 支持共享:通过保护机制可以方便地实现段(尤其是代码段)的共享。
  • 快速地址转换:硬件直接执行,开销小。

6.2 缺点 (Cons)

  • 外部碎片:最主要的问题,导致物理内存利用率下降,分配困难。
  • 段内稀疏问题:如果一个逻辑段本身很大但内部使用稀疏(例如一个大堆),分段机制仍然要求为整个段(可能的最大尺寸)找到连续或足够大的物理空间(取决于具体实现),无法解决段 内部 的稀疏性问题。
  • 段增长困难:如果一个段需要增长,可能没有相邻的空闲物理内存,需要移动段或寻找更大的空闲块。

6.3 结论

  • 分段是对基址/界限方法的重要改进,提高了内存效率和灵活性,并引入了共享的可能性。
  • 然而,外部碎片问题非常棘手,且其对稀疏性的支持粒度停留在段级别。
  • 这些局限性促使了内存管理技术向 分页 (Paging)段页式结合 (Segmentation with Paging) 的方向发展,以求更好地解决内存碎片和支持任意粒度的稀疏地址空间。