OS - Shared Library Organization
Created by : Mr Dk.
2020 / 06 / 30 16:28
Nanjing, Jiangsu, China
本文内容来自于 程序员的自我修养 - 链接、装载与库,俞甲子 石凡 潘爱民著。
由于大量程序使用动态链接机制,导致系统中存在大量的共享对象。为了便于维护、升级,OS 一般会对共享对象的目录组织和使用方法有一定的规则。
共享库兼容性
理论上,只需要将新版本的共享库将旧版本的共享库替换即可,但是共享库版本的更新可能导致接口的更改或删除。共享库的更新分为:
- 兼容更新 - 所有原有接口保持不变
- 不兼容更新 - 更新改变了共享库的原有接口
这里所谓的接口是 二进制接口 (Application Binary Interface, ABI)。导致 ABI 发生改变的行为有:
- 导出函数的行为发生改变 (调用该函数以后产生的结果不一样)
- 导出函数被删除
- 导出数据的结构发生变化 (比如结构体定义发生改变)
- 导出函数的接口发生变化 (函数返回值、参数)
共享库版本命名
Linux 的共享库命名规则:lib<name>.so.x.y.z
x
- 主版本号 (重大升级,不同主版本号的库不兼容)y
- 次版本号 (增量升级,增加新的接口符号,保持原有符号不变)z
- 发布版本号 (错误修正、性能改进,接口不变)
Linux 和 Solaris 使用 SO-NAME 的命名机制来记录共享库的依赖关系。SO-NAME 即共享库文件名去掉次版本号和发布版本号:如对 libfoo.so.2.6.1
,SO-NAME 为 libfoo.so.2
。SO-NAME 定死了共享库的接口,相同 SO-NAME 的库,次版本号高的兼容次版本号低的。在 Linux 中,系统为每个共享库创建一个名称为 SO-NAME 的符号链接:
$ ls -alt libthread*
lrwxrwxrwx 1 root root 19 6月 4 22:24 libthread_db.so.1 -> libthread_db-1.0.so
-rw-r--r-- 1 root root 35648 4月 17 2018 libthread_db-1.0.so
由于历史原因,动态链接器 (
ld.so
) 和 C 库的共享对象不按 Linux 标准的命名机制走。
SO-NAME 软链接会指向主版本号相同,剩余版本号最新的共享库。对于所有依赖共享库的模块来说,在编译、链接和运行时,都使用共享库的 SO-NAME。在动态链接的 .dynamic
中记录了依赖的共享对象名称,有时,系统中无需保留能被新版本兼容的旧版本共享对象。因此,只需要将 SO-NAME 记录到 .dynamic
中,就能使动态链接器根据 SO-NAME 自动定向到系统上最新版本的兼容共享库。
Linux 上的 ldconfig 工具在安装或更新共享库时,会遍历系统中的所有共享库目录,并更新所有软链接,使其指向最新版本的共享库。
由于动态链接器只根据 SO-NAME 判断共享对象版本,当程序依赖次版本号较高的共享库,而运行在次版本号较低的共享库系统上时,就可能缺少一些符号。因为共享库只保证向后兼容,并不保证向前兼容,这称为 次版本号交会问题。SO-NAME 无法解决这个问题,而现代的系统使用 符号版本机制 来解决这个问题。
共享库系统路径
大多数开源操作系统遵循 FHS (File Hierarchy Standard) 标准存放系统文件。FHS 规定一个系统中主要有三个存放共享库的位置:
/lib
- 存放系统最关键和基础的共享库,主要被/bin
和/sbin
下的程序使用/usr/lib
- 系统所需但非关键的共享库/usr/local/lib
- 保存于 OS 本身并不相关的库,主要是一些第三方的应用程序库
共享库查找过程
动态链接器的查找规则:
- 绝对路径 - 直接查找
- 相对路径
/lib
/usr/lib
/etc/ld.so.conf
配置文件
另外 ldconfig 程序会将 SO-NAME 收集起来,存放到 /etc/ld.so.cache
中,并建立 SO-NAME 缓存用于快速查找。所以理论上来说,在系统指定的共享库目录下更新库后,都应当运行 ldconfig,以便调整 SO-NAME 和缓存。
通过修改环境变量,可以改变动态链接器装载共享库路径的方法。