3.4 库版本化
在共享库和应用程序之间维护二进制级的兼容性或ABI是很重要的。共享库的ABI是应用程序依赖的运行时接口;如果每次发布时共享库的ABI都与以前的兼容,那么在其中某一个版本的共享库上编译的应用程序不需要任何改动就可以在后续版本上运行。库版本化就是Linux以及同期的其他操作系统实现二进制兼容性的方法。
我们以前移植过的一些应用程序需要库版本化的支持。各UNIX平台也都实现了库版本化,但实现的方法不尽相同。Linux提供了两种不同的技术来实现库版本化:外部库版本化和符号版本化。
3.4.1 外部库版本化
链接过程中,链接器(ld)会查找以.so结尾的共享库文件。以.so结尾的库文件叫链接器名称,这是由他们在Linux上的使用方式决定的。当编译一个依赖某一共享库的应用程序时,仅仅是该共享库的soname(不是共享库的文件名)作为依赖关系被记录在应用程序的二进制代码中。运行时链接器就是使用共享库的soname来查找和装载该库的。共享库的soname只包含有大版本号(例如,libfoo.so.1)
当修改后的共享库与以前版本不兼容时,新的共享库必须有一个新的外部版本名称。也就是说,该库的soname必须改变。这些不兼容的修改包括:删除一个符号,去掉某函数的一个参数,改变了某函数的语义属性以致与以前的定义不再一致并且与老版本二进制不兼容等等。我们来看下面的例子。(见pdf附件 341.pdf)
3.4.2 符号版本化
就像前面所提到的,当对共享库所作的修改能够向前兼容时,我们只增大小版本号。这种修改包括增加一些新的接口同时又不改变已有的接口。但是,即使只做这种小版本的修改,也会出现一个很重要的问题:一个在某一小版本的共享库上编译的应用程序并不一定能够在以前小版本的库上运行。这是因为该应用程序可能使用了新增加的、以前小版本的库中没有的接口。为了解决这个问题,引入了符号版本化。符号版本化允许共享库记录下每个小版本都新增了什么内容。
在Linux上,GNU ld可以使用-version-script连接器选项来创建符号版本化的共享库。编译器选项-Wl,--version-script=mapfile告诉链接器哪些符号要从生成的共享库中输出出来。每个符号分属global(被输出)和local(不被输出)两类中的一种。来看下面的例子。foo.c包含一个函数foo1,该文件用来创建1.1版本的共享库。(见附件 示例代码.pdf)
可以看到,这次main只引用了版本化库的LX_1.1。
GNU ld还允许在定义符号的源文件中把符号绑定到某一版本中,而不仅仅是在脚本文件中指定。另外,GNU ld还允许同一函数的多个版本出现在同一个共享库中。更多详细信息,请参考GNU ld手册(注释13)和Ulrich Drepper的文章“How to Write Shared Libraries。
从2.1版本开始,glibc就已经实现了符号版本化。符号版本化同时也是LSB规范1.2及更高版本的一部分。
3.5 动态链接器(运行时链接器)
Linux动态链接器(/lib/ld.so.1或/lib64/ld64.so.1)查找和装载应用程序所需的共享库,准备应用程序的运行,然后运行应用程序。除非编译时为ld指明-static选项,否则Linux二进制程序都是动态链接的。
在所有现代UNIX操作系统上,都有一些环境变量可以影响动态链接器的运行。例如AIX上的环境变量LIBPATH可以改变动态链接器的搜索路径。以下环境变量可以影响到Linux上动态链接器的运行:
- LD_LIBRARY_PATH,以冒号分开的目录列表,运行时会在这些目录中查找需要的库。
- LD_PRELOAD,以空格分开的库列表,这些库会在其他所有库之前装载。这常常用来有选择的覆盖某些共享库中的函数。
- LD_BIND_NOW,如果该环境变量设置成非空字符串,动态链接器会在程序启动时解析所有符号,而不是首次引用时才解析符号(也就是常说的“延迟绑定)。这在使用调试器时非常有用。
- LD_TRACE_LOADED_OBJECTS,如果该环境变量设置成非空字符串,程序会列出它所依赖的共享库,就像运行ldd命令一样,而不是正常的执行。
Linux动态链接器采用广度优先(breadth first)的方式解决库的依赖关系。也就是说,首先是可执行程序所依赖的库按照动态节(dynamic section)列出的顺序被装载进来,然后是“第一个被依赖的库所依赖的库按照同样的方法装载进来,以此类推,直到所有的依赖关系都被解决。
在命令行运行下面的命令,会得到更多关于Linux动态链接器的信息:
(代码)(P69第最后一行)
$ info ld.so
上一页;;[1];[2];[3][4];[5];下一页
标签: linux