1.为什么要动态链接
静态链接缺点:
1.静态链接的方式对于计算机内存和磁盘的空间浪费非常严重。特别是多进程操作系统情况下,静态链接极大的浪费了内存空间。
2.静态链接对程序的更新,部署和发布也会带来很大的麻烦。一旦程序中有模块更新,整个程序就要重新链接,发布给用户
要解决空间浪费和更新困难这2个问题最简单的办法就是把程序的模块互相分割开来,形成独立的文件,而不再将它们静态的链接在一起。
简单的说,就是不对哪些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说把这个过程推迟到了运行时再进行。这就是
动态链接的基本思想。
多个程序共享一个目标文件模块的好处是,节省内存,还可以减少物理页面的换入换出,也可以增加 CPU 缓存的命中率,因为不同的进程间的
数据和指令都集中子啊了同一个共享模块上。
动态链接也使得程序的升级变得很简单。当我们要升级程序库或程序共享的某个模块时,理论上只要简单的将旧的目标文件覆盖,而无需将所有的
程序再重新链接一遍。当程序下一次运行的时候,新版本的目标文件会自动装载到内存并链接起来。程序就完成了升级。
动态链接还有一个特点是,程序在运行时可以动态的选择加载各种程序模块,这个有点就是被人们后来用来制作程序的插件。比如某个公司开发了某个
产品,它按照一定的规则制定好程序的接口,其他公司或开发者可以按照这种接口来编写符合要求的动态链接文件。该产品程序可以动态的载入各种第三方
开发的模块,在程序运行的时候动态的链接,实现程序功能的扩展。
动态链接还可以加强程序的兼容性。一个程序在不同的平台运行时可以动态的链接到由操作系统提供的动态链接库,这些动态链接库相当于在程序和操作系统
之间增加了一个中间层,从而消除程序对不同平台之间的依赖。
动态库的基本思想是把程序按照模块拆分成各个独立的部分,在程序运行时才将它们链接成一个完整的程序,而不是像静态链接一样,把所有的程序模块都链接成
一个单独的文件。
动态链接涉及运行时的链接以及多个文件的装载,必须要有操作系统的支持,因为动态链接的情况下,进程的虚拟地址空间的分布比静态链接的情况更为复杂,还有
一些存储管理,内存共享,进程线程等机制在动态链接的情况下也会有些微妙的变化。
当程序被装载的时候,系统的动态链接器会将程序所需要的所有动态库装载到进程的地址空间,并且将程序中所有未决的符号绑定到相应的动态链接库中,并进行
重新定位。
程序与 libc.so 之间真正的链接工作是由动态链接器完成的,而不是我们前面看到的静态链接器 ld 完成的。也就是说,动态链接把链接的这个过程从本来的程序
装载被推迟到了装载的时候。动态链接会导致程序在性能的一些损失,性能损失大概在 5% 一下,这点性能损失能换来程序在空间上的节省和程序构建和升级时候的灵活。
2.简单的动态链接例子
gcc -fPIC -shared -o Lib.so Lib.c
gcc -o program1 program1.c ./Lib.so
gcc -o program2 program2.c ./Lib.so
图7-3 中有一个步骤与静态链接不一样,那就是 program.o 被连接成可执行文件这一步。在静态链接中,这一步链接过程会把 program1.o 和 Lib.o 链接
到一起,并产生输出可执行文件 program1。但是在这里,Lib.o 没有被连接进来,连接的输入目标文件只有 program1.so(当然还有C语音运行库)。但是从前面
的命令行看到,Lib.so 也参与了链接过程。
关于模块:
在静态链接时,整个程序最终只有一个可执行文件,它是一个不可分割的整体;但是在动态链接下,一个程序被分成了若干个文件,有程序的主要部分,即可执行文件(program1)
和程序所依赖的共享对象(Lib.so),很多时候我们也把这些部分称为模块,即动态链接下的可执行文件和共享对象都可以看做是程序的一个模块。
当程序模块 program1.c 被编译成 program.o 时,编译器还不知道函数 foobar() 函数的地址。当链接器将 program.o 链接成可执行文件时,
这个时候链接器必须确定 program1.o 中所引用的 foobar()函数的性质。如果 foobar() 是一个定义与其他静态目标模块中的函数,那么链接器将会按照
静态链接的规则,将 program1.o 中的 foobar 地址引用重定位;如果 foobar() 是一个定义在某个动态共享对象中的函数,那么链接器就会将这个符号的引用标记为一个动态链接
的符号,不对它进行重定位,把这个过程留到装载时再进行。
这里有个问题,链接器如何知道 foobar() 的引用是一个静态符号还是一个动态符号?这实际上就是我们要用到 Lib.so 的原因。Lib.so 中保存了完整的符号信息(因为运行时进行
动态链接还须使用符号信息),把 Lib.so 也作为链接的输入文件之一,链接器在符号解析时就可以知道:foobar 是一个定义在 Lib.so 的动态符号。这样链接器就可以对 foobar 的
引用做特殊的处理,使它成为一个对动态符号的引用。
动态链接程序运行时的地址分布:
对于静态链接的可执行文件来说,整个进程只有一个文件要被映射,那就是可执行文件本身。但是对于动态链接来说,除了可执行文件本身,还有它依赖的共享目标文件,那么这种情况下,
进程的地址空间分布又是怎样的?
[[email protected] daodao]# cat /proc/31597/maps
00400000-00401000 r-xp 00000000 fd:01 1441794 /home/daodao/program1
00600000-00601000 r--p 00000000 fd:01 1441794 /home/daodao/program1
00601000-00602000 rw-p 00001000 fd:01 1441794 /home/daodao/program1
7f8374009000-7f83741bf000 r-xp 00000000 fd:01 132799 /usr/lib64/libc-2.17.so
7f83741bf000-7f83743bf000 ---p 001b6000 fd:01 132799 /usr/lib64/libc-2.17.so
7f83743bf000-7f83743c3000 r--p 001b6000 fd:01 132799 /usr/lib64/libc-2.17.so
7f83743c3000-7f83743c5000 rw-p 001ba000 fd:01 132799 /usr/lib64/libc-2.17.so
7f83743c5000-7f83743ca000 rw-p 00000000 00:00 0
7f83743ca000-7f83743cb000 r-xp 00000000 fd:01 1441793 /home/daodao/Lib.so
7f83743cb000-7f83745ca000 ---p 00001000 fd:01 1441793 /home/daodao/Lib.so
7f83745ca000-7f83745cb000 r--p 00000000 fd:01 1441793 /home/daodao/Lib.so
7f83745cb000-7f83745cc000 rw-p 00001000 fd:01 1441793 /home/daodao/Lib.so
7f83745cc000-7f83745ec000 r-xp 00000000 fd:01 132791 /usr/lib64/ld-2.17.so
7f83747dc000-7f83747df000 rw-p 00000000 00:00 0
7f83747e9000-7f83747eb000 rw-p 00000000 00:00 0
7f83747eb000-7f83747ec000 r--p 0001f000 fd:01 132791 /usr/lib64/ld-2.17.so
7f83747ec000-7f83747ed000 rw-p 00020000 fd:01 132791 /usr/lib64/ld-2.17.so
7f83747ed000-7f83747ee000 rw-p 00000000 00:00 0
7ffc041fc000-7ffc0421d000 rw-p 00000000 00:00 0 [stack]
7ffc0421e000-7ffc04220000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
我们可以看到,整个进程虚拟地址空间中,多出了几个文件的映射。Lib.so 与 program1 一样,它们都是被操作系统用同样的方法映射至进程的虚拟地址空间,只是它们占据
的虚拟地址和长度不同。program1 除了使用 Lib.so 以外,它还用到了动态链接形式的 C 语言运行库 libc-2.6.1.so。另外,还有一个值得关注的共享对象是 ld-2.6.so,
它实际上是 Linux 下的动态链接器。动态链接器与普通共享对象一样被映射到了进程的地址空间,在系统开始运行 program1 之前,首先会把控制器交给动态链接器,由它完成
所有的动态链接工作以后再把控制器交给 program1,然后开始执行。
动态链接模块的装载地址是从 0x00000000 开始的。我们知道这个地址是无效的,并且从上面的进程虚拟地址空间分布可以看到,Lib.so 的最终装载地址不是 0x00000000.
从这一点我们可以推断,共享对象的最终装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给相应的共享
对象。
1.为什么要动态链接
2.简单的动态链接例子