(Murphy) Linux 动态库机制概要小结(持续更新ing)

来源:互联网 发布:eduboard电子白板软件 编辑:程序博客网 时间:2024/06/02 14:56


前言

主要介绍一些基础知识和自己学习过程中的几个误区,如果有错漏的地方,烦请指正,一起学习!

所有知识点均基于红帽系统验证过


目录

  动态库编译

  动态库加载

  动态库配置和预加载

  动态库查询

  动态库统一部署

  dlopen的优缺点

  动态库的命名和版本控制


正文

1. 动态库的编译

    a) 生成动态链接库的命令很简单:

    #gcc -shared -fPIC -Wl,-soname,libmosquitto.so.1 mosquitto.o ...  -o libmosquitto.so.1 -Wl,--as-needed -lrt -lssl -lcrypto -lpthread

    #gcc sub_client.o -o mosquitto_sub  -L../lib ../lib/libmosquitto.so.1 -Wl,-rpath,'$ORIGIN'/lib  -Wl,-rpath-link,

     b) "-Wl,soname,${name}", 使用这个参数可能把这个“别名”写到目标文件的ELF头信息中,

     b) "-Wl,--as-needed", 在动态库前面使用这个参数,可以在自动剔除目标文件没有真正使用的库;但是在使用中要注意GCC/G++对目标文件、链接库的顺序问题,以免不当使用引起undefined referenced的错误;

     c) "-Wl, -rpath,${lib-relative-path}", 使用这个参数,添加一个私有的库搜索路径,而不会影响到系统级别的库配置信息(详见动态库搜索路径部分);多个目录之间使用冒号":"硬编码到可执行文件内部; '$ORIGING ',代表程序目录,需要加单引号,否则会被解释为变量ORIGIN的值。这样gcc的链接flags为 “-Wl,-rpath,'$ORIGING'/lib  ”

     d) "-Wl1,-rpath-link,${dir}", 用于链接阶段,如果指定了一个库A,但是库A依赖库B,而库B并没有显示指定要链接,这时候连接器会从这个参数指定的路径下搜索库B;

     e) --as-needed, rpath, rpath-link,这类参数实际上是链接器ld的参数,在命令行里通过GCC/G++则需要用 '-Wl,-rpath,${dir}'的格式传递个ld。


2. 动态库搜索路径

动态库的搜索路径分为两种路径:目标文件编译时动态库搜索路径和可执行文件加载时动态库搜索路径。编译时,目标文件搜索动态库的路径和可执行文件加载时搜索动态库的路径,没有直接关系,是两个不同的概念;

比如,

a) 你在编译时通过 -L{lib-dir-pah} -lxxx 或者 {lib-absolute-path}来指定库的路径,这些参数的作用在链接结束,生成目标文件或者可执行文件之后就结束了;

b) 当运行可执行文件,需要加载动态库的时候,可执行文件会去系统默认的一些路径如/lib, /usr/lib或者 /etc/ld.so.conf 配置的路径,以及系统的环境变量LD_LIBRARY_PATH指定的路径下搜索加载时所需的动态库;编译时通过-L{lib-dir-pah} -lxxx 或者 {lib-absolute-path}指定的路径,加载时的搜索过程不会感知到编译时指定的搜索路径。

所以通常情况下,我们编译完成后,需要把可执行文件和动态一并安装到系统下一些特定路径,比如可行性文件放到${PATH}环境变量指定的路径,如/bin/, /sbin/, /usr/bin/, /usr/sbin 等可执行文件默认搜索路径,以及动态库安装到/lib/, /usr/lib等动态库系统默认搜索的路径。

c) 那出于各种原因,如果不想把动态库安装到系统默认的搜索路径和/etc/ld.so.conf配置里,有没有可能,把动态库安装到系统的任意目下,但是也能让可执行文件在需要的时候找到动态库?答案是有的:

一种是上文提到的环境变量LD_LIBRARY_PATH,但这个方法有个不好的地方,它是一个系统级的环境变量;修改了它,那么任意可执行文件加载时都会去这个路径搜索它用到动态库,从而影响到其它可执行文件的加载过程;所以如果你的工程用到的动态库只是这个工程用到、私有的动态库,那这种方法是不太好的。

这里就需要用到另外一种方法,通过GCC的参数"-W1,-rpath ${lib-relative-path}"在编译时指定可执行文件在运行时动态库的搜索路径,把这个路径硬编码到ELF可执行文件内部,只要可执行文件可以在该指定路径下找到就可以;由于这个搜索路径是写在ELF文件内,所以不会影响到系统中其它可执行文件的加载过程;另注意,那个目录不要写绝对目录,而是一个相对目录,因为你不知道该工程会被安装到系统的哪个路径下。通过这种方法最终把可执行文件和动态库一起部署在一个工程目录下,而不必分散安装;同时便于跨平台的快速部署、卸载或者升级;避免了安装目录的权限问题,或者升级、卸载带来的潜在的影响,使它的影响尽可能局限于该工程目录下。

d)通过GCC的参数"-W1,-rpath ${lib-relative-path}"写入到ELF可执行文件里的路径,可以通过系统命令 "readelf -d 可执行文件"来查看。

e) ldconfig通常用来预加载/etc/ld.so.conf指定的路径下动态库和系统默认路径如/lib目录下的动态库,以及给带版本信息的动态库生成软连接文件(这个后续再讲),从而有助于提速可执行文件的加载时间。

注:一个可能的误区存在于,认为动态库必须放到系统的动态库路径、/etc/ld.so.conf指定的路径或者LD_LIBRARY_PATH下,然后再执行ldconfig,才可以使用,这是一个错误的概念;ldconfig只是执行一些动态库的预加载,方便加快可执行文件的加载速度的作用;没有被预加载的库,在需要的时候由可执行文件载入即可。


3. 动态库预加载和配置

a) 动态库预加载命令

ldconfig命令更新库的链接文件,搜索系统配置的动态库路径下所有的动态库并建立一个缓存文件/etc/ld.so.cache;把私有库安装到系统后执行下这个命令后,可以执行这个命令,以便使该私有库立即被加载到动态的cache中;

ldconfig命令部分参数介绍:
    -v或--verbose : 用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字.
    -n : 用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.
    -N : 此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接.
    -X : 此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新.
    -r ROOT : 此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为ROOT/etc/ld.so.conf.
                     如用-r /usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性.
    -p或--print-cache : 此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字.

b) 动态库预加载配置文件

/etc/ld.so.conf是动态链接库的搜索路径配置文件


4. 动态库查询

ldd "可执行文件"可以查看一个可执行文件所有依赖的动态库文件;

a) ldd -u #用于查看被可执行文件链接,但却不会使用的动态库;

b) 包含大量不使用的动态,对影响可执行文件的加载效率;程序员在编码时可能不会特别注意哪些库我一定会用到,哪些库不会用到,可以使用GCC参数 "-Wl,--as-needed"来

c) #strace ${path-to-excutable-file},来观察可执行文件启动过程中动态库的搜索路径都有哪些,以及其搜索的先后次序。


5. 动态库的统一部署

这个可以参照上述rpath, rpath一类参数的设置;


6. dlopen的优缺点

dlopen()一类的接口在调用的地方才加载so文件,这是它的优点;

缺点是不方便在前期编译阶段发现错误,比如确实so文件,如果在部署过程中发现这个问题,会比较尴尬。


7. 动态库的命名和版本控制

a) Linux下库的名字有三种:真名,SO-NAME(也称别名),链接名;其中动态库的真名和别名是在生成动态库的时候指定的,写到其ELF文件中的名字;链接名则是目标文件在链接动态库时使用的名字,链接名可能是别名,也可能是真名(也可以都不是,尤其是在动态库带有版本信息的时候)。

b) 动态库真名的格式:libxxx.so.{major}.{minor}.{buildnum},这种命名格式只是一个为了编译动态库版本管理的一个约定,不要求强制遵守。

c) 链接名是程序链接库时使用指定的名字,具体可以分为以下几种:

一种直接使用-l参数,调用系统中*.so的库名(格式:libname.so),无库的版本信息;以murphy linux系统中curl的库为例:
    libcurl.so为直接指向libcurl.so.4.2.0的软链接,这样可以让可执行文件通过-lcurl来调用;

   
一种是使用库的SO-NAME,方便对库的升级或者对新老版本库共存的支持;
    libcurl.so.4也是指向libcurl.so.4.2.0的软件连,方便通过SO-NAME机制链接到库的真名;
一种直接使用库的真名(不推荐);

d) 仍以上述的CULR的库为参考:
libxxx.so:

这个名字可以设置软链接到库的真名,也可以指向库的SO-NAME;
指向SO-NAME的好处是方便后续库的小版本升级的支持,更为灵活,而指向真名缺乏这种灵活性;
但是对于系统中存在一个库的两个不兼容的版本的情况,libxxx.so 只能指向其中的一个;


libxxx.so.${major-version}:

链接时使用SO-NAME,可以保证使用此库的可执行文件能使用该库的最新的版本(同一个大版本的不同小版本);也便于在系统中存在不兼容的两个版本时,链接正确的版本。

这个名字是在生成库的时候使用"-Wl,soname,{libname}"写入到动态库文件中的;ldconfig命令会帮助创建一个软连接到库的真名的软连接文件




(未完待续)

3 0