首页 > 其他分享 >动态链接库的实现原理是什么?

动态链接库的实现原理是什么?

时间:2024-07-20 09:26:11浏览次数:18  
标签:动态 函数 实现 程序 got 动态链接库 原理 foo 全局变量

今天简单聊聊动态链接库的实现原理。

假设有这样两段代码,第一段代码定义了一个全量变量a以及函数foo,函数foo中引用了下一段代码中定义的全局变量b。

图片

第二段代码定义了全局变量b以及main函数,同时在main函数中调用了第一个模块中定义的函数foo。

接下来编译器出场,编译器会把这个两个源文件编译成对应的目标文件。

目标文件中主要有两部分,代码段和数据段,这两部分里面分别包含什么内容呢?

我们定义的全局变量会被放到数据段,代码被编译生成的二进制指令会被放到代码段,第二个目标文件也一样。

图片

注意看第一段代码,这里引用了一个其它模块定义的全局变量b,这一信息记录在第一个目标文件,第二段代码引用了其它模块定义的函数foo,这一信息记录在第二个目标文件。

注意看第一段代码,这里定一个全局变量a和函数foo,我们记录下来,第二段代码定义了全局变量b和函数main,同样记录下来。

图片

接着我们开始一个叫做连连看的游戏。

第一个模块引用了变量b,变量b的定义可以在第二个模块找到。

第二个模块引用了函数foo,foo的定义可以在第一个模块找到。

这个过程叫做符号解析。

图片

这里看到的引用以及定义的符号保存在所谓的符号表中。

而如果第二个模块引用了一个叫做bar的变量,链接器翻遍所有其它模块都没找到bar这个符号的定义,而只找到了一个叫做foo的定义,这时链接器就会报一个叫做符合未定义的错误,这个错误写c/c++的程序员一定不陌生。

图片

接下来链接器会把数据段合并到一起,代码段合并到一起并确定符号的内存地址,这个过程叫做重定位。

了解了这些就可以开始讲动态库的实现原理了,动态库又叫做共享库,我们的问题是,动态库是怎么实现可以被程序之间共享的呢?

假设现在有两个运行的程序和一个动态库liba. so,动态库中定了一个全局变量a,第一个程序把变量a修改为了10。

图片

然后第二个程序开始运行,第二个程序也使用该动态库,然后把全局变量a修改为了20。

图片

这是第一个程序运行一段时间后决定打印变量a,这时你会惊讶的发现变量a从10变成了20,但是为什么。

原因就是这两个程序共享了同一个数据段,所以一个程序对数据的修改对另一人程序是可见的,因此动态库中的数据段不能共享,每个程序需要有自己的数据段。

现在数据的问题解决了,我们来看函数。

假设动态库liba.so需要引用外部定义的foo函数,由于程序1和程序2都使用了该动态库,因此必须定义出foo函数。

我们知道函数调用最终会被编译器翻译成call机器指令后跟函数地址。

图片

接下来我们需要解析出foo函数的地址到底是什么,这就是刚才我们提到的重定位,只不过动态库将这一过程推迟到了运行时。

由于程序1的foo函数位于内存地址0x123这个位置,因此链接器将call指令后的地址修正为0x123。

这时CPU执行这条call指令就能正确的跳转到第一个程序的foo函数。

图片

而第二个程序的foo函数为内存地址0x456这个位置,接下来第二个程序开始运行,CPU开始执行foo函数,由于第二个程序的foo函数在0x456,因此我们希望CPU能跳转到这里,但由于动态库中call指令后跟的是0x123这个内存地址,因此CPU执行foo函数时依然会跳转到第一个程序的foo函数。

图片

这时系统就出现了错误。

问题出在了哪里呢?

主要是call这条机器指令,这条指令后跟了一个绝对的内存地址,而不要忘了,这条指令或者说动态库是要被各个程序共享的,显然我们不能直接使用绝对地址。

该怎么办呢?

计算机中所有问题都可以通过增加一个中间层来解决。

图片

这样我们就摒弃了直接调用,而采用间接调用。

而我们这里对函数的讨论对于全局变量的应用也是一样的道理,全局变量的使用也存在同样的问题,只不过是从函数调用变成了内存读写,解决问题的方法一样,我们从直接应用改为间接引用。

接下来我们依然以函数调用为例来讲解。

那么这个中间层到底是什么呢?

答案就是got。

还记得刚才提到的每个程序都有自己的数据区吗,这个got段就属于数据区的一部分。

图片

got中有什么呢?got中记录了引用的全局变量或者函数的地址,在程序运行时链接器会找到foo的内存地址,然后填到got表中,这样通过查got表我们就能知道函数foo的内存地址了。

接下来的问题就是当CPU调用foo函数时怎么才能知道got表在哪里呢?

注意刚提到每个程序都有自己的数据区,实际上对于动态库来说也有自己的代码区。

我们现在只需要知道每个程序运行在自己的地址空间中,这些地址空间最终会被映射到真正的物理内存,动态库中的数据区会被映射到不同的内存区域,但代码段会被映射到同一段物理内存中,从而实现共享的目的。

图片

接下来我们重点看进程地址空间中的动态库布局。

注意看,动态库的数据区和代码区总是相邻的,也就是代码区和got段的相对位置总是不变的,而不管动态库被放到了哪个位置。

多个程序也一样,也就是代码区和数据区的相对位置总是固定的,这个相对位置在编译时编译器就能确定。

图片

现在foo会被编译成call指令,而程序在加载时链接器会向got段中写入foo的内存地址,显然两个程序的foo地址是不一样的。

接下来CPU开始执行第一个程序的call指令,此时CPU会做一个相对跳转,这个跳转距离是编译器确定的,CPU会跳转到got表,然后查找foo的地址发现是0x123,然后开始执行0x123这个位置的函数。

图片

而如果CPU执行第二个程序中的foo函数,那么CPU同样会进行相对跳转,这不过这次跳转到的是第二个程序的got表,然后发现foo的地址是0x456,然后开始执行第二个程序中的foo函数。

图片

这样我们就实现了执行同一个指令但却会跳转到不同地址的目的,从而在不改动动态库代码的前提先实现共享。

而如果一个动态库中引用了很多外部函数会怎么样呢?

这样程序在启动时链接器不得不对所有函数进行重定位,因此会拖慢程序启动速度。

而我们知道一个程序中不是所有的函数都会被调用到,经常调用的都是少数几个函数,为了利用这一点编译链接系统使用procedure linkage table, plt来推迟重定位这个过程,也就是程序在启动时不进行函数重定位,而是推迟到真正调用函数时,没用调用过的函数根本就不进行重定位,从而加快程序启动速度。

从这个一过程我们可以看到动态库的这种间接调用实际上会对程序性能有一定影响,但相对于动态库带来的好处与便捷,这点影响可以忽略不计。

这样,不管动态库被加载到内存的哪个位置都能正确被各个程序共享。

动态库的这个特性被称之为位置无关代码,简称position-independent code, pic,这就是为什么你在编译生成动态库时要加上pic编译选项的原因。

图片

希望这篇对大家理解动态库有帮助。

动态链接库的实现原理是什么?

标签:动态,函数,实现,程序,got,动态链接库,原理,foo,全局变量
From: https://blog.csdn.net/lxy1290439047/article/details/139767077

相关文章

  • 深入浅出 Spring AOP:从原理到实战
    深入浅出SpringAOP:从原理到实战在日常开发中,我们常常需要在不改变原有代码的情况下,为某些方法添加额外的功能,比如日志记录、权限控制、事务管理等。SpringAOP(Aspect-OrientedProgramming,面向切面编程)正是为了解决这一问题而生的。今天,我们将深入探讨SpringAOP的原理,并通过......
  • 【视频讲解】PCA主成分分析原理及R语言2实例合集|附代码数据
    原文链接:https://tecdat.cn/?p=37034原文出处:拓端数据部落公众号 分析师:RuoyiXu在数据分析的浩瀚宇宙中,我们时常面对多变量的数据海洋。这些变量虽然信息丰富,却也给处理带来了巨大挑战:工作量激增,而关键信息却可能淹没在繁杂的数据之中。为了有效减少指标数量同时尽可能保留原......
  • 大话NXP的PMIC_FS26原理和参数一篇搞定
    总体描述FS26汽车安全系统基础芯片(SBC)家族的设备设计支持入门级和中端安全微控制器,例如S32K3系列。FS26设备具有多个电源供应,并具备与其他面向汽车电气化的微控制器灵活合作的能力。FS26的应用包括动力传动、底盘、安全和低端网关技术等多个领域。该家族的设备包括多个......
  • 基于SpringBoot+Vue+uniapp的公考客观题复习系统的详细设计和实现(源码+lw+部署文档+
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • Jmeter实现本地文件的读写操作-将响应结果写入到本地Excel中
    一、环境准备1、引入操作EXcel文件的包2、安装JMeter:确保已安装JMeter。Java环境:确保系统中已安装JDK,并配置了JAVA_HOME环境变量。二、脚本准备1.配置JMeter测试计划创建线程组添加线程组:右键点击“测试计划(TestPlan)”,选择“添加(Add)”->“线程(Threads(Use......
  • 【C语言】实现一个通讯录,一步一步详细讲解,小白也能看!!!
    目录设计思路代码实现 代码仓库 设计思路1.通讯录存放的信息这个通讯录保存的信息包括:名字,年龄,性别,电话,住址。2.通讯录的功能1.通讯录可以存放100个人的信息。2.增加联系人3.删除联系人4.修改联系人5.查询联系人6.显示所有人3.文件规划我们准......
  • 手写数字识别——KNN模型实现
    MNIST手写数字识别        MNIST手写数字数据库有一个包含60,000个示例的训练集和一个包含10,000个示例的测试集。每个图像高28像素,宽28像素,共784个像素。每个像素取值范围[0,255],取值越大意味着该像素颜色越深    下载:http://yann.lecun.com/e......
  • ISIS原理和配置
    ISIS为CLNP进行路由计算类比:CLNP协议NSAP地址IP协议IP地址NSAP(网络服务访问点)是OSI协议栈中用于定位资源的地址,主要用于提供网络层和上层应用之间的接口。NSAP包括IDP及DSP,如下图所示:IDP相当于IP地址中的主网络号。它是由ISO规定,并由AFI与IDI两部分组成。AFI表......
  • 基于Python+Django的智能水果销售系统设计与实现(源码+数据库+讲解)
    文章目录前言详细视频演示项目运行截图技术框架后端采用Django框架前端框架Vue可行性分析系统测试系统测试的目的系统功能测试数据库表设计代码参考数据库脚本为什么选择我?获取源码前言......
  • python实现快速幂
    若需要计算a^b,如果使用循环来计算显然效率是很低的以下有三种方法实现快速幂方法一,python自带函数pow(a,b,mod),其中a为底数,b为指数,mod是对该数取模,mod参数有时候可以不传a=pow(5,9)方法二,利用递归实现快速幂,该方法需要注意分类讨论,考虑到指数为0,指数为1以及指数是奇数的情......