4.2 程序的装入和链接

本文最后更新于:2025年10月27日 上午

一、核心流程:用户源程序到可执行进程的三步转化

用户源程序需经过编译→链接→装入三个核心步骤,才能转化为内存中可执行的进程,三者的逻辑关系与执行顺序如下:

  1. 第一步:编译(Compile)

    • 执行主体:编译程序。
    • 核心操作:将用户编写的高级语言(如C、Java)源代码,翻译成由机器指令构成的若干个目标模块(每个模块对应源代码中的一个逻辑单元,如函数、类),目标模块地址通常从“0”开始(相对地址),且可能依赖外部库函数(如printf、math函数)。
  2. 第二步:链接(Link)

    • 执行主体:链接程序。
    • 核心操作:将编译生成的所有目标模块,以及程序运行所需的库函数模块(如系统库、第三方库),合并为一个完整的装入模块(可执行文件的前身)。
    • 关键任务:
      • 修正相对地址:将各目标模块的“局部相对地址”统一转换为装入模块的“全局相对地址”,确保模块间地址连续。
      • 解析外部符号:处理模块间的函数调用、变量引用(如模块A调用模块B的函数),将“符号名”替换为实际的地址偏移量,避免运行时“找不到符号”错误。
  3. 第三步:装入(Load)

    • 执行主体:装入程序。
    • 核心操作:将链接生成的装入模块加载到内存的指定物理地址空间,同时为进程创建进程控制块(PCB) ,记录进程的内存地址、状态等信息,最终形成可调度执行的进程。

二、程序的装入方式:三种地址变换策略

装入的核心是“将装入模块的相对地址转换为内存物理地址”,根据地址变换的时机不同,分为三种装入方式:

1. 绝对装入方式(Absolute Loading Mode)

  • 适用场景:单用户、单任务操作系统(如早期DOS),内存地址固定且唯一。
  • 核心逻辑:编译阶段直接生成“绝对物理地址”的目标代码(无需链接阶段调整地址),装入程序直接将目标模块加载到编译时指定的物理地址。
  • 特点:地址固定,无法适应多道程序环境(多个程序可能争抢同一物理地址),灵活性极差。

2. 可重定位装入方式(Relocation Loading Mode)

  • 适用场景:多道程序环境(多个进程共享内存),目标模块起始地址从“0”开始(相对地址)。
  • 核心逻辑:装入时一次性完成地址变换——根据内存空闲区的实际物理地址,将装入模块中所有相对地址(如指令中的“2500”)统一加上“空闲区起始地址”(如10000),转换为物理地址(如12500),后续运行过程中地址不再改变。
  • 示例:若装入模块起始地址为0,某指令“LOAD 1,2500”(相对地址2500),装入到物理地址10000开始的区域后,指令变为“LOAD 1,12500”(10000+2500)。
  • 特点:解决多道程序地址冲突问题,但地址变换仅在装入时执行,进程运行期间无法移动内存位置(若内存碎片需整理,进程需重新装入)。

3. 动态运行时装入方式(Dynamic Run-time Loading Mode)

  • 适用场景:需支持进程在运行中动态移动内存位置的场景(如内存紧缩、进程对换)。
  • 核心逻辑:装入时不进行地址变换,装入模块仍保留相对地址;进程运行过程中,每次访问指令或数据时,由硬件地址变换机构(如重定位寄存器) 实时将相对地址转换为物理地址(物理地址=相对地址+重定位寄存器中的进程基址)。
  • 示例:重定位寄存器存放进程基址10000,指令“LOAD 1,2500”运行时,实时计算物理地址10000+2500=12500,若进程移动到基址15000,只需更新重定位寄存器值,无需修改指令地址。
  • 特点:地址变换动态执行,进程可灵活移动内存位置,支持内存碎片整理和进程对换,是现代操作系统的核心装入方式,但依赖硬件支持。

三、程序的链接方式:三种模块合并时机

链接的核心是“合并模块并处理地址与符号”,根据链接时机不同,分为三种方式:

1. 静态链接(Static Linking)

  • 执行时机:程序运行前(编译、装入前)。
  • 核心逻辑:链接程序将所有目标模块、库函数模块一次性合并为一个完整的可执行文件(装入模块) ,后续不再拆分;可执行文件中包含程序运行所需的全部指令和数据,地址已完全确定。
  • 关键操作
    • 调整相对地址:将各模块的局部地址统一为可执行文件的全局地址(如模块A占0~1000,模块B占1001~2000)。
    • 解析外部符号:将“调用模块B函数”的符号引用,替换为模块B在可执行文件中的实际地址(如1500)。
  • 优点:运行时无需依赖外部模块,启动速度快。
  • 缺点
    • 可执行文件体积大(包含所有库函数代码),浪费磁盘和内存空间。
    • 若库函数更新(如修复漏洞),需重新编译链接生成新的可执行文件,维护成本高。

2. 装入时动态链接(Load-time Dynamic Linking)

  • 执行时机:程序装入内存的过程中(边装入边链接)。
  • 核心逻辑:编译仅生成“未链接的目标模块集合”,装入程序加载目标模块时,按需链接所需的库函数模块(如用到printf时才链接标准输入输出库),链接完成后再将整体装入内存。
  • 优点
    • 便于模块修改和更新:库函数更新后,无需重新编译用户程序,只需替换库文件即可(装入时会链接新库)。
    • 支持模块共享:多个进程可共享同一库模块(如多个程序共用标准库),节省内存空间。
  • 缺点:装入过程需同步执行链接,延长程序启动时间;若依赖的库文件缺失,装入会失败。

3. 运行时动态链接(Run-time Dynamic Linking)

  • 执行时机:程序运行过程中(需调用某模块时才链接)。
  • 核心逻辑:程序装入时仅加载主模块和已确定需立即使用的模块,其他模块(如条件分支中的函数、不常用的库)暂不装入;当程序执行到需调用某未链接模块时,由动态链接器实时加载该模块并完成链接,链接后继续执行。
  • 示例:程序主模块装入后,执行到“if(debug) 调用日志模块”时,若debug为真,才加载并链接日志模块;若debug为假,日志模块始终不装入内存。
  • 优点
    • 加快程序装入速度(仅加载必要模块)。
    • 节省内存空间(未用到的模块不装入),尤其适合包含大量可选功能的程序(如大型软件的插件)。
  • 缺点:运行时可能因模块缺失或链接错误导致程序崩溃;首次调用未链接模块时,存在短暂的链接延迟。

四、核心对比:装入方式与链接方式的关键差异

1. 装入方式对比

装入方式 地址变换时机 硬件依赖 灵活性(进程移动) 适用场景
绝对装入 编译时 单用户、单任务系统
可重定位装入 装入时(一次性) 低(需重新装入) 早期多道程序系统
动态运行时装入 运行时(实时) 有(重定位寄存器) 高(支持动态移动) 现代多道程序操作系统

2. 链接方式对比

链接方式 链接时机 可执行文件体积 模块共享能力 维护成本(库更新)
静态链接 运行前 高(需重新编译)
装入时动态链接 装入时 低(替换库文件)
运行时动态链接 运行时(按需) 低(替换库文件)

五、核心总结

  1. 流程关系:编译生成目标模块,链接合并为装入模块,装入加载为进程,三者依次执行,共同完成“源代码→可执行进程”的转化。
  2. 装入核心:地址变换时机决定灵活性,动态运行时装入因支持实时地址转换,成为现代OS的首选。
  3. 链接核心:链接时机决定资源利用率,运行时动态链接因按需加载模块,大幅节省内存和磁盘空间,是现代大型程序的主流链接方式。

4.2 程序的装入和链接
https://hellowydwyd.github.io/2025/10/27/4-2-程序的装入和链接/
作者
YuDong Wang
发布于
2025年10月27日
许可协议