Program Library HOWTO(1)

来源:互联网 发布:淘宝如何跨店凑单 编辑:程序博客网 时间:2024/05/21 22:49

看到一个有用的关于在Linux下创建和使用库的文档,随手翻译(不全)。
原文链接:http://tldp.org/HOWTO/Program-Library-HOWTO/index.html


简介

一个“程序库”指的是一个包含了编译代码(和数据)的文件,此文件会合并到以后需要使用到它的程序当中。程序库会的优点有:使得程序变得更加模块化、重编译的速度更快、更容易升级代码。

这个文档首先讨论静态库(static library),静态库是在程序运行之前已经整合到可执行文件之前。之后讨论共享库(shared library),是在程序开始时加载并且在程序之间共享。最后讨论动态加载库(DL: dynamically loaded),可以被程序任何时候使用。

大部分开发程序库的开发人员都会使用共享库,因为其可以允许使用者单独的升级每一个库(update their libraries separately from the applications that use the libraries)。动态加载库十分有用,但是其需要稍微复杂的流程才能使用,而且大部分程序并不需要如此高的灵活性。相反的,静态库的升级会显得麻烦一点,因此一般情况下并不推荐使用静态库。但是,他们每个都有各自的优点和缺点。用C++的开发人员和使用动态加载库可以查阅文档“C++ dlopen mini-HOWTO”。

这份文档只讨论可执行文件和库的Executable and Linking Format(ELF)格式文件,这种格式如今广泛的应用在各Linux的发布版本当中。

如果你想构建一个可以输出到不同系统,你应该考虑用GNU libtool来生成和install库,而不是直接用Linux的tools

GNU libtool is a generic library support script that hides the complexity of using shared libraries (e.g., creating and installing them) behind a consistent, portable interface. On Linux, GNU libtool is built on top of the tools and conventions described in this HOWTO. For a portable interface to dynamically loaded libraries, you can use various portability wrappers. GNU libtool includes such a wrapper, called “libltdl”. Alternatively, you could use the glib library (not to be confused with glibc) with its portable support for Dynamic Loading of Modules. You can learn more about glib at http://developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html. Again, on Linux this functionality is implemented using the constructs described in this HOWTO. If you’re actually developing or debugging the code on Linux, you’ll probably still want the information in this HOWTO.


静态库

静态库是简单的传统目标文件(object files)的集合,一般的静态库是以“.a”后缀的文件。静态库是用ar(archiver) 程序来生成。

静态库允许使用者连接到程序时,不需要重编译代码(应该是静态库)(翻译的不好这里),节省重编译的时间。由于现在编译器变快了,因此重编译时间显得没那么重要了。静态库常用于以下场景:开发人员允许程序员链接到他们的库,但是不想给源代码出去(类似库提供商有利,但对程序员是不利的)。理论上,使用静态库链接的代码会比共享库和动态加载库运行速度提高(1%-5%),但实际应用上可能被其它因素给掩盖掉了。

生成一个静态库,或者将一个目标文件加入到存在的静态库中,用以下命令:

ar rcs my_library.a file1.o file2.o

这个命令将目标文件file1.o 和file2.o添加到静态库my_library.a中,若不存在则新建它。

若想使用该静态库,用户可以在创建程序的可执行文件时在编译和链接的过程中唤醒该过程。如果是使用gcc来生成可执行文件,你可以使用 -l选项来指定用到哪一个库。

在使用gcc时需要逐一它的参数的先后顺序;-l选项是一个linker的选项,并且需要一个在即将编译文件的名字后面添加。这是跟正常的语法大相径庭的,如果不这样做(放在前面)就会出现莫名其妙的错误。


共享库

程序在他们开始运行是加载共享库。当共享库能够被正确的installed,所有之后开始运行的程序都会自动的使用新的共享库。除此之外,由于Linux的允许,还可以更加的灵活和精细:

  • 更新了库以后,依然支持程序使用旧版本的库。这些库的非向后兼容版本;
  • 程序运行时,可以覆写特定库或者覆写某个库里的函数
  • 在程序运行的过程中可以完成以上所有步骤

convention

对于共享库的特性,有很多规则需要遵守。你需要理解不同库名字的不同,尤其是“soname”和“real name”(和他们是如何交互的)。你也应该知道他们应该放在文件系统的何处。

共享库的名字

每一个共享库都有一个特殊的名字叫做“soname“。soname有一个前缀“lib”,然后是库的名字,后面紧跟着是period和版本号(即每一次interface的概念会递增的数字)。一个fully-qualified的soname包括它所在目录的前缀(?),在working system中是共享库“real name”的符号链接(symbolic link)。

每一个共享库也有一个“real name”,也即是包括真实库代码的文件名字。real name在soname上补充了

The real name adds to the soname a period, a minor number, another period, and the release number. The last period and release number are optional. The minor number and release number support configuration control by letting you know exactly what version(s) of the library are installed.

除此以外,还有一个名字是编译器在请求一个库时候所用到的名字,(称为linker name),即是没有任何版本号的soname。
.
管理共享库的关键就是这些名字的separation。当程序内部列出他们所需要的共享库,应该只为他们列出soname。相反的,当你建立一个共享库的时候,你只要建立一个指定文件名字的库即可(可以有更多的版本信息)。当你install一个新版本的库是,你将它install在特定几个目录下然后运行ldconfig程序。ldconfig检测存在的文件然后建立sonames作为real names的符号链接(symbolic links),同事设置缓存文件 /etc/ld.so.cache(会在下文解释)。

ldconfig并不会设置linker 的名字。这是在库的安装过程中已经完成了,linker 的名字是简单的建立为对“latest”soname或者“latest”real name的符号链接。我会建议linker name是soname的符号链接,因为在大部分的情况下,如果你更新了库,你会想自动使用它当链接的时候。我问H.J.Lu,为什么ldconfig不会自动的设置linker names。他的解释是,基本上你会选择使用最新的库当你跑代码的时候,但是,有时也会选择旧版本的库。因此,ldconfig不会假设你会想时有那个库来链接的,那么安装的时候就必须特别的改变符号链接,更新linker到底使用哪个库。

从而,/usr/lib/libreadline.so.3是一个fully-qualified soname,ldconfig会设置一个符号链接到real name 类似 /usr/lib/libreadline.so.3.0. 那里也会有一个linker name , /usr/lib/libreadline.so ,是引用 /usr/lib/libreadline.so.3的符号链接。

文件系统 Placement

共享库必须放置在文件系统的某个地方。对于大多数的开源软件,都会趋向于遵从GNU的标准。> for more information see the info file documentation at info:standards#Directory_Variables.

GNU的标准建议在distributing source code时安装的所有库都安装在 /usr/local/lib 这个默认目录下(并且所有的commands should go into /usr/local/bin)。它们还定义了覆盖这些默认值和调用安装例程的约定。

Filesystem Hierarchy Standard(FHS)讨论了什么文件需要放在哪里合适在一个distribution当中( http://www.pathname.com/fhs)。根据FHS,大部分的库应该安装在 /usr/lib上,但是系统启动需要的库则放在 /lib上,系统不需要的应该放在/usr/local/lib上。

这两个文件之间并不存在实际意义上的冲突。

the GNU standards recommend the default for developers of source code, while the FHS recommends the default for distributors (who selectively override the source code defaults, usually via the system’s package management system).

(也就是一个文件是给开发人员建议的,另一个文件是建议系统版本distributors)。在实际的应用当中是works nicely:你下载最新的源代码(很可能是有bug的)会自动的把它们安装到“local”目录下(/usr/local),并且一旦代码成熟了,package managers 会覆写然后将代码放到distributions的标准路径上。需要注意的是,当你的库需要调用只能通过库来调用的程序时,你需要将那些程序放在/usr/local/libexec(如果是distribution的话就放在/usr/libexec下)。对于Red Hat驱动的系统,它的编译器不会默认的在/usr/local/lib中来搜索库;看下面关于 /etc/ld.so.conf的讨论。

Other standard library locations include /usr/X11R6/lib for X-windows. Note that /lib/security is used for PAM modules, but those are usually loaded as DL libraries (also discussed below).

How libraries are used

在GNU glibc-based的系统中,包括所有的Linux系统,打开一个ELF的二进制可执行文件会自动的导致程序loader的加载和运行。在Linux系统中,这个loader叫做 /lib/ld-linux.so.X(X是版本号)。这个loader,找到并且加载程序所用到的所有共享库。

被搜索的路径列表存储在这个文件下:/etc/ld.so.conf。很多Red Hat驱动的发布版本的上述文件并没有包括 /usr/local/lib这个路径。我认为这是一个bug,因此需要手动加入这个路径到这个文件底下来修复这个bug。

如果你想覆写一个库里面的函数,并且保留库的其余部分,你应该将这个库(.o文件)添加到以下文件:/etc/ld.so.preload。这些“preloading”的库会在标准集之前被搜索到。这个preloading文件是为了发布紧急的补丁所使用的,一般发布版本的程序不会包括这个文件的。

搜索所用的目录,在程序的开始时会造成效率低下,因此需要一个缓存的机制来提高效率。ldconfig这个程序会默认读取 /etc/ld.so.conf这个文件,设置好合适的富豪链接到动态链接目录上(name他们就会follow the standard conventions),然后会写一个cache到 /etc/ld.so.cache这个文件下,用作其他程序之用。这样会极大的提高链接相应库的速度。潜在的意思是,ldconfig这个程序会再库的添加,库的删除,和库的包含路径变更都会运行。运行ldconfig也是包管理者安装库时候的一个不走。On start-up ,动态的loader会实际上调用/etc/ld.so.cache这个文件,然后加载程序所需要的库。

By the way, FreeBSD uses slightly different filenames for this cache. In FreeBSD, the ELF cache is /var/run/ld-elf.so.hints and the a.out cache is /var/run/ld.so.hints. These are still updated by ldconfig(8), so this difference in location should only matter in a few exotic situations.

环境变量

LD_LIBRARY_PATH

在Linux下,环境变量LD_LIBRARY_PATH是一个以分好分割的路径,是搜索库时在标准集之前优先搜索的路径。这对于调试一个新bug和用一个非标准的库时是十分有用的。LD_PRELOAD这个环境变量列出了对标准集库函数进行覆写的共享库, 也就是 /etc/ld.so.preload做的那样。这些都被loader /lib.ld-linux.so 应用。

LD_DEBUG ####(略)

Other Environment Variables ####(略)

*创建一个共享库

创建一个共享库是很简单的。首先,创建需要用到gcc -fPIC或者gcc -fpic来生成目标文件,使其能够被添加到共享库里。-fPIC和-fpic选项都是使得开启“position independent code”这个功能,一个队共享库所必需的功能。然后你要通过 -W1 的gcc选项来传soname。见下面的例子,-W1选项会传递选项的之道linker那里(这里就是-soname),-W1后面的逗号不是笔误,而且不能有空格。创建共享库会用到一下的格式命令:

gcc -shared -Wl,-soname,your_soname \    -o library_name file_list library_list

这里有一个例子,首先创建两个目标文件(a.o 和 b.o),然后创建一个共享库包含这两个目标文件。注意到,这条命令包括了调试的命令(-g)和会生成警告(-Wall),这都不是生成共享库所必需的,但是推荐使用。编译器会生成目标文件(用-c),并且包括所必需的-fPIC选项:

gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,-soname,libmystuff.so.1 \    -o libmystuff.so.1.0.1 a.o b.o -lc

这里有几点需要注意的:

  • Don’t strip the resulting library, and don’t use the compiler option -fomit-frame-pointer unless you really have to. The resulting library will work, but these actions make debuggers mostly useless.
  • 使用 -fPIC 或者 -fpic来生成代码。无论是-fPIC 或者-fpic生成的代码都是目标依赖的(target-dependent)。-fPIC选项无论怎样都能使用,但是会生成较大的代码。-fpic选项会生成较小和较快的代码,但是会依赖于平台,例如全局可见的符号数量或者代码的大小。linker 会告诉你是否可行当你创建共享库的时候。如果有疑问,我会选择-fPIC,因为它永远都会成功。
  • 在一些情况下,需要包括以下的选项“-W1,-export-dynamic”。一般的,动态符号表会包括哪些被动态目标文件使用的符号。这个选项的作用是(在生成一个ELF文件时),将所有的符号添加到动态符号表中。你需要用到这个选项当有“reverse dependencies”的时候,例如,一个DL库有一个未解决的符号,一般这个符号必需在程序中定义使能够加载这些库。为了使“reverse dependencies”能够运作,master program必需要使它的符号动态的可获取。注意到,如果只是对于Linux系统,你可以用“-rdynamic”来代替“-W1,export-dynamic”,但是根据ELF的文档,“-rdynamic”标志不能再非Linux的系统下工作。

*Installing and Using a Shared Library

以下的简单例子会将库复制到一个标准的目录下(例如,/usr/lib)然后启动ldconfig。

首先,你将会需要建立一个共享库,然后,你需要设置必需的符号链接,特别是一个从soname到real name 的链接(对一个没有版本号的soname也是)。最简单的实践例子如下:

 ldconfig -n directory_with_shared_libraries

最后,当你需要编译你的程序时,你需要告诉链接器你需要使用的任何静态或者共享库。用-l 和-L选项。

如果你不能或者不想安装一个库到标准的目录下(例如你不能修改/usr/lib中的库),name你需要另一种方法。这种情况下,你需要将这个库安装在其他地方,并且给足够的信息给你的程序让它可以找到对应的库。有好几种解决方案。简单的话,可以使用gcc 的-L标志来解决。你可以使用“rpath”方法,尤其你只有一个程序用到不在标准目录下的库。你也可以通过环境变量来解决这个问题。你可以设置LD_LIBRARY_PATH这个变量。如果你是使用bash,你可invoke my_program以以下的方式:

LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH  my_program

通常情况下,你可以无需顾虑的更新你的库。如果有API需要改变的话,库应该更改它的soname。这样的话,单系统下可以兼容多个库,每个程序都能正确链接到匹配的库中。(关于如何wrapper旧库略)

你可以通过ldd程序来列出所有的共享库。例如,

 ldd /bin/ls

通常例会看到一个被依赖的soname的列表,紧跟着是这些名字指向的目录。实际操作史昂,你会至少包括以下两个依赖:

  • /lib/ld-linux.so.N(N是1或以上,通常至少是2)。这是加载其他所有库的库。
  • libc.so.N(N是6或以上)。这是一个C库。对于大多数程序都会包括这个库。因为其他语言都会或多或少用到C的库。(?)

Beware:对于不信任的程序不要运行ldd。(解释略)

*Incompatible Libraries

当一个新版本的库不能与旧版本兼容,则soname需要改变。在C中,有4个原因导致终止binary compatible:

  1. 函数的行为改变,导致不再跟原来的说明一致。
  2. 输出的数据改变(例外:在结构体的最后增加item,只要那些结构体都只是在库内分配的)
  3. 一个输出的函数被移除
  4. 一个输出函数的接口改变了。

如果你能够避免以上的情况,你的库可以做到binary-compatible。换句话说,如果你可以避免上述改变,你可以保持你的Application Binary Interface(ABI)兼容。例如,你如果想增加新的函数但是不删除旧的那个。你可以在结构体内增加items只有在你确保就得程序不会对此敏感的情况下,可以在结构体的尾部加入新的item,只让库来allocate the structure,making the extra items optional and so on。注意,当用户使用结构体数组时,你不能扩充该结构体。

对于C++(and other languages supporting compiled-in templates and/or compiled dispatched methods),情况就有些trickier。以上的情况适用,而且还包括海量的issues。以下是不完整的列表——保持binary compatibility,不能做的事情:

  1. add reimplementations of virtual functions (unless it it safe for older binaries to call the original implementation), because the compiler evaluates SuperClass::virtualFunction() calls at compile-time (not link-time).
  2. 增加或者移除虚成员函数,因为这会改变每一个子类的vtbl的大小和layout。
  3. 改变可以被inline的成员函数访问的数据成员的数据类型或者移除它
  4. 改变class hierarchy
  5. 增加或者移除私有的数据成员,因为这会改变所有子类的大小和layout
  6. 移除公共或者protected的成员函数,除非他们是inline
  7. 使一个公共或者protected的成员函数inline
  8. 改变一个inline函数的行为,除非旧版本能够继续运行
  9. 在一个portable程序中改变成员函数的的访问方式。

鉴于这么长需要注意的列表,C++的开发人员尤其必要的规划更新,放置打破binary compatibility。幸运的是,在Unix-like系统中(包括Linux),你的库可以拥有多个版本,并同时被加载,那么尽管是一些磁盘空间的占用浪费,但是使用者依然可以运行需要旧库的“旧”程序。

0 0