http://www.linuxidc.com/Linux/2012-04/59071.htm
代码:
/* hello.c - demonstrate library use. */
#include <stdio.h>
void hello(void)
{ printf("Hello, library world./n");}
/* libhello.h - demonstrate library use. */
void hello(void);
/* 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