动态链接(8/13)

news2025/1/14 18:35:23

静态链接的缺点:生成的可执行文件体积较大,当多个程序引用相同的公共代码时,这些公共代码会多次加载到内存,浪费内存资源。

为了解决这个问题,动态链接对静态链接做了一些优化:对一些公用的代码,如库,在链接期间暂不链接,而是推迟到程序运行时再进行链接。这些在程序运行时才参与链接的库被称为动态链接库。程序运行时,除了可执行文件,这些动态链接库也要跟着一起加载到内存,参与链接和重定位过程,否则程序可能就会报未定义错误,无法运行。

动态链接的好处是节省了内存资源:加载到内存的动态链接库可以被多个运行的程序共享,使用动态链接可以运行更大的程序、更多的程序,升级也更加简单方便。

在 Windows 下解压一个软件安装包,里面的 .dll 后缀的文件就是动态链接库,需要和可执行文件一起安装到系统中。程序在运行前会首先把它们加载到内存,链接成功后程序才能运行。

在 Linux 环境下,动态库文件的后缀为 .so。

gcc -fPIC -shared add.c sub.c mul.c div.c -o libtest.so
gcc main.c libtest.so
cp libtest.so /usr/lib/ # 程序就可以运行了

在上面的程序中,可执行文件 a.out 是动态链接生成的,所以在运行 a.out 之前,libtest.so 这个动态链接库要放到 /lib、/usr/lib 等系统默认的库路径下,否则 a.out 就会链接失败,无法正常运行。

在 Linux 环境下,当我们运行一个程序时,操作系统首先会给程序 fork 一个子进程,接着动态链接器被加载到内存,操作系统将控制权交给动态链接器,让动态链接器完成动态库的加载和重定位操作,最后跳转到要运行的程序。

动态链接器本身也是一个动态库(/lib/ld-linux.so文件),动态链接器被加载到内存后,会首先给自己重定位,然后才能运行。像这种自己给自己重定位然后自动运行的行为,一般称为自举。

动态链接器解析可执行文件中未确定的符号及需要链接的动态库信息,将对应的动态库加载到内存,并进行重定位操作。这个过程其实和静态链接的重定位过程一样,只不过推迟到了运行阶段而已。重定位结束后,程序中要引用的所有符号都有了地址和定位,动态链接器将控制权交给要执行的程序,跳转到该程序运行。

在这里插入图片描述
动态链接需要考虑的一个重要问题是加载地址。静态链接时,加载地址等于链接地址,这个地址是固定的。动态链接过程中,类似静态链接的重定位,动态链接库被加载到内存后,目标文件的起始地址也发生了变化,需要重定位。一个可执行文件对动态链接库的符号引用,要等动态链接库加载到内存后地址才能确定,然后对可执行文件中的这些符号修改即可。

main() 函数调用了 add() 函数,但 add() 函数的地址还不能确定,等到 libtest.so 加载到内存后,add() 函数的地址才能确定下来。加载器通过动态链接、重定位操作,更新了符号表中 add() 函数的实际地址,并修正 main() 函数指令中引用 add() 函数的地址,然后程序才可以正常运行。

这种装载时重定位的操作,虽然解决了可执行文件中绝对地址的引用问题,但也带来了另外一个问题:对于每个进程,动态库被加载到了内存的不同地址,也只能被进程自身共享,无法在多个进程间共享,无法节省内存,违背了动态库的设计初衷。如果有一种方法,将动态库设计成无论放到哪里,都可以执行,而且可以被多个进程共享,那么这个问题就迎刃而解了。

与地址无关的代码

如果想让我们的动态库放到内存的任何位置都可以运行,都可以被多个进程共享,一种比较好的方法是将我们的动态库设计成与地址无关的代码。将指令中需要修改的部分(如绝对地址符号的引用)分离出来,剩余的部分就和地址无关了。需要被修改的指令(符号)和数据在每个进程中都有一个副本,互不影响各自的运行。

编译代码时加上 -fPIC 参数(Position-Independent Code)就可以实现代码与地址无关:把这段代码放在内存中的任何位置,都无须重定位,直接运行即可(使用相对跳转指令代替对绝对地址的访问)。

全局偏移表

在动态库的设计中,对于模块内的符号相互引用,通过相对寻址很容易实现代码与地址无关。但是当动态库作为第三方模块被不同的应用程序引用时,库中的一些绝对地址符号(如函数名)将不可避免地被多次调用,需要重定位。动态库中的这些绝对地址符号,如何能做到同时被不同的应用程序引用呢?

每个应用程序将引用的动态库(绝对地址)符号收集起来,保存到一个表中,这个表用来记录各个引用符号的地址。当程序在运行过程中需要引用这些符号时,可以通过这个表查询各个符号的地址。这个表被称为全局偏移表(Global Offset Table,GOT)。

在一个可执行文件中,其引用的动态库中的绝对地址符号会被分离出来,单独保存到 GOT 表中,GOT 表以 section 的形式保存在可执行文件中,这个表的地址在编译阶段已经确定了。当程序运行需要引用动态库中的函数时,会将动态库加载到内存,根据动态库被加载到内存中的具体地址,更新 GOT 表中的各个符号(函数)的地址。等下次该符号被引用时,程序可以直接跳到 GOT 表查询该符号的地址,因为 GOT 表在可执行文件中的位置是固定不变的,所以程序中访问 GOT 表的指令也是固定不变的,唯一需要变化的是:动态库加载到内存后,库中的各个函数的位置确定,在 GOT 表中实时更新各个符号在内存中的真实地址就可以了。

这样做的好处是:在内存中只需要加载一份动态库,当不同的程序运行时,只要修改各自的 GOT 表,它们引用的符号都可以指向同一份动态库。

延迟绑定

动态连接通过使用与地址无关这一技术,加载到内存任意地址都可以运行。与地址无关这一技术在 ARM 平台可以使用相对寻址来实现。ARM 相对寻址的本质其实就是寄存器间接寻址,只不过基址换成了 PC 而已,访问效率还是比较低的,包括程序运行之前的动态链接和重定位操作,也会对程序的及时响应和性能造成一定的影响。可执行文件一般都采用延迟绑定:程序在运行时,并不急着把所有的动态库都加载到内存中并对它们进行重定位。当动态库中的函数第一次被调用时,才会把用到的动态库加载到内存并进行重定位。

C 标准库起始就是以动态库的封装形式保存在 Linux 系统中的,不同的应用程序都会调用 printf() 函数,当它们在内存中运行时,只需要加载一份 printf() 函数代码到内存就可以了。各个应用程序在引用 printf 这个符号时,就会启动链接器,将这份代码映射到各自进程的地址空间,更新各自 GOT 表中 printf() 函数的实际地址,然后通过查询 GOT 表找到 printf() 在内存中的实际地址,就可通过间接访问跳转执行。

共享库

现在大多数软件都是采用动态链接的方式开发的,不仅可以节省内存空间,升级维护也比较方便。在发布软件包时,可执行文件及其以来的动态链接共享库被一起打包发布,如果你依赖的是系统默认自带的共享库,如 C 标准库,则不需要跟软件一起打包。程序安装时,可执行文件会复制到 Linux 系统的默认路径下,如 /bin、/sbin、/usr/bin、/usr/local/bin 等,这些路径由环境变量 PATH 管理和维护。可执行文件依赖的共享库一般要放到库的默认路径下面:如 /lib、/usr/lib 等。当程序运行时,动态链接器首先被加载到内存运行,动态链接器会分析可执行文件,从可执行文件的 .dynamic 段中查询该程序运行需要依赖的动态共享库,然后到库的默认路径下查找这些共享库,加载到内存中并进行动态链接,链接成功后将 CPU 的控制权交给可执行程序,程序就可以正常运行了。

动态链接器在查找共享库的过程中,除了到系统默认的路径下查找,也会到用户指定的一些路径下去查找,用户可以在 /etc/ld.so.conf 文件中添加自己的共享库路径。为了减少每次查找文件的时间消耗,/etc/ld.so.conf 修改后,可以使用 ldconfig 命令生成一个缓存 /etc/ld.so.cache 以提高查找效率。每当我们新增、删除或修改共享库的路径时,使用 ldconfig 更新一下缓存就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/872029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Titanic--细节记录三

目录 image sklearn模型算法选择路径图 留出法划分数据集 ‘留出’的含义 基本步骤和解释 具体例子 创造一个数据集 留出法划分 预测结果可视化 分层抽样 设置方法 划分数据集的常用方法 train_test_split 什么情况下切割数据集的时候不用进行随机选取 逻辑回归…

Linux系统下安装Git软件

环境说明 Linux系统:CentOS 7.9 安装GCC等 JDK版本:jdk-8u202-linux-x64.tar.gz Maven版本:apache-maven-3.8.8-bin.tar.gz 在以上环境下安装Git(git-2.41.0.tar.gz)软件。 查看是否安装Git软件 查看Git版本&#…

python代码一行过长怎么办,python中一行代码太长

这篇文章主要介绍了python每行代码长度不能超过100个字符,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。 30行python代码实现豆瓣电影排行爬取 实现过程完整代码 今天我们想实…

四、Linux常用命令(一)

1、ls命令 (1)ls: list,列表。 (2)作用:使用列表把当前文件夹下所有文件显示出来。 (3)ls -a: 显示所有文件,包括隐藏文件。 (3)ls -l: 显示文件的详细信息。 (3)显示所有文件且显示详细信息,以下四种方式都可以。 ls -a -l…

腾讯云2核2g轻量应用服务器能容纳多少人?

腾讯云轻量应用服务器2核2g能容纳多少人?轻量应用服务器2核2g配置自带4M公网带宽,以网站应用为例,假设优化后的网页平均大小为60KB,2核2G4M带宽轻量服务器可以支持10个并发数,即同时10个人在1秒内同时打开网站&#xf…

Visual Studio 2019 解决scanf函数报错问题

前言 Visual Studio 2019 解决scanf函数报错问题 博主博客链接:https://blog.csdn.net/m0_74014525 关注博主,后期持续更新系列文章 *****感谢观看,希望对你有所帮助***** 系列文章 第一篇:Visual Studio 2019 详细安装教程&…

软工导论知识框架(八)面向对象设计风格

一.面向对象实现 把面向对象设计结果翻译成面向对象程序测试并调试面向对象的程序 二.程序设计语言 所有语言都可完成面向对象实现,但效果不同。 使用非面向对象语言编写面向对象程序,则必须由程序员自己把面向对象概念映射到目标程序中。 1.将来能够占…

Flutter源码分析笔记:Widget类源码分析

Flutter源码分析笔记 Widget类源码分析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/article/details/132259681 【介绍】&#x…

JZ37序列化二叉树

题目地址:序列化二叉树_牛客题霸_牛客网 题目回顾: 解题思路: 首先,序列化就是将二叉树的节点值放入一个字符串中,这里可以按照前序遍历的思路来进行操作,谦虚遍历是:根左右的情况,…

Java 集合详解

目录 1.集合体系结构 2.Collection集合 2.1 Collection集合 2.1.1 Collection基本方法 2.1.2 Collection遍历方式 2.1.2.1 迭代器遍历 2.1.2.2 增强for循环 2.1.2.3 Lambda表达式 3.List集合 3.1 List集合的基本方法 3.2 List集合的遍历方式 4.数据结构 4.1 数据结…

设计模式之七:适配器模式与外观模式

面向对象适配器将一个接口转换成另一个接口,以符合客户的期望。 // 用火鸡来冒充一下鸭子class Duck { public:virtual void quack() 0;virtual void fly() 0; };class Turkey { public:virtual void gobble() 0;virtual void fly() 0; };class TurkeyAdapter :…

52.Linux学习day02 基础命令详解2

目录 Linux常见的基础命令 1.cp 2.mv 3.rm 4.find 5.grep 6.管道 | 7.wc 8.su 9.关机与重启 10.runleve Linux常见的基础命令 1.cp 用于复制文件或目录 使用 cp 命令的基本格式如下: cp [选项] 源文件 目标文件或目录选项:cp 命令支持一些选…

Django模板

文章目录 模板Template实践 模板Template 在Django框架中,模板是可以帮助开发者快速生成呈现给用户页面的工具 模板的设计方式实现了我们MVT中VT的解耦(M: Model,V:View,T:Template),VT有着N:M的关系,一个V可以调用任…

接口自动化测试-Requests模块实战详解,一篇打通...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 什么是requests&a…

Postman下载教程

目录 下载 安装 注意事项 看到很多小伙伴在问 Postman 下载的相关问题,花时间整理了下,下面教新入门的小伙伴如何去下载 Postman。 开始前我们可以先了解下:Postman 简介 下载 第一步:进入 Postman 官网 首先,我…

机器学习终极指南:特征工程(02/2) — 第 -2 部分

接上文:机器学习终极指南:特征工程(01/2) 五、处理不平衡数据 处理不平衡的数据是机器学习的一个重要方面。不平衡数据是指目标变量的分布不均匀,并且与另一个类相比,一个类的代表性不足。这可能导致模型…

NeuS环境配置

TypeError: Descriptors cannot not be created directly. pip install --upgrade protobuf pip install --upgrade tensorboard运行 python exp_runner.py --mode train --conf ./confs/wmask.conf --case bmvs_bearRuntimeError: indices should be either on cpu or on th…

二级考python和c语言哪个好,计算机二级python和c

大家好,小编来为大家解答以下问题,二级python和二级c语言哪个更吃香一些,二级python和二级c语言哪个更吃香一点,今天让我们一起来看看吧! 计算机二级貌似只是在校园里的自嗨,出来工作后并没有觉得这个证书有…

分布式事务原子性-TCC

一、分布式事务-原子性 随着数据量不断的变大,单机所能处理的数据总归是有上限的,所以现阶段分布式的应用系统在各个领域中遍地生花。接下来我们就来聊一下分布式系统中非常重 要的特性分布式事务的原子性功能。之前没有了解过分布式相关知识的读者可以…

一文带你迅速了解下Spring中的AOP

1. 什么是AOP? AOP(Aspect Oriented Programming):面向切面编程。 面向切面编程是一种思想,其实就是对某一类事情进行统一的处理。而 SpringAOP就是一种AOP的具体实现的框架。这就好比 IOC 和 DI 一样的关系。 上述就是对登录功…