内部碎片

在上一篇文章中使用单对基址/界限寄存器实现了简单的内存管理。

但可以发现分配给线程的整块内存块中有很大一部分未被利用,例如栈和堆之间的动态增长空间。这些未被使用的空间是线程内部的,无法被其他线程使用,被称为内部碎片 (internal fragmentation)

分段

很自然的想法:既然如此,就不要使用线程的一整个虚拟地址空间作为分配单位了。

将线程的地址空间按照特定长度与用途分为一个个连续段 (segmentation)

  • 文本段
  • 数据段
  • 堆段
  • 栈段

OS 以段为单位将其分配到物理空间中;为此,MMU 需要对每段维护一对基址/界限寄存器。

分段能很容易的支持内存共享 (memory sharing):如果两个进程共享代码段,只需要将它们代码段的虚拟地址映射到同一个物理地址即可。

假设 2 与 3 被取消。

段表

每个线程都有一个段表 (segment table),记录与段有关的信息。

一个段表项将记录对应段的:

  • 段号 (segment number)
  • 段基址 (segment base)
  • 段界限 (segment bound)
  • 保护位 (protection bit):该段的访问权限
  • 增长位 (growth bit):该段的增长方向,1 为由低到高,0 为由高到低
  • 有效位 (valid bit):该段是否有效

地址翻译:一个例子

虚拟空间大小为 16KB,段表如下:

Segment Base Size (max 4K) Grows Positive?
Code (00) 32K 2K 1
Heap (01) 34K 3K 1
Stack (11) 28K 2K 0

虚拟地址 15 KB 将会被翻译成什么?

  • 首先 $16$ KB $=2^{14}$ KB,虚拟地址共有 14 位。$15$ KB 写成二进制是 $11101010011000$。
  • 共有 3 段,需要至少 2 位来表示(所以实际上是 4 段,其中 1 段为空),前 2 位 $11$ 是段号。
  • 查询段表发现这个地址位于栈段。
  • 剩余的 12 位是 offset,一共是 $3$ KB。
  • 每段的 max size 是 4K($16$ K / 4 段):由于栈向负增长,段内偏移量为 $3-4=-1$ K。
  • 物理地址 $=$ 基址 $+$ 段内偏移量 $=28-1=27$ K。

外部碎片

由于作为分配单位的段大小不同,物理空间在不断的线程分配与释放过程中产生了越来越多的空洞;这并非段内的,而是段间的,因此被称为外部碎片 (external fragmentation)

  • 内存合并 (memory coalescing):线程释放内存块时,合并相邻空闲块
  • 内存压缩 (memory compaction):把所有空闲块合并成一个大的连续段移动至一端

空闲列表维护策略

OS 通过维护空闲列表将空闲块分配给各段。

  • 最优适配 (best fit):满足要求的空闲块之中最小的
  • 最劣适配 (worst fit):满足要求的空闲块之中最大的
  • 首次适配 (first fit):第一个满足要求的空闲块
  • 下次适配 (next fit):首次适配,但从上次分配结束的地方继续搜索