Skip to content

入门:地址绑定与连续分配

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

速查

  • 为何要内存管理:RAM 有限且多进程共享——OS 要决定每个进程放哪、地址怎么映射、内存不够怎么换;不管理则进程互相覆盖、地址硬编码无法多份运行。
  • 逻辑地址(虚拟地址)vs 物理地址:CPU/程序产生的地址是逻辑地址(相对程序起点),内存芯片上的真实地址是物理地址;由 MMU(内存管理单元) 把逻辑地址翻译成物理地址。
  • 地址绑定三时机:①编译时(地址写死,绝对代码,不可重定位);②加载时(编译成可重定位代码,加载时改成绝对地址,之后不动);③执行时(运行期动态绑定,靠 MMU/页表翻译,可换页/搬移,最灵活)。
  • 单一连续分区:最简,用户区整体给一个进程(早期单道),多道时加保护寄存器,但只能一道用户程序。
  • 固定分区(多道):内存预切成若干大小固定的分区,每分区一道进程;小进程进大分区 → 内部碎片(分区内剩余浪费)。
  • 动态分区:按进程实际需求恰好分配,无内部碎片;但分配/释放产生小空洞 → 外部碎片(总空闲够但无大块)。
  • 三种放置策略首次适应(找第一个够大的空闲块,低地址优先,最快最常用)、最佳适应(找最小的够大块,省大块但产生极小碎片)、最坏适应(找最大块,减少小碎片但很快耗尽大块)。
  • 碎片整理(紧凑 compaction):搬移进程把零散空闲合并成大块——但要求执行时绑定(地址可重定位)且是停机操作,开销大。
  • 伙伴系统:物理内存按 2 的幂次分配/合并,要 17KB 给 32KB(有内碎片),但合并查找 O(log n),是 Linux 物理页分配器核心。
  • slab 分配:内核为频繁创建/销毁的同类型小对象(如 inode/task_struct/skbuff)预分配对象池,复用已构造对象,分配 O(1),消除反复初始化开销。
  • 非连续分配(分页/分段/虚拟内存)是下一叶——把进程打散到不连续的内存区域,靠页表翻译地址,彻底消除外部碎片。
  • 进阶顺序连续分配与碎片伙伴系统与 Slab 分配参考

一、为何要内存管理

主存(RAM)有两个根本约束:①容量有限(远小于磁盘);②多进程共享(几十上百个进程都要驻留内存运行)。如果没有人管理,会出现两类灾难:

  1. 地址冲突:进程 A 和进程 B 都想把自己的代码加载到地址 0x400000,谁的代码覆盖谁的?没有隔离,一个进程的 bug 写坏了另一个的数据。
  2. 地址硬编码:编译时就把地址写死,则程序只能加载到固定位置,无法多份运行(两个 bash 没法都从 0x400000 起)。

操作系统的内存管理解决这些问题的核心思路是地址抽象——给每个进程一套独立的逻辑地址(从 0 开始的虚拟空间),由硬件(MMU)把它翻译成物理地址。这样每个进程都"以为自己独占整个内存",互不干扰;同时 OS 决定逻辑地址映射到哪个物理位置(地址绑定),并在内存不够时换出(虚拟内存,下一叶)。

一句话:内存管理 = 地址抽象 + 分配策略 + 碎片治理。本叶讲前两者(连续分配模型),虚拟内存留给下一叶。

二、逻辑地址 vs 物理地址

这是内存管理最基础的概念,必须分清:

逻辑地址(Logical/Virtual)物理地址(Physical)
谁产生CPU 执行指令时产生、程序里写的地址内存芯片上的真实地址,地址总线送出
范围相对程序起点,每个进程独立空间(从 0 起)全局唯一,对应真实 RAM 单元
关系程序"看到"的地址硬件"实际访问"的地址
翻译MMU(内存管理单元)翻译——
  • MMU(Memory Management Unit):CPU 内的硬件部件,把 CPU 产生的逻辑地址翻译成物理地址。分页模式下查页表,连续模式下查重定位寄存器(base + limit)。
  • 逻辑地址空间:CPU 能产生的所有逻辑地址的集合(如 64 位下 0 到 2^64)。物理地址空间:实际 RAM 的大小(如 16GB)。
  • 为什么要分离:分离后程序代码不再依赖物理位置,可重定位、可多份运行、可换页——这是进程隔离与虚拟内存的基石。

三、地址绑定的三个时机

"程序里的地址"何时变成"确定的物理地址"?这就是地址绑定(Address Binding),有三个时机,灵活性递增:

  1. 编译时绑定(Compile time):编译器直接生成绝对地址(如从 0x1000 起)。优点是简单快;缺点是不可重定位——程序只能加载到固定位置,想多份运行或挪位置都不行。早期批处理系统用过。
  2. 加载时绑定(Load time):编译器生成可重定位代码(地址都是相对的),加载器在把程序载入内存时,根据实际起始地址把所有相对地址改成绝对地址。之后地址就固定了。比编译时灵活(可加载到任意位置),但加载后不能再动。
  3. 执行时绑定(Execution time):地址绑定延迟到运行期——进程运行时,每次访存都由 MMU 动态翻译(查页表/重定位寄存器)。这样进程可以在内存中搬移(紧凑 compaction、换页换出再换入),最灵活。现代操作系统(支持虚拟内存)都用执行时绑定
源码 ──编译──> 逻辑地址 ──绑定──> 物理地址

        ┌─────────┼─────────┐
        ▼         ▼         ▼
    编译时       加载时     执行时
  (绝对代码)  (加载改绝对)  (MMU 动态翻译)
   最死板        中等        最灵活(现代 OS)
  • 关键点:只有执行时绑定的进程才能被紧凑搬移(因为地址可动态重翻译);编译时/加载时绑定的进程一动就出错。

四、连续分配:把内存切成块

连续分配(Contiguous Allocation)是最朴素的模型——每个进程占用内存中一段连续的区域。按内存怎么切分,有三种方案:

  1. 单一连续分区:内存分两段——OS 区(常驻)+ 用户区(整体给一个用户进程)。早期单道批处理用。多道时加保护寄存器(base/limit)防止用户进程越界到 OS 区。简单,但同时只能一道用户程序,CPU/内存利用率低。
  2. 固定分区(Fixed/Multi-partition):内存预切成若干大小固定的分区(可等大或不等大),每分区容纳一道进程。进来的进程按大小放进合适的空分区。多道的基础。但产生内部碎片——分区比进程大时,分区内剩余空间浪费(小进程进大分区尤其严重),且无法被其他进程使用。
  3. 动态分区(Dynamic Partitioning):不预切,按进程实际需求恰好分配一块。分配时找一块够大的空闲区,切出进程需要的大小,剩余归还空闲池。无内部碎片(恰好够),但随进程频繁分配/释放,内存中出现大量小空洞——外部碎片:总空闲空间够,但没有一块连续的够大区域。

固定分区的内部碎片与动态分区的外部碎片是连续分配的根本矛盾——这就是后续分页(打散进程消除外部碎片)与伙伴系统/slab(治理小碎片)的动机。三种放置策略(首次/最佳/最坏适应)与紧凑 compaction 的细节见连续分配与碎片

下一步

理解了地址抽象与连续分配的三种方案后,下一步深入碎片的成因与治理——连续分配与碎片讲透固定/动态分区、三种放置策略、内部 vs 外部碎片、紧凑 compaction 的代价。