感觉讲得挺详细 注: ln 命令用法 ln –s 源文件 目标文件 (目标文件即为软链接文件) 可用ls -l查看软链接文件具体指向哪个文件

前言

针对同一动态组件的不同版本链接和加载。

一、概念         
         DLL HELL字面意思是DLL"灾难",是由于com组件(动态库)升级引起的程序不能运行的情况。
        原因
         有三种可能的原因导致了DLL Hell的发生:
                一是由使用旧版本的DLL替代原来一个新版本的DLL而引起的。这个原因最普遍,是Windows 9X用户通常遇到的DLL错误之一。
                二是由新版DLL中的函数无意发生改变而引起。尽管在设计DLL时候应该向下兼容,然而要保证DLL完全向下兼容却是不能的。
                三是由新版DLL的安装引入一个新的Bug。

二、linux下的解决方案——命名规范       
       Linux 上的Dll ,叫sharedlibrary。Linux 系统面临和Window一样的问题,如何控制动态库的多个版本问题。为解决这个问题,Linux 为解决这个问题,引入了一套命名机制,如果遵守这个机制来做,就可以避免这个问题。但是这只事一个约定,不是强制的。但是建议遵守这个约定,否则同样也会出现 Linux 版的Dll hell 问题。

Real Name        
          首先是共享库本身的文件名:共享库的命名必须如 libname.so.x.y.z最前面使用前缀”lib”,中间是库的名字和后缀”.so”,最后三个数字是版本号。x是主版本号(Major Version Number),y是次版本号(Minor Version Number),z是发布版本号(Release Version Number)。

主版本号(不兼容):重大升级,不同主版本的库之间的库是不兼容的。所以如果要保证向后兼容就不能删除旧的动态库的版本。

次版本号(向下兼容): 增量升级,增加一些新的接口但保留原有接口。高次版本号的库向后兼容低次版本号的库。

发布版本号(相互兼容):库的一些诸如错误修改、性能改进等,不添加新接口,也不更改接口。主版本号和次版本号相同的前提下,不同发布版本之间完全兼容。

SO-NAME       
         严格遵守上述规定,确实能避免动态库因为版本冲突的问题,但是读者可能有疑问:在程序加载或运行的时候,动态链接器是如何知道程序依赖哪些库,如何选择库的不同版本?
Solaris和Linux等采用SO-NAME( Shortfor shared object name )的命名机制来记录共享库的依赖关系。每个共享库都有一个对应的“SO-NAME”(共享库文件名去掉次版本号和发布版本号)。比如一个共享库名为libtest.so.3.8.2,那么它的SO-NAME就是libtest.so.3。

在Linux系统中,系统会为每个共享库所在的目录创建一个跟SO-NAME相同的并且指向它的软连接(Symbol Link)。这个软连接会指向目录中主版本号相同、次版本号和发布版本号最新的共享库。也就是说,比如目录中有两个共享库版本分别为:/lib/libtest.so.3.8.2和/lib/libtest.so.3.7.5,么软连接/lib/libtest.so.3指向/lib/libtest.so.3.8.2。

建立以SO-NAME为名字的软连接的目的是,使得所有依赖某个共享库的模块,在编译、链接和运行时,都使用共享库的SO-NAME,而不需要使用详细版本号。在编译生产ELF文件时候,如果文件A依赖于文件B,那么A的链接文件中的”.dynamic”段中会有DT_NEED类型的字段,字段的值就是B的SO-NAME。这样当动态链接器进行共享库依赖文件查找时,就会依据系统中各种共享库目录中的SO-NAME软连接自动定向到最新兼容版本的共享库。

★  readelf -d sharelibrary 可以查看so-name
★  Linux提供了一个工具——ldconfig,当系统中安装或更新一个共享库时,需要运行这个工具,它会遍历默认所有共享库目录,比如/lib,/usr/lib等,然后更新所有的软链接,使她们指向最新共享库。

Link Name 
       当我们在编译器里使用共享库的时候,如用GCC的“-l”参数链接共享库libtXXX.so.3.8.1,只需要在编译器命令行指定 -l XXX 即可,省略了前缀和版本信息。编译器会根据当前环境,在系统中的相关路径(往往由-L参数指定)查找最新版本的XXX库。这个XXX就是共享库的“链接名”。不同类型的库可能有相同的链接名,比如C语言运行库有静态版本(libc.a)也动态版本(libc.so.x.y.z)的区别,如果在链接时使用参数”-lc”,那么连接器就会根据输出文件的情况(动态/静态)来选择合适版本的库。eg. ld使用“-static”参数时吗,”-lc”会查找libc.a;如果使用“-Bdynamic”(默认),会查找最新版本的libc.so.x.y.z。

更详细可以参见

http://www.linuxidc.com/Linux/2012-04/59071.htm

代码:

1. File libhello.c

/* hello.c - demonstrate library use. */
#include <stdio.h>
void hello(void)
{ printf("Hello, library world./n");}

2. File libhello.h

/* libhello.h - demonstrate library use. */
void hello(void);

3. File main.c

/* main.c -- demonstrate direct use of the "hello" routine */
#include "hello.h"
int main(void)
{
hello();
return 0;
}

1.生成共享库,关联real name 和soname 。

gcc -g -Wall -fPIC -c hello.c -o hello.o

gcc -shared -W,soname,-libhello.so.0 -o libhello.so.0.0.0 hello.o

将会生成共享库libhello.so.0.0.0.

可以用系统提供的工具查看共享库的头:

readelf -d libhello.so.0.0.0 | grep libhello

ox00000000000e(SONAME)    library soname: [libhello.so.0]

2.应用程序,引用共享库。

先手动生成link 名字,以被后面的程序链接时用

ln -s libhello.so.0.0.0 libhello.so.0

gcc -g -Wall -c main.c -o main.o -I.

gcc  -o main main.o -lhello -L.

(这里我会出问题。因为:执行gcc  -o main main.o -lhello -L.命令的时候,默认会找libhello.so这个文件,但是显然没有,直接出错。我又执行:

ln -s libhello.so.0.0.0 libhello.so 之后,才可以,到现在,没明白为什么?)

查看编译出来的程序:

readelf -d main | grep libhello

ox000000000001(NEEDED)    shared library: [libhello.so.0]

运行该程序,需要指定共享库的路径。 有两种办法,第一种使用环境变量“LD_LIBRARY_PATH”. 两外一种办法就是将共享库拷贝到系统目录(path 环境变量指定的其中一个目录)。

暂停! 我们还没有解决一个问题是,程序只知道soname,怎么从soname 找到共享库,即real name 文件呢? 这需要我们定义一个link文件,连接到共享库本身。

ln -s libhello.so.0.0.0 libhello.so.0

当然这个路径需要放到LD_LIBRARY_PATH环境变量中。

这样就可以运行该程序。

[Note]Linux 系统提供一个命令 ldconifg 专门为生成共享库的soname 文件,以便程序在加载时后通过soname 找到共享库。 同时该命令也为加速加载共享库,把系统的共享库放到一个缓存文件中,这样可以提高查找速度。可以用下面命令看一下系统已有的被缓存起来的共享库。

ld -p

运行程序:

./main这样,直接运行是不行了。动态库必须在运行的时候,也指定路径。所以,请将.so文件,复制到/lib或者/usr/lib下,然后执行 ldconfig /lib命令就OK了。

ldconfig是一个动态链接库管理命令
为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig
ldconfig  命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态 链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为  /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.
ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令.)

3.共享库,小版本升级,即接口不变.

当升级小版本时,共享库的soname 是不变的,所以需要重新把soname 的那个连接文件指定新版本就可以。 调用ldconfig命令,系统会帮你做修改那个soname link文件,并把它指向新的版本呢。这时候你的应用程序就自动升级了。

4.共享库,主版本升级,即接口发生变化。

当升级主版本时,共享库的soname 就会加1.比如libhello.so.0.0.0 变为 libhello.so.1.0.0. 这时候再运行ldconfig 文件,就会发现生成两个连接 文件。

ln -s libhello.so.0---->libhello.so.0.0.0

ln -s libhello.so.1----->libhello.so.1.0.0

尽管共享库升级,但是你的程序依旧用的是旧的共享库,并且两个之间不会相互影响。

问题是如果更新的共享库只是增加一些接口,并没有修改已有的接口,也就是向前兼容。但是这时候它的主版本号却增加1. 如果你的应用程序想调用新的共享库,该怎么办? 简单,只要手工把soname 文件修改,使其指向新的版本就可以。(这时候ldconfig 文件不会帮你做这样的事,因为这时候soname 和real name 的版本号主板本号不一致,只能手动修改)。

比如: ln -s libhello.so.0 ---> libhello.so.1.0.0

但是有时候,主版本号增加,接口发生变化,可能向前不兼容。这时候再这样子修改,就会报错,“xx”方法找不到之类的错误。

总结一下,Linux 系统是通过共享库的三个不同名字,来管理共享库的多个版本。 real name 就是共享库的实际文件名字,soname 就是共享库加载时的用的文件名。在生成共享库的时候,编译器将soname 绑定到共享库的文件头里,二者关联起来。 在应用程序引用共享库时,其通过link name 来完成,link时将按照系统指定的目录去搜索link名字找到共享库,并将共享库的soname写在应用程序的头文件里。当应用程序加载共享库时,就会通过soname在系统指定的目录(path or LD_LIBRARY)去寻找共享库。

当共享库升级时,分为两种。一种是主板本不变,升级小版本和build 号。在这种情况下,系统会通过更新soname( ldconfig 来维护),来使用新的版本号。这中情况下,旧版本就没有用,可以删掉。

另外一种是主版本升级,其意味着库的接口发生变化,当然,这时候不能覆盖已有的soname。系统通过增加一个soname(ldconfig -p 里面增加一项),使得新旧版本同时存在。原有的应用程序在加载时,还是根据自己头文件的旧soname 去寻找老的库文件。

5.如果编译的时候没有指定,共享库的soname,会怎么样?

这是一个trick 的地方。第一系统将会在生成库的时候,就没有soname放到库的头里面。从而应用程序连接时候,就把linkname 放到应用程序依赖库里面。或者换句话说就是,soname这时候不带版本号。 有时候有人直接利用这点来升级应用程序,比如,新版本的库,直接拷贝到系统目录下,就会覆盖掉已经存在的旧的库文件,直接升级。 这个给程序员很大程度的便利性,如果一步小心,就会调到类似windows的Dll hell 陷阱里面。建议不要这样做。

【Note】

1. 指定共享库加载的路径。LD_LIBRARY_PATH 优先于 path 环境变量。

2. ldd 可以查看程序,或者共享库依赖的库的路径

3. nm 查看共享库暴露的接口

4. ldconfig 可以自动生成soname 的连接文件。并提供catch 加速查找。

5.readelf 可以查看动态库的信息,比如依赖的库,本身的soname。

6. objdump 与readelf 类似。

7 ld The GUN linker

8. ld.so  dynamic linker or loader

9. as the portable GNU assembley

【Reference】

http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html

LINUX总结第13篇:LINUX下动态库及版本号控制的更多相关文章

  1. LINUX下动态库及版本号控制

    针对同一动态组件的不同版本链接和加载. 一.概念                  DLL HELL字面意思是DLL"灾难",是由于com组件(动态库)升级引起的程序不能运行的情况 ...

  2. Linux下动态库查找路径的问题

    说到和动态库查找路径相关的问题,总体上可以分为两类:    第一类: 通过源代码编译程序时出现的找不到某个依赖包的问题,而如果此时你恰好已经按照它的要求确确实实.千真万确.天地良心地把依赖库给装好了, ...

  3. 谈谈Linux下动态库查找路径的问题 ldconfig LD_LIBRARY_PATH PKG_CONFIG_PATH

    谈谈Linux下动态库查找路径的问题 ldconfig LD_LIBRARY_PATH  PKG_CONFIG_PATH 转载自:http://blog.chinaunix.net/xmlrpc.ph ...

  4. 谈谈Linux下动态库查找路径的问题

    学习到了一个阶段之后,就需要不断的总结.沉淀.清零,然后才能继续"上路".回想起自己当年刚接触Linux时,不管是用源码包编译程序,还是程序运行时出现的和动态库的各种恩恩怨怨,心里 ...

  5. [转]谈谈Linux下动态库查找路径的问题

    http://blog.chinaunix.net/uid-23069658-id-4028681.html 学习到了一个阶段之后,就需要不断的总结.沉淀.清零,然后才能继续“上路”.回想起自己当年刚 ...

  6. 深入理解LINUX下动态库链接器/加载器ld-linux.so.2

    [ld-linux-x86-64.so.2] 最近在Linux 环境下开发,搞了好几天 Compiler 和 linker,觉得有必要来写一篇关于Linux环境下 ld.so的文章了,google上搜 ...

  7. 转:谈谈Linux下动态库查找路径的问题

    http://blog.chinaunix.net/uid-23069658-id-4028681.html 学习到了一个阶段之后,就需要不断的总结.沉淀.清零,然后才能继续“上路”.回想起自己当年刚 ...

  8. Linux下动态库生成和使用

    Linux下动态库生成和使用 一.动态库的基本概念 1.动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序.动态链接库是目标文件的集合,目标文件在动态链接库中 ...

  9. linux 运行时加载不上动态库 解决方法(转)

    1. 连接和运行时库文件搜索路径到设置     库文件在连接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的.一般 Linux 系统把 /lib 和 /usr ...

随机推荐

  1. Window 点击“X”关闭之后无法show

    将Window的属性closeAction设置为hide就可以了. var panel1 = Ext.create('Ext.panel.Panel', { title: 'this is panel ...

  2. BZOJ 2876 骑行川藏

    http://www.lydsy.com/JudgeOnline/problem.php?id=2876 拉格朗日乘数法:f'+入g'=0,f为函数的导数,g为限制条件的导数. 思路:E=Σki*si ...

  3. SCSI磁盘标准的架构与文档

    来自scsi标准的官方网站http://t10.org/,具体的文档可以去浏览官方网站. (*) This chart reflects the currently approved SCSI pro ...

  4. SWFUpload批量上传插件

    SWFUpload是一个批量上传插件,在HTML4.1里面,估计也只有Flash+javascript配合才能够做到了.先复制个重要的网址,这个应该是官方的文档了,相当齐全. http://leeon ...

  5. scheme一页纸教程

    这是一个大学教授写的,非常好,原文:http://classes.soe.ucsc.edu/cmps112/Spring03/languages/scheme/SchemeTutorialA.html ...

  6. centos linux中怎么查看和修改计算机名/etc/sysconfig/network

    centos linux中怎么查看和修改计算机名 查看计算机名:在终端输入hostname 修改的话 hostname +计算机名(重启后失效)要永久修改的话要修改配置文件/etc/sysconfig ...

  7. GRUB启动管理器

    Linux学习笔记之 5 Linux GRUB启动管理器 1.GRUB简介 1.1grub与启动引导器     启动引导器是计算机启动过程中运行的第一个真正的软件,通常计算机启动时在通过BIOS自检后 ...

  8. easy_install django==1.4.2_百度搜索

    easy_install django==1.4.2_百度搜索 安装指定版本的django

  9. poj 3311 Hie with the Pie(状态压缩dp)

    Description The Pizazz Pizzeria prides itself or more (up to ) orders to be processed before he star ...

  10. struts2复习(五)拦截器总结

    1. 拦截器(Interceptor): 拦截器是Struts2的核心,Struts2的众多功能都是通过拦截器来实现的. 2. 拦截器的配置 1)编写实现Interceptor 接口的类.  2)在s ...