共享库

从文件结构上讲,共享库和共享对象没什么区别,Linux下共享库就是普通的ELF共享对象。由于共享对象可以被各个程序之间共享,所以它也就成为了库的很好的存在形式,很多库的开发者都以共享对象的形式让程序来使用。

共享库的版本

共享库的兼容

我们知道,共享库是在不断更新的。但是这种更新也会导致接口的更改和删除。这个时候,就可能导致依赖共享库的程序无法正常运行。

一般来说,共享库的更新是有两种的:

  • 兼容更新。所有的更新只是在原有的共享库基础上添加一些内容。
  • 不兼容更新。更新会改变原有的接口。

注意!我在这里写到的接口是指ABI而不是API。

导致ABI改变的行为

一般改变C语言的ABI有四种主要的行为:

  1. 导出函数的行为发生改变。也就是说调用这个函数以后产生的结果和以前的不一样,对函数的执行逻辑进行了改变
  2. 导出函数被删除
  3. 导出数据的结构发生变化,比如共享库定义的结构体变量的结构发生改变,即对导出函数的返回值,参数列表的删除或者名称的修改。
  4. 导出函数的接口发生变化。如函数返回值、参数被更改。

当然还有其他行为:
比如说不通版本的编译器、操作系统和硬件平台等。

如何保持ABI兼容

对于C++,ABI问题会更加的严重,因为有虚函数表,模板实例化,多重继承等问题。

但是,一般来说,按照以下的规则编写C++共享库基本就可以保持ABI兼容。

  • 不要在接口中使用虚函数,万不得已要使用虚函数时,不要随意删除、添加或在子类中添加新的实现函数。
  • 不要改变类中任何成员变量的位置和类型。
  • 不要删除非内嵌的public或protected成员函数。
  • 不要将非内嵌的成员函数改成内嵌成员函数。
  • 不要改变成员函数的访问权限。
  • 不要在接口中使用模板
  • 不要改变接口的任何部分干脆不要使用C++作为共享库接口。

共享库版本命名

Linux规定共享库的文件名规则必须如下:

libname.so.x.y.z

后面跟的三个数字组成了版本号。
“x”表示主版本号

主版本号表示库的重大升级,不同主版本号的库之间是不兼容的

“y”表示次版本号

次版本号表示库的增量升级,即增加了一些新的符号接口,且保持原来的符号不变。在主版本号相同的情况下,高的次版本号向后兼容低的次版本号的库。

“z”表示发布版本号

发布版本号表示库的一些错误的修正、性能的改进等,并不添加任何新的接口,也不对某个接口进行更改。相同主版本号、次版本号的共享库,不同发布版本号之间完全兼容

程序如何获取依赖共享库信息

我们假定程序中有一个它所依赖的共享库列表,其中每一项对于于它所依赖的一个共享库。

SO-NAME

对于Linux来说,普遍会采用一个叫SO-NAME的命名机制来记录共享库的依赖关系。每个共享库都有一个对应的SO-NAME

SO-NAME即共享库的文件名只保留主版本号。

如 libfoo.so.2.1.6 的SO-NAME就是 libfoo.so.2

显然,我们可以得出SO-NAME相同的两个共享库,次版本号大的兼容次版本号小的。

SO-NAME的优势

SO-NAME其实是一个软链接。实际上这个软链接会指向目录中主版本号相同、次版本号和发布版本号最新的共享库。

所以它的优势在于:
不用更改程序,直接更改软链接

总之,SO-NAME表示一个库的接口,接口不想后兼容,SO-NAME就会发生变化。

链接名

当我们在编译器使用共享库的时候,我们会使用更简洁的方式。

比如需要链接一个libXXX.so.2.6.1,就只需要在shell脚本中输入-lXXX就行

共享库的系统路径

FHS规定,一个系统中主要有两个存放共享库的位置:

  • /lib ,这个位置主要存放系统最关键和基础的共享库
  • /usr/lib 这个目录下主要保存的是一些非系统运行时所需要的关键性的共享库
  • /usr/local/lib,这个目录用来存放一些跟操作系统本身并不十分相关的库。

FHS,一个大多数开源操作系统都遵循的标准

共享库的查找过程

在linux中,动态链接的ELF可执行文件在启动时会同时启动动态链接器/lib/ld-linux.so.X,程序所依赖的共享对象全部由动态链接器负责装载初始化

任何一个动态链接的模块所依赖的模块路径保存在dynamic段中,由DT_NEED类型的项表示。动态链接器对于模块的查找有一定的规则:

如果DT-NEED里面保存的是绝对路径,那么动态链接器就会按照这个路径去查找。如果是相对路径,那么动态链接器就会在/lib/usr/lib和由/etc/ld.so.conf配置文件指定的目录中查找共享库。

但是!

每次都去找这个目录是否是太过笨逼?

所以Linux中引入了一个叫ldconfig的小程序,它会把共享库目录下的各个共享库创建、删除或更新相应的SO-NAME,这样每个共享库的SO-NAME就能够指向正确的文件共享库;并且这个程序还会将这些SO-NAME收集起来,集中存放到/etc/ld.so.cache文件,并建立一个SO-NAME的缓存

参考文献

 [1] 俞甲子 石凡 潘爱明.程序员的自我修养.电子工业出版社,2009.4.