动态链接库的版本控制

DLL Hell

DLL Hell:同一台机器上,运行着A和B两个程序,他们使用了同一个so;程序A在升级时使用新的so直接覆盖老的so,此时可能会造成程序B无法正常运行。

因此需要对动态链接库进行版本控制。

so name

在介绍版本控制前,需要先了解动态链接库的三种name:real namesonamelink name

  • link namelibxxx.so称为动态链接库的link name
  • real name:实际编译出来的动态链接库是具有版本号后缀的,如libxxx.so.x.y.z,称为动态链接库的real name

    其中x代表主版本号,y代表小版本号,z代表duild号。

  • sonamelink 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动态库的程序为例,编译的标准步骤如下:

  1. 创建一个指向real name文件的link name文件,即 ln -s libtest.so.1.0.0 libtest.so
  2. 编译程序,通过指定-ltest,编译器会去查找libtest.so文件,但实际参与编译的是libtest.so.1.0.0文件
  3. 编译器发现libtest.so.1.0.0中记录着soname libtest.so.1,告诉程序在运行时应该引用libtest.so.1
  4. libtest.so.1文件,则是通过执行ldconfig命令生成出来的指向libtest.so.1.0.0的软链接,所以程序实际运行过程中使用的是libtest.so.1.0.0

升级动态库

  1. 小版本升级,比如从libtest.so.1.0.0升级为libtest.so.1.1.1。这个时候,按照约定它的sonamelibtest.so.1是不变的,所以使用者可以直接把新版本so丢到机器上,执行ldconfig,新生成的libtest.so.1就变成了指向libtest.so.1.1.1的软连接。小版本升级是后向兼容的,所以这里直接进行升级是没有问题的。
  2. 主版本升级,比如从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的程序没有影响。