天天看点

我眼中的linux共享库

共享库数量庞大,所以操作系统需要对共享对象的目录组织和使用方法有一定的规则。

1.1共享库兼容性

共享库更新分为兼容性更新和不兼容性更新。共享库中的接口,被称作二进制接口,即ABI(application binary interface);

防止共享库不兼容的重要因素:不要改变接口的任何部分或者干脆不要使用C++作为共享库接口。

拓展:C++类模板和模板类

模板类是类模板实例化后的一个产物,说个具体点的例子吧,我们把类模板比作是一个做饼干的模子,而模板类就是用这个模子做出来的饼干,至于这个饼干是什么味道的就要看你自己在实例化时用的是什么材料了,你可以做巧克力饼干,也可以做牛奶饼干,这些饼干出了材料不一样外,其它的东西都是一样的了。

1.2共享库版本命名规则

Libname.so.x.y.z,x主版本号,y次版本号,z发布版本号。

可执行文件中,保存了需要共享库的主版本号。Solaris和linux中的SO-NAME机制记录了共享库之间的依赖关系。在linux中,对于共享库,会存在同目录下,创建一个指向此共享库的软链接。

软链接的作用是软链接指向的是最新版本的共享库(同一主版本下的最新版本),有一个好处就是使得依赖于某个共享库的模块,不依赖于详细的版本号。

SO-NAME表示一个库的接口。

链接器参数设为 -lc表示按需选择动态/静态库,-static表示静态,-Bdynamic表示动态库(默认情况)。

SO-NAME机制加上共享库的符号版本信息,保证了主版本号一致下的程序正常运行。(lunux中并未广泛运用,Glibc则是)

Linux支持同一函数的多个版本的存在,以便于链接器挑选合适版本的符号进行链接(函数以符号保存,通过符号定位函数)。

1.3 共享库查找过程

动态链接的ELF可执行文件在启动时会自动启动动态链接器,程序运行所以来的共享对象全部是由动态链接器负责装载和初始化。

Linux系统中包含一个ldconfig程序,作用是为共享库目录下的各个共享库创建、删除、更新相应的SO-NAME(符号链接),并会收集起来,集中存放到/etc/ld.so.cache文件里边,并创建一个SO-NAME的缓存,/etc/ld.so.cache的结构被特殊设计,以便于快速查找。

理论上,程序安装在安装过程中,都会调用ldconfig程序。不同系统下,上述的两个文件名称或者路径会不一样。

1.4环境变量

Linux提供一种方法,即提供环境变量LD_LIBARARY_PATH,改变某个应用程序共享库的查找路径,而不影响系统中的其他程序。LD_PRELOAD环境变量里路径边的共享库和目标文件都会被优先提前加载。发布一个版本的程序不应该依赖于LD_PRELOAD。LD_DEBUG环境变量可以打开动态链接器的调试功能,显示链接中的有用信息等。

1.5共享库的创建与安装

创建共享库或共享对象,gcc、-shared、-fPIC(position independent code)。

Strip工具可以清除共享库或者可执行文件中所有的符号和调试信息。

或者在gcc时,指定-s(清除所有调试信息)和-S(清除符号调试信息)。

共享库的安装,**方法一为:将共享库拷贝到对于目录下(需要root权限),然后运行ldconfig。方法二,无非也是为了建立相应的SO-NAME软链接,并告诉编译器和程序如何查找到该共享库并正确运行,**此时也需要用到ldconfig,需要制定共享库所在目录,即执行如下命令:

1.6共享库构造函数与析构函数

构造函数即可以帮助共享库在被装载时,能够进行一些初始化工作,如打开文件、网络连接等。在函数声明时加上“attribute((constructor))”的属性,即指定该函数为构造函数,使用“attribute((destructor))”为析构函数用。当用dlopen()打开一个共享库时,构造函数会在dlopen函数返回前执行,而析构函数则会在main函数退出,或者程序调用exit函数时执行。如果共享库时运行时加载的,则会在调用dlclose函数返回前执行。__attribute__语法是gcc对C、C++的拓展,对其他编译器语法不通用。多个共享库的构造函数可以选择指定优先级按顺序执行,或者无顺序。

1.7共享库脚本

上述提到的共享库都是动态链接的ELF共享对象文件(.so),事实上共享库还可以是符合一定格式的链接脚本文件,即讲一个或者多个共享库按照一个方式组合起来即可,这种脚本文件的链接过程是动态完成的,也就是运行时完成。LD是一个共享库,也是一个动态链接脚本。

继续阅读