学习《程序员的自我修养》一书,进行记录总结。本文为第八章的主要内容。本章主要讨论了linux中共享库的版本规划(部署层面),符号版本组织(代码层面),共享库的查找策略创建、安装。

共享库版本

共享库的一个附加便利是,当某个.so内容变化时,在一定情况下,只需要把旧的.so替换为新的即可使程序得到新版本的特性。但是这里面这ABI(应用程序二进制借口)的兼容问题。如果.so中的函数接口、函数意义、数据结构、发生变化或者某些符号被删除,则会导致可执行程序无法正确调用。特别是在c++语言中,ABI兼容问题更为复杂(继承、模板、重载等)。还有一种情况是不同版本的so使用不同版本的编译器都可能导致ABI不兼容。所以书上给了7条保持ABI兼容的建议。特别是最后一条不要改变接口的任何部分或者干脆不要用C++作为共享库的接口。(我感觉,既然so都变了,还是重新编译链接一下看看出了什么错然后改了就行了吧...)

共享库版本命名

libname.so.x.y.z
x主版本号:各个主版本之间互不兼容
y次版本号:同x情况下,次版本号向后兼容低版本,即用x.1的程序也能用x.2反之不保证
z发布版本号:无论发布版本号怎么变,都相互兼容

SO-NAME--解决主版本号之间的兼容问题

libname.so.x.y.z <-- libname.so.x
即创建一个只保持主版本号的符号链接到特定的共享库上。这样做的好处是,在编译链接、执行时始终使用符号链接来保持对于当前主版本更新的灵活性。使用gcc -l参数后面跟链接库名字就是链接某个库的意思。其中可以只使用-l name,链接器会自动寻找最新的版本。lc为根据输出文件来决定链接动态版本还是静态版本。-Bdynamic为链接动态库, -static找静态库。

基于符号的版本机制

SO-NAME只解决主版本号的区分。次版本号可能存在不兼容,此时采用符号版本机制。大体策略就是讲共享库的符号按照版本划分为有依赖关系的集合,比如S1<-S2<-S3..<-SN表达了一个高版本依赖低版本的集合例子。编译时,用到了那些集合,就标记用到的最高版本的符号集合,比如某个可执行用到了S3符号集,那么它可以在包含大于S3的版本的系统中运行,否则链接器会负责报错。

共享库系统路径

大多数开源操作系统包括linux遵循FHS标准(File Hierarchy Standard,文件层级标准),规定了系统内每个目录该放什么东西。对于共享库相关的定义如下。

  • /lib,放最关键、最基础的共享库。如动态链接器、C语言运行库、数学库等主要被/bin和/sbin下程序用到的库。
  • /usr/lib/ 存放非运行时的关键性共享库,主要是开发时用到。
  • /usr/local/lib 存放和操作系统本身并不相关的库,一般是第三方程序的库。

共享库的查找过程

首先,可执行程序和共享库中在.dynamic段中的DT_NEED类型中的项表明了可执行程序、共享库所依赖的共享库。如果是绝对路径,则去找那个路径。如果是相对路径,则首先在LD_LIBRARY_PATH环境变量中定义的路径下查找,之后在/etc/ld.so.conf定义的目录中查找(但是一般这个步骤只在缓存中查找即使用ldconfig来进行缓存,然后链接器在/etc/ld.so.cacha中查找),最后再/lib和/usr/lib中查找。如果还没有则宣告失败。在编译链接时期LD_LIBRARY_PATH相当于gcc附加的-L选项。

有用的环境变量

  • LD_LIBRARY_PATH,编译时相当于gcc中附件-L选项, 运行时告诉链接器首先在这个变量定义的目录中查找相对路径依赖库。
  • LD_PRELOAD, 指定预先装载的共享库或者目标文件,优先于LD_LIBRARY_PATH。由于全局符号介入,LD_PRELOAD中定义的符号会覆盖以后加载的符号,可以实现更方便的debug(覆盖某些函数,添加打印信息)。
  • LD_DEBUG,可以指定在动态链接库发挥作用时打印的信息,可以设置的值有:

    • "files"显示整个装载过程
    • "bindings"显示动态链接符号绑定过程
    • "libs"显示动态链接库的查找过程
    • "versions"显示符号版本的依赖关系
    • "reloc"显示重定位过程
    • "symbols"显示符号表查找过程
    • "statistics"显示动态链接过程中的各种统计信息
    • "all"显示所有信息
    • "help"显示上面的可选值的帮助信息

标签: 编译链接, 程序员的自我修养

添加新评论