再谈Linux下的动态库

为了解决上一篇的博客《Linux下静态库、动态库的创建和使用》最后留下的问题,今天总结一下Linux下动态库

版本号的控制。

一、动态库版本号的组成
    对于上一篇博客中提到的库文件libcurl.4.3.0,其中4代表主版本号,3代表次版本号,0代表发行版本号,
    因此动态库的命名形式为:libname.x.y.z
    x -- 主版本号(不兼容):重大升级,不同主版本的库之间的库是不兼容的。所以如果要保证向后兼容就不能删除旧             的动态库的版本。
    y -- 次版本号(向下兼容): 增量升级,增加一些新的接口但保留原有接口。高次版本号的库向后兼容低次版本号的               库。
    z -- 发布版本号(相互兼容):库的一些诸如错误修改、性能改进等,不添加新接口,也不更改接口。主版本号和次            版本号相同的前提下,不同发布版本之间完全兼容。
    
二、动态库的3个名字
1、real name 
        动态库的real name即动态库本身的文件名,例如libcurl.so.4.3.0
2、soname (short for shared object name)
         动态库的soname即是libname.so.x,显然是保留了主版本号,例如libcurl.so.4。
3、linker name
        动态库的linker name即链接名,libname.so,去掉了后面的版本号,例如libcurl.so。

三、创建带有soname的动态库
        在我的上一篇博客中创建的动态库是没有版本号的,也没有soname的。
        假如我要开发并维护一个动态库,库的版本控制是必要的,就要按照上述版本号命名的方法。
        gcc -c -fpic mylib.c -o mylib.o
        gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 mylib.o
        生成了库文件libmylib.so.1.0.0,然后执行命令:readelf -d libmylib.so.1.0.0
       结果如下:
  Dynamic section at offset 0x668 contains 21 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libmylib.so.1]
 0x000000000000000c (INIT)               0x468
 0x000000000000000d (FINI)               0x5c8
 0x000000006ffffef5 (GNU_HASH)           0x1b8
 0x0000000000000005 (STRTAB)             0x318
 0x0000000000000006 (SYMTAB)             0x1f8
 0x000000000000000a (STRSZ)              133 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x200810
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x438
 0x0000000000000007 (RELA)               0x3d8
 0x0000000000000008 (RELASZ)             96 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x3b8
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x39e
 0x000000006ffffff9 (RELACOUNT)          1
 0x0000000000000000 (NULL)               0x0

      由上面的结果可以看到,有一个SONAME字段,Library soname:libmylib.so.1,由此得出结论,在编译生成动态库时,由于我指定了库文件 的soname为libmylib.so.1,编译器会这个soname写入库文件的头部。还有一个NEEDED字段表示该动态库所依赖的其他动态库,在本例中是依赖标C库。
 
四、使用带有SONAME的动态库
          测试环境:CentOS6.5 x86_64   GCC 4.4.7
          执行pwd:/home/user/lib 
          执行ls: libmylib.so.1.0.0  main.c  mylib.c  mylib.h  mylib.o
          编译:gcc main.c -o main -L. -lmylib
          编译报错:
         /usr/bin/ld: cannot find -lmylib
         collect2: ld returned 1 exit status
         这样编译无法通过,上篇博客已经说明原因。
        这样编译:gcc main.c -o main libmylib.so.1.0.0  -Wl,-rpath=/home/user/lib
        可以通过,这样是直接把libmylib.so.1.0.0当成目标文件,完成链接过程,生成可执行程序main
        然后执行命令:readelf -d main
Dynamic section at offset 0x7a8 contains 22 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libmylib.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [/home/user/lib]
 0x000000000000000c (INIT)               0x400498
 0x000000000000000d (FINI)               0x4006b8
 0x000000006ffffef5 (GNU_HASH)           0x400260
 0x0000000000000005 (STRTAB)             0x400388
 0x0000000000000006 (SYMTAB)             0x400298
 0x000000000000000a (STRSZ)              146 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x600960
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400468
 0x0000000000000007 (RELA)               0x400450
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400430
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x40041a
 0x0000000000000000 (NULL)               0x0

        由以上执行结果可知,main依赖2个动态库,分别是libmylib.so.1,libc.so.6
 显然libmylib.so.1是libmylib.so.1.0.0的SONAME,由此得出结论:
 编译器在链接动态库时,会读取库的SONAME,并且写在可执行文件的头部,那么程序在运行时,
 会根据自己的头部记录的SONAME,去搜索对应的动态库。其中RPATH字段是动态库搜索路径,程序
 运行时,会首先去这个路径寻找所依赖的动态库。

       但是此时,生成的main并不能执行,报错如下:
       ./main: error while loading shared libraries: libmylib.so.1: cannot open shared object 
       file: No such file or directory
      为何不能执行呢?执行:ldd main  结果如下
 [root@localhost lib]# ldd main
  linux-vdso.so.1 =>  (0x00007fff7c3ff000)
libmylib.so.1 => not found
libc.so.6 => /lib64/libc.so.6 (0x00000034b0800000)

/lib64/ld-linux-x86-64.so.2 (0x00000034b0000000)

       原来libmylib.so.1这个文件 not found,这个文件确实没有,只有libmylib.so.1.0.0,由此得出结论:
程序运行会很据自己头部信息去寻找这些以SONAME命名的库文件。

         上一篇博客提到,使用动态库有4种方法,上面我们使用的是最后一种方法,那么再试试其他方法吧!!!

         编译:gcc main.c -o main libmylib.so.1.0.0 ,然后再把/home/user/lib路径加入/etc/ld.so.conf文件中,

         执行:ldconfig

  查看当前目录有何变化,执行:ll
  total 32
  lrwxrwxrwx. 1 user user   17 Aug 26 15:32 libmylib.so.1 -> libmylib.so.1.0.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o

       由上面结果可以看到,当前目录生成了一个名为libmylib.so.1软链接,而且指向libmylib.so.1.0.0
此时在执行:ldd main
[root@localhost lib]# ldd main
linux-vdso.so.1 =>  (0x00007fff8ebff000)
libmylib.so.1 => /home/user/lib/libmylib.so.1 (0x00007fac4c78a000)
libc.so.6 => /lib64/libc.so.6 (0x00000034b0800000)
/lib64/ld-linux-x86-64.so.2 (0x00000034b0000000)

        可以看出,main头部信息里记录是:libmylib.so.1,因此运行时就去找这个文件,于是找到了而这个文                         件/home/user/lib/libmylib.so.1,而它是个软链接指向libmylib.so.1.0.0,于是就可以加载这个库文件了,

        程序可以正确运行了。

五、ldconfig有何用处?
        由上面可以看出,ldconfig作用就是生成了一个以SONAME命名并且指向库文件的软链接。
        如果现在我的库文件更新了,增加一些接口,依然兼容原来的版本,那么库文件名应该为:                                         libmylib.so.1.1.0,SONAME仍然是libmylib.so.1
        gcc -c -fpic mylib.c -o mylib.o
        gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.1.0 mylib.o
[root@localhost lib]# ll
  total 40
  lrwxrwxrwx. 1 root root   17 Aug 26 15:47 libmylib.so.1 -> libmylib.so.1.0.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 15:59 libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o
      显然多了一个库文件libmylib.so.1.1.0,然后再执行:ldconfig
root@localhost lib]# ll
  total 40
  lrwxrwxrwx. 1 root root   17 Aug 26 16:28 libmylib.so.1 -> libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 15:59 libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o
          结果:软链接libmylib.so.1不再指向libmylib.so.1.0.0,而是指向libmylib.so.1.1.0,这是为什么呢?
显然软链接会指向一个最新的版本,程序就会加载到这个最新的库,由于相同SONAME的库高版本是向下兼容           的, 因此可以最大程度的兼容程序。
        如果我的动态库又更新了,这次变化很大,删除了一些以前的接口,那么这次主版本号要变化了,库文件名应该        为 libmylib.so.2.0.0, SONAME也变化为libmylib.so.2, 而且不兼容以前的版本libmylib.so.1.x.y.
  gcc -c -fpic mylib.c -o mylib.o
  gcc -shared -Wl,-soname,libmylib.so.2 -o libmylib.so.2.0.0 mylib.o
  执行:ldconfig
  [root@localhost lib]# ll
  total 48
  lrwxrwxrwx. 1 root root   17 Aug 26 16:28 libmylib.so.1 -> libmylib.so.1.1.0
  -rwxrwxr-x. 1 user user 5900 Aug 26 14:37 libmylib.so.1.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 15:59 libmylib.so.1.1.0
  lrwxrwxrwx. 1 root root   17 Aug 26 16:41 libmylib.so.2 -> libmylib.so.2.0.0
  -rwxr-xr-x. 1 root root 5900 Aug 26 16:41 libmylib.so.2.0.0
 -rwxrwxr-x. 1 user user 6731 Aug 26 15:13 main
  -rw-r--r--. 1 user user  345 Aug 26 14:52 main.c
  -rw-rw-r--. 1 user user  340 Aug 26 14:22 mylib.c
  -rw-rw-r--. 1 user user  304 Aug 26 14:21 mylib.h
  -rw-rw-r--. 1 user user 1544 Aug 26 14:37 mylib.o
   于是乎,又生成一个库文件libmylib.so.2.0.0 和一个软链接 libmylib.so.2,显然多个不同SONAME的库文件可以同时工作了,互不影响。
  结论: 1、ldconfig会遍历/etc/ld.so.conf 中的目录(也包括/lib,/usr/lib)下的库文件,根据读取库文件的                           SONAME,会在库文件 所在目录,生成一个以SONAME命名的软链接并且指向这个库文件。
                      2、如果有多个SONAME相同的库文件,那么这个软链接只会指向一个最新版本的库文件,即次版本号                    最大的库,若次版本号也相同,会指向发布版本号最大的库文件。
                     3、ldconfig还会生成缓存记录在/etc/ld.so.cache中,程序寻找库,会首先找这个cache文件。

     关于linker name:

      我们在链接库的时候,比如 gcc main.c -o main  -L. -lmylib,只需要使用库的名字即可,

      如何才能做到这样子呢?

      那就必须手动创建软链接: ln -s libmylib.so.1 libmylib.so,生成软链接libmylib.so指向libmylib.so.1,libmylib.so.1       也是一个软链接。
      编译参数 -L. -lmylib 表示在当前目录寻找libmylib.so文件,
      libmylib.so -> libmylib.so.1 -> libmylib.so.1.1.0 最终会链接到具体的库文件。
      当然 libmylib.so 也可以指向 libmylib.so.1,这个需要根据实际情况,选择库文件的版本。

  六、解决上篇博客的留下的问题
                 通过以上的分析,已经清楚了动态库的加载细节,可以解决哪个问题了。
      问题: gcc main.c -o main /home/user/lib/libcurl.so.4.3.0  -Wl,-rpath=/home/user/lib 
     这种可以编译通过,但是程序无法运行。
     解决方法:        方法一、在进入/home/user/lib目录下,建立SONAME软链接,ln -s libcurl.so.4.3.0 libcurl.so.4

                                   方法二、把libcurl.so.4.3.0文件改名为libcurl.so.4,如果使用的库不更新的话,可以使用这种方法。



上面讲了那么多,下面做一个要点总结:

     1、要解决不同版本库依赖问题,要遵守上述的库版本号命名约定。
     2、如何创建带有SONAME的动态库。
     3、编译器在编译链接动态库时,会先读取动态库的SONAME,并把SONAME记录在可执行文件的头部(还有一              种特殊情况,假入动态库没有SONAME,例如libmylib.so,那么编译器会把动态库的文件名添加到可执行那个文            件的头部,其实就是real name, 其实种情况,可以理解为real name,Soname,linker name 三者相同)
     4、程序运行时,会读取自己头部记录的所依赖的库文件名字,会根据这个名字去寻找同名的文件(去哪里寻找,            前面已经总结),找到该文件后加载它,显然大多时候,是找到一个软链接,该软连接指向具体的库文件,这            样便可以加载成功。
     5、关于ldconfig的作用,这里不再陈述。

由于笔者的水平有限,出错在所难免,恳请读者拍砖指正,谢谢阅读。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值