动态链接库的版本控制
DLL Hell
DLL Hell:同一台机器上,运行着A和B两个程序,他们使用了同一个so;程序A在升级时使用新的so直接覆盖老的so,此时可能会造成程序B无法正常运行。
因此需要对动态链接库进行版本控制。
so name
在介绍版本控制前,需要先了解动态链接库的三种name:real name、soname、link name。
- link name:
libxxx.so称为动态链接库的link name。 - real name:实际编译出来的动态链接库是具有版本号后缀的,如
libxxx.so.x.y.z,称为动态链接库的real name。其中
x代表主版本号,y代表小版本号,z代表duild号。 - soname:
link name+主版本号,即libxxx.so.1。
编译动态库
编译动态链接库时要带上编译选项-soname以指定soname。例如编译动态库libtest.so.1.0.0时,编译方式如下:
1gcc -fPIC -o test.o -c test.c
2gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so.1.0.0 test.o
通过readelf -d查看动态段,可以发现soname信息被记录到了libtest.so.1.0.0的文件头中:
1readelf -d libtest.so.1.0.0 | grep soname
2 0x0000000e (SONAME) Library soname: [libtest.so.1]
此时执行ldconfig命令将自动生成libtest.so.1文件,它是一个指向libtest.so.1.0.0的软连接。
不难想到:
- 如果主版本发生变化,新老版本的soname会发生变化。
- 如果小版本发生变化,新老版本的soname应该保持不变。
编译程序
以使用上面编译好的libtest.so.1.0.0动态库的程序为例,编译的标准步骤如下:
- 创建一个指向real name文件的link name文件,即
ln -s libtest.so.1.0.0 libtest.so - 编译程序,通过指定
-ltest,编译器会去查找libtest.so文件,但实际参与编译的是libtest.so.1.0.0文件 - 编译器发现
libtest.so.1.0.0中记录着sonamelibtest.so.1,告诉程序在运行时应该引用libtest.so.1 - 而
libtest.so.1文件,则是通过执行ldconfig命令生成出来的指向libtest.so.1.0.0的软链接,所以程序实际运行过程中使用的是libtest.so.1.0.0
升级动态库
- 小版本升级,比如从
libtest.so.1.0.0升级为libtest.so.1.1.1。这个时候,按照约定它的sonamelibtest.so.1是不变的,所以使用者可以直接把新版本so丢到机器上,执行ldconfig,新生成的libtest.so.1就变成了指向libtest.so.1.1.1的软连接。小版本升级是后向兼容的,所以这里直接进行升级是没有问题的。 - 主版本升级,比如从
libtest.so.1.1.1升级为libtest.so.2.0.0。这个时候,按照约定它的soname变成了libtest.so.2,此时ldconfig生成的软连接为libtest.so.2,指向libtest.so.2.0.0。一般主版本升级会有后向兼容性问题,但是由于使用了新的soname,因此对使用老版本so的程序没有影响。