共享库文件简介
1. 概述
在本教程中,我们将探索如何在 Linux 文件系统中组织so (共享库)文件。
2. 例子
为了更好地理解本文的上下文,我们需要一个示例。由于这可能因操作系统而异,让我们使用ldconfig 工具找到一些可使用的库:
$ ldconfig -p
....
libGLX.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLX.so.0
libGLU.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLU.so.1
libGLESv2.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLESv2.so.2
libGL.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGL.so.1
libFLAC.so.8 (libc6,x86-64) => /lib/x86_64-linux-gnu/libFLAC.so.8
libEGL_mesa.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libEGL_mesa.so.0
libEGL.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libEGL.so.1
libBrokenLocale.so.1 (libc6,x86-64, OS ABI: Linux 3.2.0) => /lib/x86_64-linux-gnu/libBrokenLocale.so.1
libBrokenLocale.so (libc6,x86-64, OS ABI: Linux 3.2.0) => /lib/x86_64-linux-gnu/libBrokenLocale.so
libBLTlite.2.5.so.8.6 (libc6,x86-64) => /lib/libBLTlite.2.5.so.8.6
libBLT.2.5.so.8.6 (libc6,x86-64) => /lib/libBLT.2.5.so.8.6
ld-linux-x86-64.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
使用ldconfig,我们可以看到该系统上安装的所有库的打印列表。
让我们去zip 库:
$ ldconfig -p | grep "libzip.so*"
libzip.so.5 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzip.so.5
libzip.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libzip.so
在这个系统上,zip 库位于/lib/x86_64-linux-gnu/libzip.so*。
请注意,如果 zip 库不存在,我们将不得不从列表中选择另一个libname.so文件。
$ ls -l /lib/x86_64-linux-gnu/libzip.so*
lrwxrwxrwx 1 root root 11 Nov 27 2018 /lib/x86_64-linux-gnu/libzip.so -> libzip.so.5
lrwxrwxrwx 1 root root 13 Nov 27 2018 /lib/x86_64-linux-gnu/libzip.so.5 -> libzip.so.5.0
-rw-r--r-- 1 root root 105672 Nov 27 2018 /lib/x86_64-linux-gnu/libzip.so.5.0
因此,以libzip 为例,让我们讨论三个不同的文件及其编号,例如*.so.5和.so.5.0*。
3. Linux 上的 SO 文件组织
我们可以看到我们选择的示例中有三个文件:
/lib/x86_64-linux-gnu/libzip.so
/lib/x86_64-linux-gnu/libzip.so.5
/lib/x86_64-linux-gnu/libzip.so.5.0
这些文件中的每一个都有一个特殊的名称和用途:
libzip.so # is called the linker-name used for linking.
libzip.so.5 # is called the soname used by the operating system loader.
libzip.so.5.0 # is called the real-name which is updated by the library maintainer.
其中“zip”是库的名称。
每个文件的末尾都有不同的编号。这些数字代表库的版本。它们非常重要,因为正是这些版本决定了每个文件的角色。
它们是两种流行的版本控制方案。一是语义版本控制。另一个变体是 Libtool 版本控制方案。
让我们从更详细地讨论这些版本方案开始,然后使用使用的命名约定。然后我们将详细讨论每个文件的作用。
3.1. Linux 版本控制
Linux 中的每个库都有其文件名中写入的版本信息。例如,lbizip.so.5.0具有 libzip.so.XYZ 的通用形式。这也称为语义版本控制 ,Linux 系统上的每个库都遵循的约定。
X 代表主要版本。当库开发人员进行非向后兼容更改时,他们将不得不增加主要版本。
Y 代表次要版本。向后兼容的更改会增加次要版本。例如,添加一个孤立的功能,如新功能。另请注意,如果 Z 编号不存在,那么这也可能代表错误修复。
Z 是补丁号。这是可选的。错误修复将增加补丁版本。
一个更重要的方面是发布版本。
让我们快速浏览一下已安装的llvm库:
$ ldconfig -p | grep LLVM
libLLVM-9.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-9.so.1
libLLVM-9.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-9.so
libLLVM-8.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-8.so.1
libLLVM-8.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-8.so
**注意这里有两个版本,libLLVM-9和libLLVM-8用于同一个库llvm。**版本 8 中的分歧意味着该库不再向后兼容。因此,库开发人员决定创建一个单独的版本libLLVM-9。
3.2. Libtool 版本控制
重要的是不要将libtool版本方案 与语义版本混淆。libtool版本控制方案的目标是跨多个平台标准化版本。
使用*libtool * 的开发人员可以使用libtool版本方案;但是, libtool会尽可能地将其转换为Linux 系统的语义版本控制。
与语义版本控制相反。** libtool版本方案具有 XYZ 编号字段的当前、修订和年龄**。这不是主要、次要和补丁。libtool版本方案还附带了一组关于何时增加版本值的规则!
3.3. 命名约定
查看ldconfig列出的各种库,我们可以注意到一个约定。
ldconfig实用程序和其他工具期望so(共享对象)文件遵循 lib*.so* 或 ld-*.so* 的命名模式,其中后者仅为动态链接器保留。这个约定在许多 Linux 工具中是硬编码的。这就是为什么ldconfig列出的所有库都以lib开头的原因。
期望此约定的工具的一个示例是GCC 。当开发人员尝试使用 GCC 编译代码时,库名称与*-l*标志一起传递:
gcc a.c -l zip
链接器采用字符串“lib”并附加“zip”(库名称)。这会产生一个名为libzip.so的文件,链接器会尝试查找该文件。
结论是 Linux 环境中的工具遵循libname.so的命名约定。
4. SO 文件
让我们详细看看每个链接器文件。首先,让我们通过实名制。
** 实名是磁盘上物理库的实际文件名。**重要的一点是,如果我们按如下方式扩充libzip库:
# Linker-name
/lib/x86_64-linux-gnu/libzip.so
# sonames
/lib/x86_64-linux-gnu/libzip.so.4
/lib/x86_64-linux-gnu/libzip.so.5
# real-names
/lib/x86_64-linux-gnu/libzip.so.4.1
/lib/x86_64-linux-gnu/libzip.so.4.2
/lib/x86_64-linux-gnu/libzip.so.5.0
我们可以在 Linux 系统上拥有多个物理库。他们都幸福地生活在同一个文件夹中。通过创建soname文件,我们可以使用适当的库来执行应用程序。开发人员甚至可以通过更改链接器名称来选择要链接的库。
让我们详细看看其余的概念。
5. linker名称
libzip.so是一个符号链接,没有与之关联的数字。它被称为linker名称。
由于链接器的搜索机制而得名。搜索机制接受一个作为库名称的键并返回一个具有libname.so模式的文件。 作为一个符号链接并且没有版本,libzip.so可用于指向任何libzip版本:
$ ls -lah /lib/x86_64-linux-gnu/libzip.so
lrwxrwxrwx 1 root root 15 Mar 9 12:45 /lib/x86_64-linux-gnu/libzip.so -> libzip.so.5
在此特定示例中,libzip.so指向最新的主要libzip版本libzip.so.5。
默认情况下,符号链接由安装脚本设置以指向他们正在安装的库的版本。如果我们想更改要链接的库,我们可以更改符号链接。
** linker名称符号链接作为一个概念提供了链接器可以依赖的一致接口。**它还使我们可以自由地告诉链接器要搜索和链接哪个库文件。
6. soname
现在让我们更详细地看一下soname。libzip.so.5是通过运行ldconfig自动生成的。
让我们看看这是如何工作的:
首先,让我们删除libzip.so.5,如下所示:
$ sudo rm /usr/lib/x86_64-linux-gnu/libzip.so.5
$ ls -l /usr/lib/x86_64-linux-gnu/libzip*
lrwxrwxrwx 1 root root 33 May 21 16:21 /usr/lib/x86_64-linux-gnu/libzip.so -> /lib/x86_64-linux-gnu/libzip.so.5
-rw-r--r-- 1 root root 105672 Nov 27 2018 /usr/lib/x86_64-linux-gnu/libzip.so.5.0
所以我们已经成功删除了libzip.so.5的符号链接。
现在,让我们运行ldconfig:
$ sudo ldconfig
$ ls -l /usr/lib/x86_64-linux-gnu/libzip*
lrwxrwxrwx 1 root root 33 May 21 16:21 /usr/lib/x86_64-linux-gnu/libzip.so -> /lib/x86_64-linux-gnu/libzip.so.5
lrwxrwxrwx 1 root root 13 May 21 16:26 /usr/lib/x86_64-linux-gnu/libzip.so.5 -> libzip.so.5.0
-rw-r--r-- 1 root root 105672 Nov 27 2018 /usr/lib/x86_64-linux-gnu/libzip.so.5.0
啊哈!libzip.so.5又回来了。它指向实际的库libzip.so.5.0。
这里重要的一点是,ldconfig如何知道要创建什么符号链接?
让我们更详细地看一下libzip.so.5.0文件:
$ readelf -d /usr/lib/x86_64-linux-gnu/libzip.so.5.0
Dynamic section at offset 0x18db0 contains 28 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
0x0000000000000001 (NEEDED) Shared library: [libbz2.so.1.0]
0x0000000000000001 (NEEDED) Shared library: [libcrypto.so.1.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libzip.so.5]
....
我们可以看到soname被写入了库的元数据中。开发人员将其设置为libzip.so.5。符号链接libzip.so.5是一个库组,代表libzip库的主要版本 5。
因此,符号链接反映了实际位于物理库libzip.so.5.0中的元数据。
为了更好地理解它是如何使用的,让我们看一下*ls *:
$ ldd `which ls`
linux-vdso.so.1 (0x00007ffd0319c000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f02b281d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f02b262b000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f02b259b000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f02b2595000)
/lib64/ld-linux-x86-64.so.2 (0x00007f02b2883000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f02b2572000)
如我们所见,ldd 列出了ls的运行时依赖项。第一列包含共享对象的sonames。第二列是共享对象的路径以及加载它的地址。
让我们以libpthread.so.0为例,试着想想这个条目是如何添加的:
$ ls -l /usr/lib/x86_64-linux-gnu/libpthread*
-rwxr-xr-x 1 root root 157224 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
-rw-r--r-- 1 root root 6587378 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread.a
lrwxrwxrwx 1 root root 37 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread.so -> /lib/x86_64-linux-gnu/libpthread.so.0
lrwxrwxrwx 1 root root 18 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread.so.0 -> libpthread-2.31.so
编译ls时,链接器遵循libpthread.so的链接器名称符号链接。这导致查看libpthread-2.31.so的元数据,其中链接器从真实库libpthread-2.31.so中提取了soname元数据。
$ readelf -d libpthread-2.31.so | grep soname
0x000000000000000e (SONAME) Library soname: [libpthread.so.0]
此元数据包含已添加到运行时依赖项列表的soname libpthread.so.0 。
**这表明soname的主要目的是用于执行应用程序。**动态链接器使用soname将这些文件加载到内存中。这样,开发人员可以在后台更新库而不会破坏任何运行时依赖项。