静态链接带来的问题
像是
libc
这种几乎每个程序都要用到的库, 如果是静态的, 那么不仅意外着每个程序的 可执行文件很大, 浪费磁盘空间. 并且当程序加载到内存时, 可能许多程序都会用到printf
, 使得内存中会存在好多份的printf
源码.维护和更新难. 一旦静态链接的其中一个目标文件更新, 所有的可执行程序都要重新链接.
不满足局部性原理. 上面提到, 内存中同时存在多份的
printf
源码会破坏局部性原理的. 显然如果所有的程序共享一份printf
源码的想法更好. 即动态加载.可移植性差. 静态链接, 只要有一个依赖目标文件的实现不同, 软件厂商就得专门发布一个 版本. 而动态链接则信赖客户电脑上的动态库, 相当于一个中间层.
动态链接的过程
对比静态链接使用ld
链接器在编译后即执行链接, 动态链接则是将链接过程推迟到运行时,
即装载到内存时.
这样, 链接器在链接产生可执行文件时就有两种做法:
- 对于静态符号, 按照静态链接的规则进行地址引用重定位
- 对于动态符号, 链接器则仅标记其为动态链接中的符号, 不进行处理. 而是等到装载时由 专门的动态链接器来完成动态符号的链接工作.
⁉️ 链接器如何确定一个符号是静态的 or 动态的?
在动态共享对象(.so)中保存了完整的动态符号表*, 表中存在的符号即为动态的, 否则为静态.
Linux 的 C 语言运行库
glib
的动态链接版本叫libc.so
. 它在外存上只保存一份, 所有的程序 都可以在运行时使用它. 所以千万不要删掉它.
动态链接有一定的性能损失, 因为每次运行程序时都要重新链接, 并不像静态链接是一劳永逸的. 也有例如
延迟绑定
对性能进行优化的方法, 大概仅有 5%的损耗, 与带来的便利相比可以忽略不计.
地址无关代码 PIC
GCC 生成动态库时需要添加参数-fPIC
, 含义就是生成地址无关码
地址无关码的含义是代码中不包含任何的绝对地址引用, 全都是相对地址.
- 对于模块内的跳转/数据引用, 使用相对的跳转/加载指令. 例如 ARM 指令集中的
B
,ADR
, 经过汇编器之后, 目标的地址都会转为相对于该指令(PC)的偏移. - 对于模块间的跳转/数据引用, 借用GOT 表来间接实现地址无关.
而如果该动态库中全部使用相对地址, 那么加载时也就不需要进行重定位, 即所有的程序都可以 共享这些地址无关代码.
上面说 PIC 的动态库不需要重定位其实是错误的, 只不过它的重定位过程不需要修改代码段, 而是设置了一个放置在数据段的GOT表来实现代码段部分的地址无关特性.
现在貌似 GCC ARM 版本在编译动态库时强制使用-fPIC 选项, 否则会报错. 对此我不是 非常确定!
一般来说, 不将主程序编译为地址无关码. 因为主程序不需要共享, 而且地址无关码的调用 需要两个指令:
计算地址
+跳转
. 多了一步根据偏移得到绝对地址.
动态加载
上面说的,有了动态链接之后,库文件的重定位到运行时。运行时一定是库加载到内存时候吗? 看你怎么理解“运行时”,是主程序运行的时刻,还是库中的代码运行的时刻。所以我可以回答的是: 主程序运行时,动态库不一定立马加载到内存,也就不一定发生动态链接。
动态加载这种思想类似于 COW,可以提升主程序启动的速度,启动消耗的内存更小。 缺点就是第一次调用库的速度变慢,因为加载、重定位都需要发生。
有一个需要注意的点,一般的动态链接过程(不考虑动态加载)就是所有的库一股脑加载到内存,
然后进行重定位工作。重定位完所有符号后,会将主程序的 GOT 表设置为只读,避免 got 表被恶意修改,
这个过程是动态链接器ld.so
做的。
这个说法可以通过看 elf 的 section 布局确定,got 表都会和一些其他的只读 section 放在一起, 而与总是 RW 的 section 分开,这样布局以后设置 got 只读的时候更加方便。这里图中的 got 虽然是可写入的,只是为了可以被重定位修改,改完之后由 ld.so 重映射为只读。
加入动态加载之后,其实 got 表不能在主程序加载完后就设为只读,因为链接延后到了动态库加载到内存时, 现在的做法时,将 got 表进行拆分==> .got 和.got.plt, .got.plt 保存的是以后运行中随时需要重定位的符号, 就不能被设为只读,而为了兼容,.got 就会在主程序加载后被设为只读,因为里面没有需要执行时重定位的符号了。
主程序编译时添加选项
-Wl,-z,lazy
启用动态加载
动态链接和动态加载关系
动态加载和动态链接并不进行绑定
- 静态链接-动态加载:因为是静态链接,所以库的地址已经确定,但加载时只分配虚拟地址空间
- 动态链接-动态加载:用到库时才加载到内存,此时才进行动态重定位
- 动态链接-静态加载:运行主程序时库同时被加载到内存,然后立马执行动态重定位