首先回答前面的问题,一共有多少种方法来指定告诉linux共享库链接器ld.so已经编译好的库libbase.so的位置呢?

答案是一共有五种,它们都可以通知ld.so去哪些地方找下已经编译好的c语言函数动态库,它们是:

1)ELF可执行文件中动态段中DT_RPATH所指定的路径。即在编译目标代码时, 对gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径,eg:gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c

2)环境变量LD_LIBRARY_PATH 指定的动态库搜索路径

3)/etc/ld.so.cache中所缓存的动态库路径,这个可以通过先修改配置文件/etc/ld.so.conf中指定的动态库搜索路径,然后执行ldconfig命令来改变。

4)默认的动态库搜索路径/lib

5)默认的动态库搜索路径/usr/lib

另外:在嵌入式Linux系统的实际应用中,1和2被经常使用,也有一些相对简单的的嵌入式系统会采用4或5的路径来规范动态库,3在嵌入式系统中使用的比较少, 因为有很多系统根本就不支持ld.so.cache。

那么,动态链接器ld.so在这五种路径中,是按照什么样的顺序来搜索需要的动态共享库呢?答案这里先告知就是按照上面的顺序来得,即优先级是:1-->2-->3-->4-->5。我们可以写简单的程序来证明这个结论。

首先,写成5个函数,这5个函数名称都叫pt,但是里面的内容不一样:

pt1.c
#include void pt(){
printf("1 path on the gcc give \n");
}
pt2.c
#include void pt(){
printf("2 path on the LD_LIBRARY_PATH \n");
}
pt3.c
#include void pt(){
printf("3 path on the /etc/ld.so.conf \n");
}
pt4.c
#include void pt(){
printf("4 path on the /lib \n");
}
pt5.c
#include void pt(){
printf("5 path on the /usr/lib \n");
}

然后,分别编译这5个函数,然后将它们分别移到上面5种情况对应的5个不同目录下:

gcc -fPIC -c pt1.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/1/
gcc -fPIC -c pt2.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/2/
gcc -fPIC -c pt3.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/3/
gcc -fPIC -c pt4.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /lib/
gcc -fPIC -c pt5.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /usr/lib/

再次,编写一个main函数m,让它来调用函数pt:

m.c
#include /*void pt();*/
int main(){
printf("start....\n");
pt();
printf("......end\n");
return 0;
}

最后,准备环境,让ld都知道这5个路径:

(a) 往/etc/ld.so.conf总增加一行,内容:/tmp/st/3,然后执行 ldconfig 命令

(b) export LD_LIBRARY_PATH=/tmp/st/2

另外3中路径,ld都可以得到,请接着看下面。

之后测试:

gcc m.c -o m1 -L/tmp/st/1 -lpt -Wl,-rpath,/tmp/st/1
./m1
start....
1 path on the gcc give
......end

这里在可执行文件中动态段中DT_RPATH所指定的路径,因此需要在编译m.c的时候就指定路径,由于其他路径都也告诉了ld,很明显,此种方法优先级最高了。

gcc m.c -o m -L/tmp/st/1 -lpt
./m
start....
2 path on the LD_LIBRARY_PATH
......end

这里很显然调用了LD_LIBRARY_PATH指定了路径中的共享库,因此此种情况优先级第二。

mv /tmp/st/2/libpt.so /tmp/st/2/libpt2.so
/m
start....
3 path on the /etc/ld.so.conf
......end

这里是调用了/etc/ld.so.cache中所缓存的动态库路径中的共享库,因此此种情况优先级第三。

mv /tmp/st/3/libpt.so /tmp/st/3/libpt3.so
./m
start....
4 path on the /lib
......end

这里是调用/lib中的共享库,优先级第四。

rm /lib/libpt.so
./m
start....
5 path on the /usr/lib
......end

这里是调用/usr/lib中的共享库,优先级第五。

故证明这五种路径指定方法的优先级是1-->2-->3-->4-->5!

# ldconfig -p 发现有两个libevent, 应用程序会使用其中哪一个呢?

综合多个人给出的答案, 如果两个libevent的soname相同, 那么将会调用ldconfig -p 先打印出来的那个:

# ldconfig -p | grep libevent
libevent-1.4.so.2 (libc6,x86-64) => /usr/local/lib/libevent-1.4.so.2
libevent-1.4.so.2 (libc6,x86-64) => /usr/lib64/libevent-1.4.so.2
# ldd /usr/local/bin/memcached
linux-vdso.so.1 =>  (0x00007fff51d7b000)
libevent-1.4.so.2 => /usr/local/lib/libevent-1.4.so.2 (0x00002ac681ea6000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003672200000)
libc.so.6 => /lib64/libc.so.6 (0x0000003671600000)
libnsl.so.1 => /lib64/libnsl.so.1 (0x0000003675200000)
librt.so.1 => /lib64/librt.so.1 (0x0000003672600000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x0000003677600000)
/lib64/ld-linux-x86-64.so.2 (0x0000003671200000)
# cd /usr/local/lib
# rename libevent bak_libevent libevent*
# ldconfig
# ldd /usr/local/bin/memcached
linux-vdso.so.1 =>  (0x00007fff9316f000)
libevent-1.4.so.2 => /usr/lib64/libevent-1.4.so.2 (0x00002b1fafb0b000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003672200000)
libc.so.6 => /lib64/libc.so.6 (0x0000003671600000)
libnsl.so.1 => /lib64/libnsl.so.1 (0x0000003675200000)
librt.so.1 => /lib64/librt.so.1 (0x0000003672600000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00000036

命名约定

在开始探讨共享对象之前,简要地看一下库的命名规则。静态库一般由字母 lib开头,并有 .a 的扩展名。共享对象有两个不同的名称: soname和 real name。 soname 包含前缀 "lib",然后紧跟库名,其次是 ".so"(后面紧跟另一个圆点),以及表明主版本号的数字。soname 可以由前缀的路径信息来限定。real name 是包含库的已编译代码的真正文件名。real name 在 soname 后添加一个圆点、小的数字、另外一个圆点和发布号。(发布号和其相应的圆点是可选的。)

我们会看到由 Program-Library How-To 定义的名称,称作 linker name ,它指的是不带版本号的 soname。客户所使用的库名是指 linker name。通常,这是与 linket name 的链接。而 soname 是到 real name 的链接。

这里拿 soname /usr/lib/libhello.so.1来作为示例。这是一个全限定 soname,它链接到 /usr/lib/libhello.so.1.5 。其相应的 linker name 是 /usr/lib/libhello.so 。这里似乎要管理许多名称,但有工具可以帮助您管理他们。(请参阅本文后面将要讲述的 实用程序和工具 中的 ldconfig 。)

现在我们来看实质性内容,并编写一个样本共享对象。库的 soname 是 libprint.so.1 ,real name 是 libprint.so.1.0 。库有一个 printstring(char*) 的函数,它将打印字 "String: ",后面紧跟着以参数形式传递给它的任何字符串。