跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++

Linux 动态链接与动态库加载深度解析

Linux 动态链接将符号解析推迟至运行时,通过内存映射实现多进程共享库代码。核心涉及动态链接器 ld-linux.so 初始化、位置无关代码 PIC 编译及 GOT/PLT 重定位机制。相比静态链接,动态链接显著降低磁盘与内存占用,支持热更新,但需处理版本兼容。理解 GOT/PLT 延迟绑定原理对调试符号未定义错误至关重要。

莫名其妙发布于 2026/3/23更新于 2026/5/129 浏览
Linux 动态链接与动态库加载深度解析

动态链接示意图

在 Linux 程序开发中,动态库是实现代码复用、减小程序体积、节省系统资源的核心技术。相较于静态链接将库代码直接合并到可执行程序的方式,动态链接把链接过程推迟到程序运行阶段,实现了多个进程共享同一份库代码,大幅提升了系统的资源利用率。

进程如何感知并加载动态库

动态库本质上是一个符合 ELF 格式的二进制文件。进程要使用动态库中的函数和数据,首先要让动态库被加载到内存并映射到进程的虚拟地址空间中。

进程对动态库的'可见性'

进程本身并不能直接识别磁盘上的动态库文件,而是通过操作系统的文件操作和内存映射机制实现对动态库的访问。当程序运行时,操作系统会根据程序的依赖信息,找到对应的动态库文件并打开,随后通过 mmap 系统调用将动态库的代码段、数据段等映射到进程的虚拟地址空间的共享区,让进程在虚拟地址层面能'看到'动态库的内容。

内存映射示意图

多进程共享动态库的实现

Linux 系统中,多个依赖同一动态库的进程,并不会在物理内存中加载多份库的副本,而是通过虚拟内存的页表映射机制实现共享:

  • 动态库被加载到物理内存后,操作系统会为其建立一份物理内存映射;
  • 每个使用该动态库的进程,其页表会将虚拟地址空间共享区的一段地址,映射到这份物理内存;
  • 进程对动态库的访问,最终都会转化为对同一份物理内存的访问,从而实现物理内存层面的库共享。

这种机制极大节省了物理内存资源,也是动态链接相比静态链接的核心优势之一。

多进程共享示意图

动态链接的核心工作原理

动态链接的核心是将符号解析和地址重定位从编译链接阶段推迟到程序运行阶段。编译器编译生成可执行程序时,并不会将动态库的函数地址、变量地址直接写入程序,而只是记录下依赖的动态库和符号信息;当程序运行时,动态链接器会完成符号的解析和地址的重定位,让程序能正确调用动态库中的函数。

程序运行前的动态链接准备

C/C++ 程序的入口并非我们编写的 main 函数,而是链接器提供的_start 函数,动态链接的初始化工作正是在_start 函数中完成的,其流程如下:

  • 设置堆栈:为程序创建初始的堆栈环境,保证函数调用的栈操作正常;
  • 初始化数据段:将初始化的全局变量、静态变量从可执行程序复制到内存,清零未初始化的 bss 段;
  • 加载动态链接器:调用系统接口加载 Linux 的动态链接器 ld-linux.so,由其负责后续的动态链接工作;
  • 解析库依赖:动态链接器读取可执行程序的动态段信息,解析出程序依赖的所有动态库(可通过 ldd 命令查看程序的库依赖);
  • 加载并映射动态库:按依赖顺序加载所有动态库,将其映射到进程的虚拟地址空间;
  • 调用__libc_start_main:完成信号处理、线程库初始化等工作后,最终调用 main 函数,将程序控制权交给用户代码。

其中,动态链接器是动态链接的核心执行者,Linux 下的 ld-linux.so 负责处理所有动态库的加载、符号解析和地址重定位。

$ ldd main.exe
linux-vdso.so.1 => (0x00007ffefd43f000)
libc.so.6 => /lib64/libc.so.6 (0x00007f533380b000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5333bd9000)

ldd 命令用于打印程序或者库文件所依赖的共享库列表。

ldd 输出示例

动态库的地址无关性:PIC 编译

动态库被加载到进程虚拟地址空间的地址是不固定的,操作系统会根据当前内存的使用情况,为动态库分配合适的虚拟地址区间。为了让动态库能在任意地址加载后都能正常运行,动态库必须采用位置无关代码(Position Independent Code,PIC)编译,也就是编译时添加 -fPIC 参数。

PIC 的核心是相对编址:动态库中的函数调用、变量访问,均使用相对于当前指令的偏移量进行编址,而非绝对地址。这样无论动态库被加载到虚拟地址空间的哪个位置,只要根据偏移量计算,就能正确找到目标函数或变量,实现地址无关性。

注意:我们的程序怎么和库具体映射起来的?动态库也是一个文件,要访问也是要被先加载,要加载也是要被打开的。让我们的进程找到动态库的本质:也是文件操作,不过我们访问库函数,通过虚拟地址进行跳转访问的,所以需要把动态库映射到进程的地址空间中。

PIC 原理图

运行时的地址重定位:从符号到实际地址

当动态库被加载到进程的虚拟地址空间后,其虚拟起始地址就被确定了。动态链接器会完成两步核心工作,实现程序对动态库符号的访问:

  • 符号解析:根据可执行程序记录的符号名(如函数名、变量名),在已加载的动态库中找到对应的符号;
  • 地址计算:结合动态库的虚拟起始地址和符号在库中的相对偏移量,计算出符号的实际虚拟地址;
  • 地址重定位:将计算出的实际虚拟地址写入程序的指定位置,让程序能通过该地址调用访问动态库函数。

简单来说,程序调用动态库函数的地址,最终是动态库起始虚拟地址 + 函数在库中的相对偏移量,这也是进程能正确调用动态库函数的关键。

重定位示意图

GOT/PLT:动态链接的核心实现机制

程序的代码段在内存中是只读的,无法直接在代码段中修改函数调用的地址,因此 Linux 通过全局偏移量表(GOT)和过程链接表(PLT)解决这一问题,实现了只读代码段的动态地址重定位,也是 PIC 的核心实现。

全局偏移量表(GOT)

GOT 是位于程序数据段(.data)的一片可读写内存区域,其核心作用是存放动态库符号的实际虚拟地址。数据段的可读写属性,让动态链接器能在程序运行时,动态修改 GOT 表中的地址值。

  • GOT 表中的每一项,对应一个程序需要访问的动态库符号(函数或变量);
  • 编译时,编译器会为每个动态库符号在 GOT 表中分配一个条目,此时条目值为无效地址;
  • 程序运行时,动态链接器会将解析后的符号实际虚拟地址,写入 GOT 表对应的条目;
  • 程序访问动态库符号时,会先从 GOT 表中读取实际地址,再通过该地址进行访问。

同时,GOT 表与动态库的相对位置是固定的,程序可以通过 CPU 的相对寻址找到 GOT 表,保证了地址无关性。需要注意的是,每个进程都有自己独立的 GOT 表,因为不同进程的动态库加载地址可能不同,进程间无法共享 GOT 表。

GOT 结构图

过程链接表(PLT):延迟绑定优化

动态链接器如果在程序启动时,就对所有动态库符号进行解析和重定位,会增加程序的启动时间——因为程序运行过程中,很多动态库函数可能一次都不会被调用。为了解决这一问题,Linux 引入了延迟绑定(Lazy Binding)机制,其核心实现就是过程链接表(PLT)。

PLT 是一段位于程序代码段的桩代码(stub code),每个动态库函数对应一个 PLT 条目,其工作流程分为第一次调用和后续调用:

(1)函数第一次被调用

  • 程序调用动态库函数时,首先跳转到该函数对应的 PLT 条目;
  • PLT 条目会读取 GOT 表中对应的条目,此时 GOT 表中的值指向 PLT 条目的下一条指令;
  • 该指令会调用动态链接器的符号解析函数,动态链接器会解析出函数的实际虚拟地址,并将其写入 GOT 表对应的条目;
  • 动态链接器跳转到函数的实际地址,执行函数逻辑。

(2)函数后续被调用

  • 程序再次跳转到 PLT 条目时,会直接读取 GOT 表中的值,此时该值已经是函数的实际虚拟地址;
  • 程序直接跳转到该地址执行函数,不再经过动态链接器的解析,实现了调用的优化。

延迟绑定将符号解析的工作推迟到函数第一次被调用时,大幅减少了程序的启动时间,是 Linux 动态链接的重要优化手段。

思路是:GOT 中的跳转地址默认会指向一段辅助代码,它也被叫做桩代码/stub。在我们第一次调用函数的时候,这段代码会负责查询真正函数的跳转地址,并且去更新 GOT 表。于是我们再次调用函数的时候,就会直接跳转到动态库中真正的函数实现。

PLT 调用流程

库间依赖的处理

动态库之间也存在依赖关系(如库 A 依赖库 B),其处理方式与程序依赖动态库一致:

  • 动态链接器会按依赖顺序加载所有的动态库,包括库的依赖库;
  • 每个动态库也都有自己独立的 GOT 表,动态链接器会依次解析所有库间的符号依赖,完善各个 GOT 表;
  • 库间的函数调用,同样通过 GOT 表 + 相对偏移的方式实现,保证了库间调用的地址无关性。

所有动态库的 GOT 表完善后,整个程序的动态链接过程才算完成,程序才能正常运行。

由于 GOT 表中的映射地址会在运行时去修改,我们可以通过 gdb 调试去观察 GOT 表的地址变化。在这里我们只用知道原理即可,大家有兴趣的可以查阅相关文档。

动态链接与静态链接的核心对比

为了更清晰地理解动态链接的优势和特点,我们将其与静态链接做核心维度的对比,如下表所示:

对比维度静态链接动态链接
链接时机编译链接阶段程序运行阶段
可执行程序体积大,包含所有库代码小,仅记录库依赖和符号信息
内存占用高,每个进程加载一份库代码低,多进程共享物理内存中的库副本
磁盘占用高,多个程序包含重复库代码低,系统中仅存一份动态库文件
程序更新需重新编译链接整个程序仅更新动态库文件,无需重新编译程序
运行性能略高,无运行时链接开销略低,存在启动时的动态链接开销
兼容性好,可执行程序独立运行依赖库版本,库版本不兼容可能导致程序崩溃

静态链接的出现,提高了程序的模块化水平。对于一个大的项目,不同的人可以独立地测试和开发自己的模块。通过静态链接,生成最终的可执行文件。我们知道静态链接会将编译产生的所有目标文件,和用到的各种库合并成一个独立的可执行文件,其中我们会去修正模块间函数的跳转地址,也被叫做编译重定位(也叫做静态重定位)。

而动态链接实际上将链接的整个过程推迟到了程序加载的时候。比如我们去运行一个程序,操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,但是无论加载到什么地方,都要映射到进程对应的地址空间,然后通过 GOT 方式进行调用(运行重定位,也叫做动态地址重定位)。

可以看到,动态链接以微小的运行性能开销,换来了系统资源的高效利用和程序的灵活更新,这也是 Linux 系统中绝大多数程序都采用动态链接的原因。

那为什么编译器默认不使用静态链接呢?静态链接会将编译产生的所有目标文件,连同用到的各种库,合并形成一个独立的可执行文件,它不需要额外的依赖就可以运行。照理来说应该更加方便才对是吧?静态链接最大的问题在于生成的文件体积大,并且相当耗费内存资源。随着软件复杂度的提升,我们的操作系统也越来越臃肿,不同的软件就有可能都包含了相同的功能和代码,显然会浪费大量的硬盘空间。

这个时候,动态链接的优势就体现出来了,我们可以将需要共享的代码单独提取出来,保存成一个独立的动态链接库,等到程序运行的时候再将它们加载到内存,这样不但可以节省空间,因为同一个模块在内存中只需要保留一份副本,可以被不同的进程所共享。

总结

动态链接与动态库加载是 Linux 操作系统和程序编译链接体系的核心技术,其核心是将符号解析和地址重定位推迟到程序运行阶段,通过操作系统的内存映射、动态链接器的符号解析、GOT/PLT 的地址重定位,实现了代码的共享和程序的高效运行。

本文从进程与动态库的关联出发,讲解了动态库的加载和共享机制,随后深入分析了动态链接的工作流程、PIC 编译的地址无关性,以及 GOT/PLT 的核心实现,最后对比了动态链接与静态链接的差异。理解这些底层机制,不仅能帮助我们解决开发中遇到的库依赖、符号未定义、库版本兼容等问题,更能让我们对 Linux 程序的运行流程有更深刻的认识。在实际 Linux 开发中,动态库的制作(-fPIC -shared)、库依赖的管理(ldd、LD_LIBRARY_PATH)、动态链接的调试(objdump、readelf)都是必备的技能,而这些技能的基础,正是对动态链接和动态库加载底层原理的理解。掌握这些内容,能让我们在 Linux 开发中更加游刃有余。

目录

  1. 进程如何感知并加载动态库
  2. 进程对动态库的“可见性”
  3. 多进程共享动态库的实现
  4. 动态链接的核心工作原理
  5. 程序运行前的动态链接准备
  6. 动态库的地址无关性:PIC 编译
  7. 运行时的地址重定位:从符号到实际地址
  8. GOT/PLT:动态链接的核心实现机制
  9. 全局偏移量表(GOT)
  10. 过程链接表(PLT):延迟绑定优化
  11. 库间依赖的处理
  12. 动态链接与静态链接的核心对比
  13. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • LangBot:企业级即时通讯 AI 机器人平台介绍
  • 飞书 CLI 开源实操:AI 接管办公全流程指南
  • Java 入门:JDK 和 IDEA 下载安装及环境搭建
  • Next-AI-Draw.io 开源 AI 绘图工具自托管部署指南
  • C++ 手写 List 容器实战:双向链表原理与源码实现
  • AI+ 低代码:医药行业数字化转型痛点与落地逻辑
  • VMware Workstation Pro 17 中 Ubuntu 24.04 无法复制粘贴的修复方案
  • Faster-Whisper 本地部署及实时语音转文本方案
  • VSCode GitHub Copilot 插件无法加载模型的解决方案
  • 2026 年高校论文 AI 率新规:哪些学校明确 AIGC 检测要求
  • 基于 Python 实现抖音私信自动回复机器人
  • 基于 DeepSeek 和 Cursor 构建智能代码审查工具实战
  • 大模型入门指南:从基本原理到应用实践
  • AirSim 无人机仿真与深度强化学习路径规划实战
  • Milvus 实战:Attu 可视化安装与 Python 整合指南
  • OpenClaw 龙虾机器人本地部署与进阶配置指南
  • 以太坊关键漏洞被发现,开发者获赏 1272 万元
  • 飞算 JavaAI 智能开发助手功能详解与安装实践
  • Spring AI Alibaba 快速入门指南
  • 主流 AI 工具生成 Word 文档指南:功能、场景与实操技巧

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online