4.2 程序的装入和链接
本文最后更新于:2025年10月27日 上午
一、核心流程:用户源程序到可执行进程的三步转化
用户源程序需经过编译→链接→装入三个核心步骤,才能转化为内存中可执行的进程,三者的逻辑关系与执行顺序如下:
-
第一步:编译(Compile)
- 执行主体:编译程序。
- 核心操作:将用户编写的高级语言(如C、Java)源代码,翻译成由机器指令构成的若干个目标模块(每个模块对应源代码中的一个逻辑单元,如函数、类),目标模块地址通常从“0”开始(相对地址),且可能依赖外部库函数(如printf、math函数)。
-
第二步:链接(Link)
- 执行主体:链接程序。
- 核心操作:将编译生成的所有目标模块,以及程序运行所需的库函数模块(如系统库、第三方库),合并为一个完整的装入模块(可执行文件的前身)。
- 关键任务:
- 修正相对地址:将各目标模块的“局部相对地址”统一转换为装入模块的“全局相对地址”,确保模块间地址连续。
- 解析外部符号:处理模块间的函数调用、变量引用(如模块A调用模块B的函数),将“符号名”替换为实际的地址偏移量,避免运行时“找不到符号”错误。
-
第三步:装入(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. 链接方式对比
| 链接方式 | 链接时机 | 可执行文件体积 | 模块共享能力 | 维护成本(库更新) |
|---|---|---|---|---|
| 静态链接 | 运行前 | 大 | 无 | 高(需重新编译) |
| 装入时动态链接 | 装入时 | 中 | 有 | 低(替换库文件) |
| 运行时动态链接 | 运行时(按需) | 小 | 有 | 低(替换库文件) |
五、核心总结
- 流程关系:编译生成目标模块,链接合并为装入模块,装入加载为进程,三者依次执行,共同完成“源代码→可执行进程”的转化。
- 装入核心:地址变换时机决定灵活性,动态运行时装入因支持实时地址转换,成为现代OS的首选。
- 链接核心:链接时机决定资源利用率,运行时动态链接因按需加载模块,大幅节省内存和磁盘空间,是现代大型程序的主流链接方式。
4.2 程序的装入和链接
https://hellowydwyd.github.io/2025/10/27/4-2-程序的装入和链接/