天天看点

linux 共享库那点事

        大多数大型软件项目将包含几个组件,您可能会在以后的某些其他项目中发现其中一些组件,或者只是出于组织目的而将它们分开。当您具有一组可重用或逻辑上不同的函数时,从中构建一个so库将很有帮助,这样您就不必将源代码复制到当前项目中并始终重新编译它-这样您就可以保留不同的模块您的程序不相交,只更改其中一个而不会影响其他程序。一旦编写并测试了它,您就可以一次又一次地安全地重复使用它,从而节省了时间和每次将其构建到项目中的麻烦。

       构建静态库非常简单,并且由于我们很少有关于它们的问题,因此我不会介绍它们。我们讲解一下有关共享库,这对于大多数人来说似乎更加令人困惑。 

      讲解中要使用的代码例子:

 foo.h 文件:

#ifndef foo_h__
#define foo_h__

extern void foo(void);

#endif  // foo_h__
           

foo.c 文件

#include <stdio.h>


void foo(void)
{
    puts("Hello, I am a shared library");
}
           

  main.c 文件

#include <stdio.h>
#include "foo.h"

int main(void)
{
    puts("This is a shared library test...");
    foo();
    return 0;
}
           

foo.h 文件定义了我们库的一个单独的函数接口 foo(),foo.c 文件包含了接口函数的实现,main.c 是使用了我们库文件的程序代码

步骤一 使用-pic 进行编译

     我们需要将我们的库源代码编译成与位置无关的代码。

$ gcc -c -Wall -Werror -fpic foo.c
           

步骤二 从目标文件创建共享库

gcc -shared -o libfoo.so foo.o
           

步骤三 链接共享库

如您所见,这实际上很容易。我们有一个共享库。让我们编译main.c并将其与libfoo链接,我们酱最终编译出来的程序命名为test,请注意,-lfoo选项不是在查找foo.o,而是在libfoo.so。 GCC假定所有库均以lib开头,并以.so或.a结尾(.so用于共享库或共享库,而.a用于静态链接库)。

$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status
           

 哦!链接器不知道在哪儿寻找ibfoo,GCC默认会去一系列的位置去查找,但我们的目录不在该位置列表中。我们需要使用-L 选项告诉gcc 去那个地方寻找我们的libfoo 库,在这个列子中我们使用当前路径

$ gcc -L/home/username/foo -Wall -o test main.c -lfoo
           

 步骤四 让库在运行是可用

但我们运行编译好的test 文件时

$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
           

 加载器不能发现我们的共享库(注释3),我们没有在标准的位置安装过它,所以需要帮助加载器一下,我们有一对选项:我们可以使用环境变量LD_LIBRARY_PATH或者rpath。让我们先尝试使用LD_LIBRARY_PATH

使用LD_LIBRARY_PATH

让我们将共享库的路径加到LD_LIBRARY_PATH 中

$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I am a shared library
           

test 可以执行了,LD_LIBRARY_PATH非常适合快速测试以及没有管理员权限的系统,但是,不利的一面是导出LD_LIBRARY_PATH变量意味着它可能会导致您运行的其他程序出现问题,这些程序也依赖LD_LIBRARY_PATH,如果您未将其恢复为以前的状态。

使用rpath

现在让我们试试rpath(首先我们要清空LD_LIBRARY_PATH)。Rpath或运行路径是一种将共享库的位置嵌入到可执行文件中的方法,而不是依赖于默认位置或环境变量。我们在链接阶段执行此操作,注意-Wl,-rpath=/home/username/foo 选项。-Wl部分将逗号分隔的选项发送到链接器,因此我们将-rpath 加上我们的工作目录选项发送到链接器。

$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I am a shared library
           

不错,这样也工作了,rpath方法很棒,因为每个程序都可以独立列出其共享库的位置。

rpath vs. LD_LIBRARY_PATH

 但是,rpath有一些缺点。首先,它要求将共享库安装在固定的位置,以便程序的所有用户都可以访问那些位置的那些库。这意味着系统配置的灵活性降低。其次,如果该库引用了NFS挂载或其他网络驱动器,则在程序启动时可能会遇到不希望的延迟

 使用ldconfig 命令 修改 ld.so

如果我们想安装我们的库,以便系统上的每个人都可以使用该库怎么办?为此,您将需要管理员权限。您出于两个原因需要这样做:首先,将库放置在标准位置,可能是/usr/lib或/usr/local/lib,普通用户没有写访问权限。其次,您将需要修改ld.so配置文件和缓存。以root用户身份执行以下操作:

$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so
           

现在,文件在标准位置并且具有正确的权限,每个人都可以读取。我们需要告诉加载器它可以使用,所以让我们更新缓存:

$ ldconfig
           

这将创建一个指向我们共享库的链接并更新缓存,以便可以立即使用。让我们仔细检查:

$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so
           

现在我们的库已安装。在测试之前,我们必须清理一些事情,再次清除LD_LIBRARY_PATH,以防万一:

$ unset LD_LIBRARY_PATH
           

重新链接我们的可执行文件。注意,我们不需要-L选项,因为我们的库存储在默认位置,并且我们没有使用rpath选项:

$ gcc -Wall -o test main.c -lfoo
           

让我们确保使用ldd使用库的/usr/lib:

$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)
           

不错让我们试着运行它

$ ./test
This is a shared library test...
Hello, I am a shared library
           

我们已经介绍了如何构建共享库,如何与之链接以及如何解决共享库中最常见的加载器问题-以及不同方法的正面和负面影响。

总结一下共享库的加载次序:

1  在可执行文件的DT_RPATH(-Wl,--rpath=)节中查找,除非这儿有DT_RUNPATH(-Wl,--rpath=.,--enable-new-dtags )节

2 在LD_LIBRARY_PATH中查找。如果出于安全原因将可执行文件设置为setuid / setgid,则将跳过此步骤。

3 在可执行文件的DT_RUNPATH中查找,除非设置了setuid / setgid位(出于安全原因)。

4 在缓存文件/etc/ ld /so/cache中查找(可以使用-z nodeflib链接器选项禁用)。

5 在默认的/lib 和/usr/lib  中查找可以使用-z nodeflib链接器选项禁用)。

继续阅读