C语言的本质(35)——共享库

时间:2021-08-20 06:53:21

库用于将相似函数打包在一个单元中。然后这些单元就可为其他开发人员所共享,并因此有了模块化编程这种说法— 即,从模块中构建程序。Linux支持两种类型的库,每一种库都有各自的优缺点。静态库包含在编译时静态绑定到一个程序的函数。动态库则不同,它是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的。

使用共享库的方法有两种:您既可以在运行时动态链接库,也可以动态加载库并在程序控制之下使用它们。本文对这两种方法都做了探讨。

静态库较适宜于较小的应用程序,因为它们只需要最小限度的函数。而对于需要多个库的应用程序来说,则适合使用共享库,因为它们可以减少应用程序对内存(包括运行时中的磁盘占用和内存占用)的占用。这是因为多个应用程序可以同时使用一个共享库;因此,每次只需要在内存上复制一个库。要是静态库的话,每一个运行的程序都要有一份库的副本。

GNU/Linux 提供两种处理共享库的方法(每种方法都源于Sun Solaris)。您可以动态地将程序和共享库链接并让 Linux 在执行时加载库(如果它已经在内存中了,则无需再加载)。另外一种方法是使用一个称为动态加载的过程,这样程序可以有选择地调用库中的函数。使用动态加载过程,程序可以先加载一个特定的库(已加载则不必),然后调用该库中的某一特定函数(图 2 展示了这两种方法)。这是构建支持插件的应用程序的一个普遍的方法。我稍候将在本文探讨并示范该应用程序编程接口(API)。

Linux下的共享库类似windows下的dll,共命令约定如下:

静态库一般由字母lib 开头,并有 .a 的扩展名,而共享对象有两个不同的名称:soname 和 real name。

soname 包含前缀 "lib",然后紧跟库名,其次是".so"(后面紧跟另一个圆点),以及表明主版本号的数字。

soname 可以由前缀的路径信息来限定。realname 是包含库的已编译代码的真正文件名。

real name 在 soname 后添加一个圆点、小的数字、另外一个圆点和发布号。格式如下:

libxxxx.so.major.minor

其中,xxxx是库的名字,major是主版本号,minor 是次版本号或叫发布号,次版本号和其相应的圆点是可选的。

soname是记录在共享库中的,其它库使用这个共享库时,实际上只需要的提供soname,动态链接器会找到名称是soname的动态库给程序使用。

这种带版本号的共享库主要是为了你可以很方便的升级你的函数库,如果某个API改变了,创建库的程序会改变主版本号,然而,如果一个函数升级了某个函数库,而功能没有发生变化,这时只需要改变次版本号,由于只改变了次版本号,所以soname没有发生改变,这样就可以做到与旧的共享库保持兼容。

下面简要说明动态库的编写过程:

/* file libhello.h - for example use! */
void printhello();

库的代码很基本,在下一个清单中显示。

/* file libprint.c */
#include "stdio.h"
void printhello()
{
printf("hello opendba/n");
}

编译:

gcc -fPIC -c libhello.c
ld -shared -soname libhello.so.1 -olibhello.so.1.0 -lc libhello.o

-soname也可以用-h代替。

注意,gcc 命令行中的 -fPIC 选项。这是生成 Position-Independent Code 所必须要的。把这个命令翻译出来就是:生成可以在进程的进程空间的任何地方载入的代码。这对于共享对象是非常重要的。使用这个选项,使得必须执行重定位的数量降低到最少。一旦载入可执行程序使用的共享对象,就必须给它分配一些空间。必须给文本和数据部分配一些位置。如果它们不是以“位置独立”方式来构建,那么载入共享对象时,程序要做大量的重定位,这会影响到性能。

现在我们分析一下传给 ld 的选项。-shared 选项表明输出的文件被认为是共享的库。通过 -soname name 选项,可以指定 soname 是什么。-o name 指定了共享对象的real name,也就是实际生成的动态库的文件名称。

为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig

ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为  /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.

ldconfig -p 输出共享库soname实际对应的共享库的文件名称。

  一个程序/shared库一般都要依赖其他的一些库,这可以用ldd来查看,它列出了依赖的库的soname,因为实际依赖是库的接口,而 soname正是反映了库的接口信息。linux使用ELF作为可执行程序和库的格式,这些依赖的库的soname保存在ELF的某个fileld里。当一个可执行程序执行时,ld.so负责把它所依赖的shared库加载到内存并链接,它按照以下顺序寻找shared库:

1. 在LD_LIBRARY_PATH环境变量指定的目录下

 2. ld.so.cache文件该shared库对应的文件

 3. /usr/lib和/lib目录下

环境变量:

LD_BIND_NOW --- 正常来讲,函数在呼叫之前是不会让程式寻找(looked up)的.设定这个旗号会使得程式库一载入,所有的寻找(lookups)便会发生,同时也造成起始的时间(startup time)较慢.当你想测试程式,确定所有的连结都没有问题时,这项旗号就变得很有用.

LD_PRELOAD 可以设定一个档案,使其具有*覆盖*(overriding)函数定义的能力.例如,如果你要测试记忆体分配的方略(strategies),而且还想置换*malloc*,那麽你可以写好准备替换的副程式(routine),并把它编译成mallolc. ,然後:

$LD_PRELOAD=malloc.o; export LD_PRELOAD
$ some_test_program

LD_ELF_PRELOAD  与LD_AOUT_PRELOAD  很类似,但是仅适用於正确的二进位型态.如果设定了 LD_ something _PRELOAD  与LD_PRELOAD  ,比较明确的那一个会被用到.

LD_LIBRARY_PATH  是一连串以分号隔离的目录名称,用来搜寻共享程式库.对ld而言,并没有 任何的影响;这项只有在执行期间才有影响.另外,对执行setuid与setgid的程式而言,这一项是无效的.而LD_ELF_LIBRARY_PATH与LD_AOUT_LIBRARY_PATH 这两种旗号可根据各别的二进位型式分别导向不同的搜寻路径.一般正常的运作下,不应该会用到LD_LIBRARY_PATH ;把需要搜寻的目录加到/etc/ld.so.conf/ 里;然後重新执行ldconfig.

LD_NOWARN 仅适用於a.out.一旦设定了这一项(LD_NOWARN=true; export LD_NOWARN ),它会告诉载入器必须处理fatal-warnings(像是次要版本不相容等)的警告讯息.

LD_WARN 仅适用於ELF.设定这一项时,它会将通常是致命讯息的"Can*t find library"转换成警告讯息.对正常的操作而言,这并没有多大的用处,可是对ldd就很重要了.

LD_TRACE_LOADED_OBJECTS  仅适用於ELF.而且会使得程式以为它们是由ldd所执行的:

 $LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynx
libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
libc.so.5 => /lib/libc.so.5.2.18