提纲:
-
引言
-
为什么要使用动态库
-
动态库的命名规范
-
动态库的搜索路径
-
杂项
-
注释
-
参考文献
-
版本记录
1引言
编译时候的时候,是否出现过undefinedreference to这样的错误?编译成功了是否又遇到过error in
loading shared library这样的错误呢?网上可以查到各种各样的解决方案,临时照着修改一下,比如增加gcc
–L和-l的选项,设定环境变量LD_LIBRARY_PATH等等可以解决问题。但过后仍然还是云里雾里,不明所以。不知道背后的原理,下一次遇到相同
的问题的时候,按照上一次解决问题的方法去解决的视乎,很有可能就无法得到期望的结果。学习还是不能知其然,一定也要知其所以然。
《Linux/Unix系统编程手册》41章和《程序员的自我修养》专门对动态链接库的内容进行了论述,解释的比较清楚。阅读之后,可以澄清自己很多以
前不是很清楚的概念,收获比较大。为了加强自己的记忆和理解,现在将相关的一些要点整理出来,水平浅薄,疏漏难免,
请各位批评指正,欢迎交流,我的个人邮箱为.
2为什么要使用动态库
谈到为什么要使用动态链接库,首先得说说静态链接库的缺点:
1. 不同的可执行文件如果用到了某个目标文件(比如某个如静态链接库中的某个目标文件),每个可执行文件会保存该目标文件(比如某个如静态链接库中的某个目标文件)的一个副本,这样会浪费很大的磁盘空间。举个例子,比如具有重要作用的C语言静态链接库。
2. 如果包含相同模块的可执行文件同时执行的话,那么每个程序都会在内存空间中保留这样模块的一个副本,浪费了内存空间。
3. 如果要修改静态库中的摸一个目标文件,那么所有链接过该库的可执行文件都需要重新链接,这为版本的管理和维护带来了相当大的难题。
动态库解决这些问题的总体思路就是:将程序和所依赖的模块分离。在编译的时候不进行链接,而是推迟到运行的时候,进行链接。这样做具体的好处由以下几点:
1. 由于程序和模块(不同程序可能都需要的)分离,那么整个磁盘仅仅需要存储一份模块即可。
2.
对于内存空间是这样的,第一个依赖摸个模块的可执行文件运行的时候,该模块会被加载到内存中,如果接下来仍然有其他的程序依赖该模块的话,则不需要加
载,这一方面加速了程序的启动,另一方面也是减少了内存的消耗。此外,也可以有效的减少物理页面的换入和换出。(程序员的自我修养一书中,提到了一个可以
增加CPU缓存命中率的优点)。
3. 由于程序和模块的分离,那么如果需要维护模块的话,不需要像静态链接那样全部重新链接。
3动态库的管理机制和命名规范
前面提到了设计动态链接库的原因之一就是解决库文件的维护和修改的问题。更新动态链接库很有可能造成原来的程序不能正常运行。根据新的动态库对于原来的
动态库的兼容性而言,可以把动态库的升级分为可兼容和不可兼容两种部分(《程序员的自我修养》8.1节对这个问题分析的比较深入,感兴趣的朋友可以阅读这
一部分)。
正是由于动态库更新升级可能造成的影响,动态链接库的升级需要一种有效的管理方式,这种管理方式的最佳体现就是动态库的命名规范。关于动态链接库的命名问题有概念:真实名称、SO-NAME和链接器名称。
1.真实名称
动态库真实名称的格式为libname.so.x.y.z。这里面前缀是lib,库名字是name.so,主版本号为x,次版本号为y,更新版本号码为z
【注1】。
如果主版本号码不同说明,版本重大升级,主版本号码不同的两个版本不能兼容。需要对程序进行修改才能使程序使用新的动态库。次版本号表示增量升级,也就是
添加了一些新的接口,因此此版本号较大的版本可以兼容较小的版本,比如依赖1.1.x的程序可以使用1.2.x动态库。更新版本号表示进行了一些BUG的
修改,性能的改进等等,没有添加新的接口,也没有对接口进行修改,因而相同的主版本号和此版本号码的情况下,程序可以运行在任意一个更新版本号下
【注2】。
2.SO-NAME
SO-NAME的格式是libname.so.x,也就是仅仅保留主版本号码。SO-NAME本质上是真实名称的一个软链接,该软链接指向目录中主版本号码相同但是次版本号码不同的共享库。
那么为什么要建立SO-NAME的机制呢?这样在编译某个程序的编译、链接和运行都依赖SO-NAME。假设程序文件依赖于共享库的真实名称比如
libfoo.so.1.1.0,那么该执行文件就只能使用1.1.0版本,而如果使用SO-NAME,则可执行文件依赖的库文件为
libfoo.so.1,可以用libfoo.so.1.2.0来替代原来的动态库,此时仅需要将SO-NAME的软链接链接到新的动态库,这样就可以实
现升级了。
3.链接名称
在使用GCC编译器的时候,你可能注意到有这样的一个选项-l,例如需要链接libABC.x.y.z这个库,我们指定-lABC即可。同SO-
NAME一样,链接名称的本质也是一个软链接,仅仅包含库的名称,不包含主要次要版本的信息,因此其一般形式为libfoo.so。一般来说,链接名称可
以指向真实名称,也可以指向SO-NAME(最好指向SO-NAME)
【注3】。
4 按照命名规范创建动态库的一个实例
根据上面介绍的动态库的管理方法和名称命名规范,下面来进行动态库的创建。
-
gcc–g –shared –Wl,-soname,libfoo.so.1 –o libfoo.so.1.0.1 foo.o
复制代码
上面的过程创建了动态链接库libfoo.so.1.0.1,这个名称也是上面提到的真实名称。-soname选项指定了soname为libfoo.so.1。
接下来,创建相应的soname和链接名称的软连接。
-
ln-s libfoo.so.1.1.1 libfoo.so.1
复制代码
-
ln-s libfoo.so.1 libfoo.so
复制代码
上面的libfoo.so.1是SO-NAME,链接名称是libfoo.so。下面编译链接该动态库。
-
gcc-o main maim.c -L. -lfoo
复制代码
-L选项指定了编译的路径,-l指定的是链接名称。指定LD_LIBRARY_PATH后可以运行该程序。
5动态库的搜索路径
前面列举了LD_LIBRARY_PATH的一个应用,其目的是指明程序运行的时候,到哪里去寻找动态链接库文件。事实上,在实际的生产环境中不应该使用这个变量
【注4】,有可能造成许多问题,之所以提供这个变量是为了方便测试。
系统中放置动态链接库的位置主要有以下几个位置,/lib,/usr/lib,/usr/local/lib,其他/etc/ld.so.conf中指
定的目录。在上述几个目录中安装了库文件之后,需要创建SO-NAME,链接名称当然软链接。既然存在这么多可能存在动态链接库的地方,那么你是否好奇搜
索动态链接库的顺序是什么样子的呢?下面就是搜索动态链接库的一般的名称。
1. 由环境变量LD_LIBRARY_PATH指定的路径
2. 由/etc/ld.so.conf指定的路径默认的共享库路径,先/usr/lib然后/lib【注5】
事实上,上述的搜索路径的顺序并不完善,因为没有考虑在gcc编译的时候采用-rpath选项的情况。-rpath选项在编译的时候即可指定搜索动态链接库的位置。比如:
-
gcc–o main –Wl,-rpath,/home/aaa/your_so_path –L/home/aaa/your_so_path –lfoo main.c
复制代码
-rpath选项将后面的字符串复制进了程序的rpath列表中。如果在上面的语句中添加了—enable-new-dtags选项的话,也能实现该功能,不过是影响了搜索动态库的顺序。如果使用了-rpath选项的话,搜索动态库的路径为:
1. –rpath指定的路径
2. 由环境变量LD_LIBRARY_PATH指定的路径
3. –rpath指定的路径(指定了--enable-new-dtags)
3. 由/etc/ld.so.conf指定的路径(实际上是/etc/ld.so.cache缓存文件)
4. 默认的共享库路径,先/usr/lib然后/lib
6杂项 -fPIC选项表示地址无关代码,这个是生成动态链接库的管理
-Wl表示将后面的参数传递给连接器。
-shared表示生成动态链接库
-LD_DEBUG选项是一个非常有用处的调试宏。
共享库可能位于系统中的不同目录,如果搜索不同的目录在加载某个动态库的话,效率会比较慢;另一方面,当安装了新版本的库,有可能so_name就不是
最新的。ldconfig工具可以解决这两个问题,首先,他搜索/etc/ld.so.conf中指定的目录,然后搜索/lib,/usr/lib等目
录,创建/etc/ld.so.cache缓存文件。然后,他寻找每个主要版本的最新的次要版本嵌入的so-name(就是gcc -Wl,-
soname,libfoo.so.1中的libfoo.so.1),并且创建响应的软链接(本文前面提到的是手动创建的软链接)
【注释】
注1:《Linux/Unix系统编程手册》一书中的描述有细微的差别,本文采用了《程序员的自我修养》的方法。
注2:《程序员的自我修养》对库的兼容性问题进行了较为清楚的描述,见8.1.1小结
注3:最好遵照规范进行命名。
注4:两本参考书籍均提到LD_LIBRARY_PATH路径不应该被滥用。
注5:两本书在先搜索/usr/lib还是/lib这个问题上不一样。
【参考文献】
《Linux/Unix系统编程手册》
《程序员的自我修养》
【版本记录】
V1.0.0 20150313 完成初稿
阅读(745) | 评论(0) | 转发(0) |