动态链接的基本思想是将程序中的部分独立模块作为动态共享库实现,在链接生成可执行文件时,动态库只提供重定位和符号信息,而不实际参与链接;等到程序运行时,由动态链接器将程序依赖的动态共享库加载到进程的虚拟地址空间中,形成一个完整的程序后执行。
动态链接的核心工作由动态链接器完成,在Linux平台下,使用的动态链接器一般为`ld-linux.so`。动态链接器需要完成的任务一般包括:
动态链接器自举:
动态链接器自身也是一个动态共享库文件,在加载动态链接的可执行文件时,必须要先加载动态链接器,并由动态链接器完成自身的初始化,即自举。
加载动态库:
可执行文件中记录了依赖的符号信息,动态链接器会根据依赖信息,读取包含依赖符号的动态库文件,并映射动态库的代码段和数据段到进程地址空间中。
重定位和初始化:
动态链接器遍历可执行文件和所有共享对象的重定位表,然后依据记录在GOT/PLT表中的重定位信息,对外部符号的引用进行修正;在完成重定位后,动态链接器调用动态库中`.init`中代码,以实现动态库特有的初始化流程。
在Windows下,动态链接文件是`.dll`为后缀名的文件,而在Linux下,动态链接文件是`.so`为后缀名的文件。创建动态链接库的步骤通常包括:
1. 编写源代码文件,例如`pro1.c`和`pro2.c`,它们都使用到了共享库`Lib.c`中的函数。
2. 使用`gcc`编译源代码文件并生成目标文件,例如`pro1.o`和`pro2.o`,但不进行链接。
3. 使用`gcc`将目标文件和共享库编译成动态链接库,例如`Lib.so`。
4. 在运行时,使用`gcc`将目标文件和动态链接库链接成可执行文件。
动态链接的主要优点包括:
空间浪费减少:
动态链接将程序的模块分割开来,形成独立的文件,避免了静态链接时产生的冗余代码。
更新方便:
当程序库或共享模块需要更新时,只需替换旧的目标文件,而无需重新链接整个程序。
内存共享:
动态链接允许多个进程共享内存中的共享库,节省了内存资源。
动态链接的主要缺点是:
运行时开销:
动态链接需要在程序运行时进行符号解析和重定位,这会带来一定的性能开销。
复杂性增加:
动态链接的进程虚拟地址空间分布更为复杂,需要操作系统的支持和管理。
总的来说,动态链接是一种高效的链接方式,适用于需要频繁更新和共享库的程序。