编译、链接学习笔记(五)动态链接

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013230511/article/details/78046286

动态链接是什么

动态链接是与静态链接相对的一种程序执行方式与模块组织的方式。说到动态链接,则需要和静态链接进行对比,才更好的解释动态链接。

静态链接的缺点

静态链接对于目标文件的组织是将所有应用到的的代码经过链接后,都合并成一个可执行文件。但这种将所有涉及到的代码都引用的方式存在很多的弊端

1. 磁盘空间占用过大

因静态链接方式在链接阶段会将所有涉及到目标文件链接成一个单独的可执行文件。当一个经常用到的库被多个目标文件引用,当存在有很多个静态链接文件使用到这个库时,将极大的浪费磁盘空间。如A目标文件中的func 方法被成千上万个文件通过静态链接引用时,将造成非常大的浪费,实际上A文件存在一份便可以满足。

这里写图片描述

2. 内存占用过大

当一个可执行文件被加载多次成为多个进程时,实际上进程中的比如代码段的数据大部分内存都是可以共享的,但以可执行文件以静态链接运行时,在内存中都有多份副本,很大的一部分被浪费掉了。

这里写图片描述

3. 版本更新不好维护

 静态链接对于程序的更新与发布也存在诸多麻烦,如引用到的库中存在有bug,则库作者比如将bug修改后,将新库推送给用户,用户需要重新静态链接成新的可执行文件。

动态链接基本思想

要解决磁盘、内存空间浪费、版本更新不好维护的问题,简单的做法是将程序按模块进行分割,形成独立的文件,等到程序使用到该模块时再按需加载,这就是动态链接的基本思想。这是一种典型的用时间换取空间的做法。

使用这种做法可以很好的解决共享相同目标文件副本而浪费磁盘和内存空间的问题,而因为动态加载的特点可以很好的做到代码可维护,当共享部分出现bug时,作者只需提供修复版本的共享库便可以。

这里写图片描述

目前主流的操作系统中,对于动态链接文件有不同的命名与扩展名
Linux中,的ELF动态链接文件称为动态共享对象(DSO,Dynamic Shared Object),以.so为扩展名.
Windows中,动态链接文件称为动态链接库(Dynamic Linking Library),以dll为扩展名。
Mac os系统中,动态链接文件也称为动态链接库(Dynamic Linking Library),以dylib为扩展名。

共享对象可重入性

为了可以使一个共享对象在任意加载,则这个共享对象必须是可重入的,也就是说不同进程调动相同的的共享对象时,都可以上下文都和进程相关的。具体到装载的实现上,要求共享对象中的内存不能是有绝对地址等。

有两种方法可以做到共享对象的可重入性

1. 装载时重定位(Load Time Relocation)

为时共享对象可以达到在任意地址中加载,首先简单的做法是,在静态链接中时,不对绝对地址进行定位,在装载共享对象时再重新定位。这种方法称为装载时重定位
例如函数func相对在共享对象中的起始地址是0x100,当模块被装载在地址在0x10000000时,假设共享对象位于虚拟内存的起始位置,则访问func时,地址则为0x100+0x10000000 = 0x10000100,此时对于模块中对于func函数的访问都使用0x10000100。
使用这种方法的原因是程度都是作为一个整体进行加载的,而函数,静态函数的在程序的偏移都是固定的,

2. 地址无关代码(Position-indenpendent Code)

装载时重定位是帮助解决模块中出现绝对地址引用的办法之一,但其有一个很大的缺点--指令部分无法在多个进程中共享。因为每次加载时都需要将绝对地址进行重定位,不同的进程对于同一个共享模块拥有不同的内存副本,这就失去了动态链接节省内存的一大优势。地址无关代码所解决的便是这个问题。这种方法的目的是,希望模块中共享的指令代码可以在装载时不因装载地址的不同而不同,这种方法的是将指令中那些需要被修改的部分分离出来,放置到数据部分中,这样指令部分就可以保持不变,数据部分每个进程拥有不同的副本。

从共享对象模块中的地址引用是否是模块外,可以将地址引用分为模块内部、外部两种引用方式。按照不同的引用方式可以分为指令引用和数据引用。因此可将此分为4类引用
  • 类型一 模块内部调用、跳转

模块内的调用或跳转是最简单的,因为模块内的函数的和调用方同处一个模块,他们之间的相对位置都是固定的。

  • 类型二 模块内数据访问

模块中的数据页与指令页之间的相对位置也是固定的,那么当指令中需访问模块内数据时,仅需要相对于当前指令加上固定的偏移量就可访问模块内的的数据了。

  • 类型三 模块间的数据访问

    当访问其他模块的目标地址时,因其他的模块的地址需要先被加载后才可以访问。要做到加载时与代码地址无关,基本的思想是把地址相关的部分放数据段中,将外部模块需要访问模块内数据时,通过数据段访问。由此就可以达到重用的目的
    在linux中,数据段建立一个指向内部变量的指针数组,称为全局偏移表(Global Offset Table,简称GOT)。

    例如当需要外部模块中的B变量时,首先访问GOT,找到B变量的地址0x20000,后面再访问0x20000地址

这里写图片描述

  • 类型四 模块间调用、跳转

与模块内的数据访问类似,都是通过GOT表进行间接访问。

例如当访问func时,先从GOT表中找到func的地址,再进行具体的调用。

这里写图片描述

3 重定向与地址无关

重定向和地址无关两者实现共享对象的运行效率,装载时重定向会比较快,这是因为地址无关代码中每次访问全局数据和函数时,都需要做一次计算当前地址和间接地址的寻址过程。

动态链接的步骤和实现

动态链接的基本步骤主要分为3步,启动动态链接器本身、装载所有需要的共享对象、重定位和初始化

1.启动动态链接器本身

其实链接器本身也是一个共享库,只是其不依赖于任何其他的共享库,操作系统将进行的控制权交由动态链接器后,动态链接器开始执行自己的初始化工作,这工作一般称为自举(Bootstrap)。

2.装载所有需要的共享对象

链接器完成自举后,将可执行文件和链接器本身的符号表都合并到一个符号表中,该表称为全局符号表(Global Symbol Table)。然后链接器开始寻找可执行文件依赖的其他共享对象,如果共享对象还依赖于其他共享对象,以此循环,知道所有的所依赖的共享对象都被加载进来。
当新的共享对象被加载进来后,它的符号表会被合并到全局符号表中,所以当所有的共享对象都被装载进来是时,全局符号表里面的将拥有所有的符号。

3.重定位和初始化

当所有的共享对象都被加载进来后,链接器重新开始遍历可执行文件和每个共享对象的重定位表,对GOT表进行修正。

至此动态链接的步骤已经走完

没有更多推荐了,返回首页