静态链接(静态链接概述)

静态链接是由链接器在链接时将库/模块的内容加入到可执行程序中静态链接使得不同的程序开发者和部门能够相对独立开发和测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先限制程序的规模也随之扩大但是慢慢地,静态链接的诸多缺点也暴露出来,比如浪费内存和磁盘空间,模块更新困难等问题,我来为大家科普一下关于静态链接?下面希望有你要的答案,我们一起来看看吧!

静态链接(静态链接概述)

静态链接

静态链接简述

静态链接是由链接器在链接时将库/模块的内容加入到可执行程序中。静态链接使得不同的程序开发者和部门能够相对独立开发和测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先限制程序的规模也随之扩大。但是慢慢地,静态链接的诸多缺点也暴露出来,比如浪费内存和磁盘空间,模块更新困难等问题。

想象一下每个程序内部除了都保留 print(),scanf() 等公用函数外,还引用了很多的其他库函数。在现代的程序中,一个普通程序可能用到 1MB 的静态库,那么如果我们的机器中运行 100 个同样的程序,就要重复近 99 MB 的内存,高达 99% 的资源浪费率。

空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也带来很多的麻烦。如果一个程序中有任何模块更新,那么整个程序就必须重新链接,应该更新的模块(规模变大)可能导致源程序后续链接的地址混乱覆盖。

动态链接

1. 基本原理

要解决空间浪费和更新困难的办法就是把程序的模块相互分割开来,不再将他们静态地链接在一起,而是等到程序运行时才进行链接,这就是动态链接的基本思想。

  • 程序从源码到运行经过:预处理、编译、汇编、链接、运行这些阶段;
  • 假设我们在汇编后有 Programe1.o、Programe2.o、lib.o 三个目标文件,Programe1 和 Programe2 都引用到了 lib;
  • 执行 Programe1 时,在连接阶段,动态链接器读取 Programe1.o 生成可执行文件,同时对于需要被链接的模块 lib 通过符号的方式代替,并不真正链接。
  • 在运行 Programe1 时,发现需要需要引用 lib,会先判断内存中是否有载入此模块,没有则载入,再通过符号解析和重定位指向真实的 lib 地址。
  • 执行 Programe2 也同理,只不过 lib 已经载入内存了,并不需要重新载入,直接复用此模块。

当我们要升级程序库或共享模块时,只要将旧的目标文件覆盖掉,同时重启程序(用到热加载时可以不用重启),不需要再链接为可执行文件,新版本的目标文件就被自动装载到内存中并且链接起来,完成程序升级。

动态链接还有一个特点就是程序在运行时可以动态地选择加载各种程序模块,这个优点后来被人们用来制作插件。比如某个公司开发了某个产品,它按照一定地规则指定好程序的接口,其他开发者就可以按照这种接口来编写符合要求的动态链接文件,该产品就可以动态地载入各种由第三方开发的模块,在程序运行时动态地链接,实现程序功能的扩展。

2. 基本实现

动态链接涉及运行时的链接及多个文件的装载,必须有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布、存储管理、内存共享、进程线程等机制要比静态链接更为复杂。在 Linux 系统中,动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),一般以 .so 为扩展名,在 Windows 中,被称为动态链接库(DLL,Dynamical Linking Library),一般以 .ddl 为扩展名。

gcc -shared -o lib.so lib.c

当链接器将 Programe1.o 链接为可执行文件时,发现需要引用 lib 模块,同时通过 lib.so 知道这个模块是一个动态模块。当调用 lib.foo() 函数时,会将其标为一个动态链接的符号,而不是真正载入模块和函数。

对于全局变量,假设 lib 中定义一个全局变量,Programe1 引用到这个变量。当链接器执行 Programe1.o 时发现了这个全局变量,但由于链接器未能链接其他的模块,并无法根据上下文判断此变量是在哪个模块中。为了能够让链接过程正常进行,链接器发现此种情况后,就在 .bss 段中创建这个变量的副本,这样,就在链接阶段把全局变量的托管权就交给 .bss,所有的模块都是通过 .bss 来管理这一个变量的副本。

3. 延迟绑定(Lazy Binding)

  • 动态链接比静态链接要灵活得多,但它是以牺牲一部分性能为代价的。动态链接比较慢的原因有:
    • 对于全局和静态的数据访问都需要进行 GOT 定位,然后间接寻址;
    • 对于模块间的调用也要先 GOT 定位,再进行间接跳转;
    • 在程序运行时,动态链接器要先寻找并装载所需的共享对象,然后进行符号查找及重定位的工作,这使得程序启动时非常的缓慢。

为了减少程序启动时的缓慢问题,动态链接提供了延迟绑定功能。在一个程序运行过程中,可能有很多函数在程序执行完都不会用到,比如一些错误处理函数或者一些用户很少用到的功能模块等。如果一开始就把所有函数都链接好实际上是一种浪费。所以延迟绑定的基本思想就是当函数第一次用到时才进行绑定(符号查找、重定位等)。所以程序开始执行时,模块间的函数调用都没有进行绑定,大大提高了程序的启动速度。

4. GOT(Global Offset Table)

在动态链接下,模块间(全局、静态)的数据访问比模块内部(局部)的数据访问稍微麻烦。因为在链接阶段,主模块并不会链接共享模块,故很难确定一些变量是在哪些模块中被定义的。于是需要借助 .bss 段,在里面维护一个全局偏移表(GOT),当程序运行时知道变量的地址后,才将 GOT 的符号与真实地址进行绑定。当要访问这些变量时,也需要通过 GOT 来间接获取目标地址。

参考文献:

  • 百度百科:静态链接
  • 《程序员的自我修养——链接、装载与库》
,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页