Linux共享库的组织

2022/12/15 Linux内核架构与系统编程 共 3804 字,约 11 分钟

共享库系统路径

​目前大多数包括Linux在内的开源操作系统都遵守一个叫做FHS(File Hierarchy Standard)的标准,这个标准规定了一个系统中系统文件应该如何存放,包括各个目录的结构、组织和作用。一个系统中主要有两个存放共享库的位置,如下:

  • /lib, 这个位置主要存放系统最关键和基础的共享库,比如动态链接器、C语言运行库、数学库等,这些库主要是那些/bin和/sbin 下的程序所需要的用到的库,还有系统启动时需要的库;
  • /usr/bin, 这个目录下保存的是一些非系统运行时所需要的关键性的共享库,主要是一些开发时用到的共享库,这些共享库一版不会被用户的程序或者shell脚本直接用到。这个目下还包括了开发时可能用到的静态库和目标文件等;
  • /usr/local/bin, 这个目录用来放置一些跟操作系统本身相关性不大的库,主要是一些第三方应用的库。比如我们在系统中安装了python语言的解释器,那么与它相关的共享库可能会被放在/usr/local/lib/python, 而他的可执行文件会被放在 /usr/local/bin下。

总体来看,/lib和/usr/lib是一些常用的、成熟的,一般是系统本身所需要的的库;而/usr/local/lib则是非系统所需的第三方程序的共享库。

共享库的查找过程

​在系统中,动态链接的ELF可执行文件会在启动时同时启动动态连接器。在Linux系统中,动态链接器是/lib/ld-linux.so.X(X是版本号),程序所依赖的共享对象全部有动态链接器负责装载和初始化。我们知道任何一个动态链接的模块所依赖的模块路径保存在“.dynamic” 段里,由DT_NEED类型的项表示。动态链接器对于模块的查找有一定的规则:如果DT_NEED里保存的是绝对路径,那么动态链接器就按照这个路径去查找;如果是相对路径,那么动态链接器会在/lib、/usr/lib和由/etc/ld.so.conf配置文件指定的目录去查找共享库。

ld.so.conf是一个文本配置文件,它可能包含其他的配置文件。 linux_0001

​如果动态链接器每次查找共享库时都去遍历这些目录,那将会非常耗时。所以Linux系统中都有一个叫ldconfig的程序,这个程序的作用是为共享目录下的各个共享库创建、删除或者更新相应的SO-NAME(即相应的符号链接),这样每个共享库的SO-NAME都能够指向正确的共享库文件;并且这个程序可能会将这些SO-NAME收集起来,集中放在/etc/ld.so.cache里面查找。而/etc/ld.so.cache的结构是经过特殊设计的,非常适合查找,所以能够很快找到共享库。

如果动态链接器在/etc/ld.so.cache里面没有找到所需要的共享库,那么他还会遍历/lib和/sur/lib这两个目录,如果还是没找到,就宣告失败。

环境变量

LD_LIBRARY_PATH

​改变共享库查找路径最简单的办法是使用LD_LIBRARY_PATH环境变量,这个方法可以临时改变某个应用程序的共享库查找路径,而不会影响其他程序。

Linux中还有一种方法可以实现与LD_LIBRARY_PATH类似的功能,那就是直接运行动态链接器来启动程序,比如:

/lib/ld-linux.so.2 -library-path /home/user/bin/ls 

有了LD_LIBRARY_PATH之后,再来总结动态链接器查找共享库的顺序。动态链接器会按照下列顺序依次装载或者查找共享对象(目标文件):

  • 由环境变量LD_LIBRARY_PATH指定的路径;
  • 由路径缓存文件/etc/ld.so.cache 指定的路径;
  • 默认共享库目录,先/usr/lib, 然后/lib。

LD_LIBRARY_PATH对于共享库开发测试来说十分方便,但是它不应该被滥用。也就是说普通用户在正常情况下不应该随意设置LD_LIBRARY_PATH来调整共享库搜索目录。随意修改并且将其导出至全局范围,将有可能引起其他程序运行出现问题。LD_LIBRARY_PATH也会影响GCC编译时查找库的路径,它里面包含的目录相当于链接时GCC的“-L” 参数。

LD_PRELOAD

​LD_PRELOAD中我们可以指定预先装载的一些共享库或者是目标文件。在LD_PRELOAD里面指定的文件会在动态链接器按照固定规则搜索共享库之前装载,它比LD_LIBRARY_PATH里面所指定的目录中的共享库还要有限。无论程序是否依赖于他们LD_PRELOAD里边指定的共享库或者目标文件都会被装载。

​由于全局符号接入这个 机制的存在,在LD_PRELOAD里边指定的共享库或者目标文件中的全局符号就会覆盖后面加载的同名全局符号,这使得我们可以很方便的做到改写标准C库中的某些函数而不影响其他函数,对于程序的调试和测试非常有用。与LD_LIBRARY_PATH一样,正常情况下应该尽量避免使用LD_PRELOAD,比如一个发布版本的程序运行不应该依赖于LD_PRELOAD。

LD_DEBUD

此变量可以打开动态链接器的调试功能,当我们设置这个变量时,动态链接器会在运行时打印出各种有用的信息,对于开发和调试共享库有很大的帮助。比如设置LD_DEBUD的值为“files”,并且运行一个简单的程序:LD_DEBUD=files ./test1.out ,动态链接器会打印整个装载过程,显示程序依赖于哪些共享库,并且按照哪些步骤装载和初始化,共享库装载时的地址等。LD_DEBUD还可以设置成其他值,如下: linux_0002

共享库的创建与安装

共享库的创建

​创建共享库的过程跟创建一版的共享对象的过程基本一致,最关键的是使用GCC的两个参数,即“-shared” 和 “-fPIC”。“-shared” 表示输出的结果是共享库类型的;“-fPIC” 表示使用地址无关代码(Position Independent Code)技术来生产输出文件。另外还有一个参数是“-Wl” 参数,这个参数可将指定的参数传递给链接器,比如当我们使用“-Wl, -soname, my_soname” 时,GCC会将“ -soname my_soname” 传递给链接器,可以指定输出共享库的SO-NAME。

​比如我们有两个源代码文件libfoo1.c 和libfoo2.c ,希望产生一个libfoo.so.1.0.0的共享库,这个共享库依赖于libbar1.so和libbar2.so两个共享库,我们可以使用如下命令行:

gcc -shared -fPIC -wl,-soname, libfoo.so.1 -o libfoo.so.1.0.0 libfoo1.c libfoo2.c -lbar1 -lbar2

​几个值得注意的事项:

  • 不要把输出共享库中的符号和调试信息去掉,也不要使用GCC的“ -fomit-frame-pointer”选项,这样做虽然不会导致共享库停止运行,但是会影响调试共享库,给后面的工作带来很多麻烦。
  • 在开发过程中,你可能测试新的共享库牡丹石又不希望影响现有的程序正常运行。我们之前提到的LD_LIBRARY_PATH是一个很好的方法,用它可以指定共享库的查找路径。还有一种方法是使用链接器的“-rpath” 选项(或者GCC的-Wl,-rpath),这种方法可以指定连接产生的目标程序的共享库查找路径。比如我们用如下命令产生一个可执行文件:
ld -rpath /home/mylib -o program.out program.o -lsomelib

这样产生的输出可执行文件的program.out在被动态链接器装载时,动态链接器会首先在“/home/mylib”查找共享库

清除符号信息

​正常编译的共享库或者可执行文件中带有符号信息和调试信息,对于发布版本来说这些信息用处不大,并且使文件尺寸变大。可以使用strip工具进行清除。

strip libfoo.so

去掉符号和调试信息以后文件往往比之前要小很多,一版只有原来的一半大小。除了使用strip工具,我们还可以使用ld的“-s” 和“-S”参数,“-S”是消除调试符号信息,“-s”消除所有符号信息。我们也可以在GCC中通过“-Wl, -s”和“-Wl, -S”给ld传递这两个参数。

共享库的安装

​创建共享库以后我们需要将它安装在系统中,以便于各种程序都可以共享它。最简单的办法是将共享库复制到某个标准的共享库目录,如/lib, /usr/lib等,然后运行ldconfig即可。

​不过上述方法往往需要系统的root权限,如果没有,则无法往/lib, /usr/lib等目录添加文件,也无法运行ldconfig程序。当然也有其他办法安装共享库,稍微麻烦一些,可以是建立相应的SO-NAME软链接,并告诉编译器和程序如何查找该共享库,以便于编译器和程序都能够正常运行。建立SO-NAME的办法也是使用ldconfig,只不过需要指定共享库的目录:

ldconfig -n shared_library_directory

在编译程序时,也需要制定共享库的位置,GCC提供了两个参数“-L”和“-l”,分别用于指定共享库的搜索目录和共享库的路径。当然也可以使用前面提过的“-rpath”参数,具体可以参考GCC手册,前面提过的LD_LIBRARY_PATH的方法也可以指定某个共享库的位置。

参考

  • 《程序员的自我修养–连接、装载与库》 第8章

文档信息

Search

    Table of Contents