5.1 为什么要有动态链接

在第4篇中简要概述了静态链接的过程,静态链接的过程可能会存在如下的缺陷:

  1. 生成的可执行目标文件可能由于合并了大量的可重定向目标文件而体积巨大,占用磁盘
  2. 修改不易。任何一个静态库中的相关模块修改都需要进行重新的链接。
  3. 会由于大量使用静态链接而使得加载到内存中的代码存在大量重复代码。比如有100个可执行目标文件加载到了内存,每个目标文件都调用了printf函数,实际上,每一个目标文件都和printf.o模块进行了合并,即prinf.o模块被重复合并到了100个可执行目标文件中,但实际上他们所执行的功能都一样。

基于上述存在的问题,提出动态链接的方式。

动态链接的过程如下图所示:
image.png
为了实现上述的过程,我们需要在命令行中调用gcc程序输入如下参数:

  1. gcc -o myproc main.c ./mylib.so

可以发现,与静态链接不同,动态链接无需指定相应链接方式的参数(静态链接需要-static , 其实在平时的编译过程中一般采用的为动态链接方式)

同时在上述的图中也可以发现,静态链接器所生成的文件只是一个部分链接的可执行目标文件,该文件还不能直接运行,需要有相应的动态链接库(即图中的.so文件)才能运行。实际上,当采用动态链接时,静态链接器不会像采用静态链接时直接合并所有的可重定向目标文件,而是将需要动态链接的信息进行记录,这样就解决了上述缺陷1,比如若main.o中需要调用printf函数,就会记录需要在libc.so中的prinf.o模块中寻找,并记录其相应的符号表和重定位表(libc.so和ld-linux.so的地址都会记录在静态链接器生成的myproc目标文件中),但不会将那些动态链接库中的模块合并。

寻找相应符号的具体地址则在动态链接器中完成,而printf.o也在真正执行myproc的时候才会被动态链接器加载到内存中,在myproc整个执行过程中,printf.o所映射的存储映像固定,这时,动态链接器会修改myproc的符号表,将相应的符号地址等信息进行修改,寻找相应外部符号的地址就需要从符号表中进行查找,实际上,采用动态链接方式之后,指令也与静态链接方式不一样,静态链接方式重定向后相关指令的数据直接修改为其地址,但由于动态链接方式下不能确定每次动态链接库中的模块会被映射到具体的存储器的位置,所以都需要通过其符号表中的信息在运行时进行确定。

这样设计之后,当我们修改动态链接库中的模块的时候,myproc可执行目标文件也不需要再次用静态链接器链接了,而只需要保持与之前相同的可执行目标文件即可,会通过动态链接器直接执行新更新之后的代码,由此解决缺陷2。

缺陷3的解决**也很简单,实际上内存中只会保存一个相同.o文件的副本,例如内存中只会保留一个printf.o文件,所有使用到该模块数据以及代码的其他程序都会到该内存处寻找相应的数据。

5.2 动态链接的缺陷

但或许动态链接最大的缺陷就是执行效率不高,一方面,执行时需要动态链接器再次进行链接,更新可执行目标文件的符号表,另一方面,由于采用的寻址方式相较于静态链接方式更为复杂,使得同样的静态链接下的指令需要更多的指令来完成。