Skip to content

连续分配与碎片

基于通用操作系统概念 · 核于 2026-08

速查

  • 固定分区(Fixed Partitioning):内存预切成大小固定的分区,每分区一道进程;小进程进大分区 → 分区内剩余浪费 → 内部碎片(Internal Fragmentation)。分区大小一旦切定不变,分配/释放 O(1),但空间利用率低。
  • 动态分区(Dynamic Partitioning):按进程需求恰好分配,无内部碎片;但分配/释放后产生大量小空洞 → 外部碎片(External Fragmentation)——总空闲够但无大块连续可用。
  • 内部碎片 vs 外部碎片内部=分配给进程的块内部有浪费(块大于需求,固定分区/伙伴系统);外部=空闲块之间太碎凑不成大块(动态分区)。
  • 首次适应(First-Fit):从头扫描,第一个够大的空闲块即分配。低地址区被频繁切割 → 低地址碎片多;但扫描快,实际系统最常用
  • 最佳适应(Best-Fit):扫描所有空闲块,选最小的够大块。省大块,但产生大量极小不可用碎片(恰好剩一点点),外部碎片最严重。
  • 最坏适应(Worst-Fit):选最大的空闲块分配(剩一大块给别人用)。减少小碎片,但大块很快耗尽,大进程一来就分配失败。
  • 紧凑(Compaction):搬移进程把零散空闲合并成大块。前提是执行时绑定(地址可动态重定位);且要停机、开销大(搬整段内存 + 改映射),现实中很少频繁做。
  • 碎片衡量:外部碎片用"总空闲 vs 最大连续空闲块"的差距衡量——总空闲 100MB 但最大块只有 5MB,则 95MB 是碎片。
  • 放置策略复杂度:首次/最佳/最坏适应分配都要扫描空闲链表,最坏 O(n);释放合并相邻空闲块 O(n)(找邻居)。这是伙伴系统 O(log n) 要改进的目标。

一、固定分区:内部碎片之源

固定分区(Fixed/Static Partitioning)在系统启动时就把用户区切成若干大小固定的分区(可等大,也可分成几档大小如小/中/大分区)。每个分区容纳一道进程:

   内存
 ┌──────────┐
 │  OS 区   │
 ├──────────┤
 │ 分区1 8MB│ ← 进程A(2MB),剩 6MB 内部碎片
 ├──────────┤
 │ 分区2 8MB│ ← 进程B(7MB),剩 1MB 内部碎片
 ├──────────┤
 │ 分区3 8MB│ ← 空
 ├──────────┤
 │ 分区4 8MB│ ← 进程C(3MB),剩 5MB 内部碎片
 └──────────┘
  • 内部碎片(Internal Fragmentation):分区内未被进程用到的剩余空间。它被锁死在分区里,其他进程无法使用,纯浪费。分区越大、进程越小,浪费越严重。
  • 优点:分配/释放极快(查分区表找个空分区即可),管理简单,硬件只需 base/limit 保护寄存器。
  • 缺点:①小进程占大分区浪费(内部碎片);②分区大小固定,进程大于最大分区就无法运行;③同时运行的进程数 ≤ 分区数。
  • 改进——多档分区:切出几档不同大小(如若干小分区给小进程、几个大分区给大进程),按进程大小选合适分区,缓解内部碎片。IBM OS/360 的 MFT(固定任务数多道)用过。

二、动态分区:外部碎片之源

动态分区(Dynamic/Variable Partitioning)不预切,按进程实际需求恰好分配。维护一条空闲链表(free list),分配时从中找一块够大的切出来,释放时归还并尝试与相邻空闲块合并:

   初始            分配A(2MB)B(7MB)C(3MB)      释放B后
 ┌────────┐       ┌────────┐                ┌────────┐
 │  OS    │       │  OS    │                │  OS    │
 ├────────┤       ├────────┤                ├────────┤
 │        │       │ A 2MB  │                │ A 2MB  │
 │        │       ├────────┤                ├────────┤
 │        │       │ B 7MB  │                │ 空7MB  │ ← 与前后
 │  空闲  │  ───> │        │    ───>        │        │   空闲合并
 │        │       ├────────┤                ├────────┤
 │        │       │ C 3MB  │                │ C 3MB  │
 │        │       ├────────┤                ├────────┤
 └────────┘       │ 空闲   │                │ 空闲   │
                  └────────┘                └────────┘
  • 无内部碎片:分给进程的块恰好等于需求(切出来的就是进程要的大小),块内无浪费。
  • 外部碎片(External Fragmentation):随进程反复分配/释放,空闲空间被打碎成许多小块,散落在已分配块之间。空闲总量可能很大,但找不到一块连续的够大区域给新进程。这是动态分区的致命伤。
  • 例子:总空闲 60MB,但分成 20+20+20 三块散布,则一个要 30MB 的进程分配失败——这就是外部碎片。
  • 缓解手段:①用好的放置策略(首次/最佳/最坏适应)减少碎片产生;②紧凑 compaction(搬移合并);③最终解法是非连续分配(分页,下一叶)彻底打散进程。

三、三种放置策略

动态分区分配一个进程时,空闲链表里可能有多个够大的块,选哪个?这就是放置策略(Placement Strategy)

策略做法优点缺点
首次适应(First-Fit)从头扫描,选第一个够大的块快(找到即停);最常用低地址区被反复切割,碎片集中在低地址
最佳适应(Best-Fit)扫描所有块,选最小的够大省大块(保留大块给大进程)产生大量极小不可用碎片,外部碎片最严重
最坏适应(Worst-Fit)扫描所有块,选最大的块分配剩一大块给别人,减少小碎片大块迅速耗尽,大进程很快无块可分
  • 首次适应:实验证明综合性能最好(扫描快、碎片可接受),是实际系统(早期 Unix、嵌入式分配器)的默认选择。
  • 最佳适应:直觉上"最省",但每次都恰好剩一点点 → 那一点点几乎没人能用 → 碎片灾难。慎选。
  • 最坏适应:每次切大块,短期内小碎片少,但大块是稀缺资源,几次后就没了 → 大进程频繁失败。
  • 复杂度:三者最坏都要 O(n) 扫描空闲链表。这是连续分配相对分页/伙伴系统的劣势之一。

四、内部碎片 vs 外部碎片

这两个概念极易混淆,必须分清——区别在碎片的位置

内部碎片(Internal)外部碎片(External)
在哪分配块内部(块 > 需求)空闲块之间(散碎凑不成大块)
谁产生固定分区、伙伴系统(向上取整)动态分区(频繁分配/释放)
能否用不能——已被分给某进程,锁死在块内理论能——总空闲够,只是不连续
解法让块大小接近需求(slab/可变块)紧凑 compaction / 分页打散
  • 记忆口诀:内碎片"块里多了点用不掉",外碎片"块间碎得拼不起来"。
  • 伙伴系统两个都有:要 17KB 给 32KB(内碎片 15KB);同时多次分配释放产生不连续小块(外碎片)。
  • 分页是消除外碎片的利器——把进程打散到任意空闲页框,外碎片归零;但页内最后一格可能浪费(极小的内碎片)。

五、碎片整理:紧凑 compaction

对付外部碎片最直接的办法是紧凑(Compaction)——把已分配的进程搬移到内存一端,把零散空闲挤到另一端合并成大块:

   紧凑前(碎片化)              紧凑后(连续空闲)
 ┌────────┐ ┌──┐┌─┐┌──┐       ┌────────┐┌──┐┌─┐┌──┐┌──────┐
 │ A      │ │空││B│ │空│  ──>  │ A      ││ B│ │C │ │大空闲│
 │        │ │隙││ │ │隙│       │        ││  │ │  │ │      │
 └────────┘ └──┘└─┘└──┘       └────────┘└──┘└──┘└──────┘
  • 前提:执行时绑定。搬移进程意味着进程的物理地址变了——只有在执行时绑定(MMU 动态翻译地址)下才能搬(改 base 寄存器或页表即可)。编译时/加载时绑定的进程搬了就地址全错。
  • 代价高昂:①要搬移整段进程内存(拷贝几十 MB,慢);②要更新所有地址映射;③停机进行(搬移中进程不能运行)。所以现实中很少频繁紧凑,多数系统用分页绕开它。
  • 替代:移动与固定分区都救不了根本矛盾,分页(下一叶)让进程无需连续,从根本上消除外部碎片。

六、连续分配为何让位给分页

连续分配的矛盾——固定分区内碎片、动态分室外碎片、紧凑贵——催生了非连续分配:把进程打散到不连续的内存区域(页框),靠页表翻译逻辑地址到物理地址。这样:①进程无需大块连续内存,外部碎片归零;②页框大小固定,分配/释放 O(1);③支持换页(虚拟内存)。这是下一叶的主题。但分页引入了页表存储与 TLB 的开销,伙伴系统/slab 仍是物理内存分配的底层。

下一步

连续分配的碎片问题讲清后,下一步看内存管理的两个高效分配机制——伙伴系统与 Slab 分配讲透 2 的幂次分配合并(O(log n))与内核对象池复用,并引入非连续分配(分页/分段)作为下一叶的引子。