定义和基本思想
动态链接英文是Dynamic Linking需要解决空间浪费和更新困难这两个问题最简单的办法就是把程序的模块相互划分开来,形成独立的文件,而不再将他们静态的链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基本思想
补充理解
普通可执行程序和动态链接库都包含指令和数据,在使用动态链接库时,程序被划分为了主要模块(Program1)和动态链接库(lib.so),实际上他们都可以看成一个程序的以各模块,提到程序模块,可以指的是程序主模块,也可以指动态链接库。
编译链接的小例子
/*Program1.c*/
#include "lib.h"
int main()
{
foobar(1);
return 0;
}
/*Program2.c*/
#include "lib.h"
int main()
{
foobar(2);
return 0;
}
/*lib.c*/
#include <stdio.h>
void foobar(int i)
{
printf("libc : %d\n",i);
}
/*lib.h*/
#ifndef LIB_H
#define LIB_H
void foobar(int i);
#endif
lib.c编译命令:
gcc -fPIC -shared -o lib.so lib.c
编译链接Program1.c和Program2.c的命令:
gcc -o Program1 Program1.c ./lib.so
gcc -o Program2 Program2.c ./lib.so
动态链接过程如下
地址无关代码
由于共享对象在编译时不能假设自己在进程虚拟地址空间中的位置,但是可执行文件基本可以确定自己在进程虚拟地址空间中的起始位置因此需要考虑共享对象在任意地址加载。
解决问题的办法1:装载时重定位。
在链接时,对所有绝对地址的引用不作重定位,把这一步推到装载时再完成。但是动态模块被装载映射到虚拟空间后,指令部分在多个进程之间共享,但是装载时重定位的做法会修改指令,没办法同一个指令多个进程共享,因为指令被重定位以后,对于每个进程时不同的,失去节省内存的优势。
优点:运行速度快
缺点:无法共享代码
解决问题的办法2:产生地址无关代码。
这需要将4中引用方式都实现地址无关性,4种引用方式是:
1 模块内部的函数调用和调转
2 模块内部的数据访问
3 模块外部的函数调用调转
优点:代码段在各个进程之间共享
缺点:运行速度稍慢
延迟绑定
基本思想:程序开始执行时,模块间的函数调用都没有进行绑定(符号查找,重定位等),在函数第一次被用到时候才进行绑定。
动态链接的基本步骤
动态链接器自举
自举:是一段具有条件限制的启动代码,条件是:
1.动态链接器作为一个共享对象,不依赖于任何其他共享对象
2.动态链接器本身所需要的全局和静态变量的重定位由它自己完成。
装载共享对象
完成自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,称他为全局符号表。在链接器寻找可执行文件所依赖的共享对象的时候,一般按照广度优先,找出所有依赖的共享对象,当一个新的共享对象被装载进来时候,他的符号表会被合并到全局符号表中。
重定位和初始化
当动态链接器拥有进程的全局符号表时,链接器重新遍历可执行文件和每个共享对象的重定位表,将它们在GOT中的每一个需要重定位的位置进行修正。
重定位完成以后,如果共享对象拥有”.init”段,动态链接器会执行这段代码实现共享对象特有的初始化特征。
开始执行
当重定位和初始化完成以后,所有的准备工作都宣告完成,所需要的共享对象也都已经装载并且链接完成,此时,进程的控制权转交给程序的入口并且开始执行。