什么是 .so.2 文件?

11
我使用 GCC 从源代码编译了 Intel TBB。它生成了 libtbb.so 和 littbb.so.2 两个文件。看起来,.so.2 文件才是真正的共享库,libtbb.so 只包含一行文本 INPUT (libtbb.so.2)
为什么会生成这两个文件而不是一个?对于 INPUT (libtbb.so.2),它的语法是什么?我想了解更多关于这方面的内容。

6
与版本1、版本3或版本4.2等不同的是,libtbb.so的第二个版本。 - Mats Petersson
2个回答

10
通常在构建共享对象(.so)时,您还要通过添加后缀(例如mylib.so.2.3.1)来处理版本。为确保您的程序可以加载此库或其他较新版本,您需要创建具有名称的链接。
mylib.so -> mylib.so.2.3.1
mylib.so.2 -> mylib.so.2.3.1
mylib.so.2.3 -> mylib.so.2.3.1

因此,.so之后的所有内容代表版本.子版本.构建号(或类似形式)。 此外,使用该方案可以同时存在多个相同库的版本,将程序切换到使用特定版本所需的全部就是放置适当的链接。


如何知道一个.so文件是真正的.so文件还是只包含了指向某个真实文件的链接?除了打印出内容之外,有没有更简单的方法? - Ryan
1
一个链接在ls -la中显示为文件,它的大小只有几个字节。一个真正的.so包含了所有库代码,因此它的大小要大得多。你也可以使用file命令。 - DNT
@Ryan: 你可以使用file命令。例如:file mylib.so会告诉你它是ASCII文本还是ELF共享对象(如果是Linux)。 - kevinarpe

5
动态链接的 ELF 二进制文件(无论是另一个库还是可执行文件)使用 共享对象名称或 soname 来标识在执行时应该链接到的库。
当创建为 ELF 共享库的库时,编译时链接编辑器会将 DT_SONAME 字段插入可执行文件中,将库的 SONAME 插入库本身。DT_SONAME 在 ELF 标准 中定义为:

此元素保存空字符结尾的字符串的字符串表偏移量,给出共享对象的名称。 偏移量是记录在 DT_STRTAB 条目中的表中的索引。 有关这些名称的更多信息,请参见下面的“共享对象依赖性”。

现在,当可执行文件被创建时,其SONAME被嵌入其中。运行可执行文件时,链接器使用它来查找动态库在预定义位置的文件中。在Windows中,预定义位置将是DLL所在的任何位置。在Linux、Mac OS X和其他兼容System V的系统中,它们将是/lib和/usr/lib等位置,这取决于所使用的链接器,并可以在链接器自己的配置中定义。
在所有情况下,链接器都会查看soname条目中命名的库是否存在于这些位置中的任何一个,如果存在,则会使用它。
请注意,标准规定soname是一个字符串,版本控制约定在事后成为一种事实上的标准,大致如下:
将soname设置为libmyname.so.A,将库文件名设置为libmyname.so.A.B或libmyname.so.A.B.C(在MacOSX中为libmyname.A.B.dylib)。从libmyname.so.A.B[.C]创建到libmyname.so.A的符号链接。
当库的ABI保持不变时,A保持不变。

B(或B.C)成为次要版本。

在Linux下,库版本通常与软件包版本号相同。这有其优缺点。

libtool规范化

GNU libtool被广泛用于构建动态库,并具有更正式的版本控制系统和强大的逻辑。 sonames的libtool版本控制系统非常好,并被复杂库采用以保持事情清晰。

在libtool下,版本控制如下:

libmylib-current.release.age

在libtool下,库的功能会随着时间的推移而增加和减少。
假设您正在开发一个库。请从版本0.0.0开始使用。
现在假设您修复了一些错误,那么只需要增加发布号码。
因此,每次发布仅修复错误但不更改任何ABI时,新名称将为libmylib.0.1.0或libmylib.0.2.0等。
在开发过程中,您可能会发现:唉!我可以更好地完成这个子功能,因此添加了一组新的功能来实现更好的操作,但由于其他人仍在使用您的库,因此您仍然保留旧的(已弃用)功能。
规则如下:
  1. 每个 libtool 库的版本信息从 ‘0:0:0’ 开始。

  2. 仅在软件公开发布前立即更新版本信息。更频繁地更新是不必要的,也只是保证当前接口号更快地增加。

  3. 如果自上次更新以来库的源代码发生了任何变化,则增加修订版号(‘c:r:a’ 变为 ‘c:r+1:a’)。

  4. 如果自上次更新以来添加、删除或更改了任何界面,请增加当前版本号,并将修订版号设置为 0。

  5. 如果自上次公开发布以来添加了任何接口,则增加年龄。

  6. 如果自上次公开发布以来删除或更改了任何接口,则将年龄设置为 0。

您可以在 libtool 文档 中了解更多信息

更新 ...

以下是一个评论,指出我的解释有误。实际上并没有错误,需要更详细的说明才能回答。请看下面。
原始反驳:

这里有一个错误:在Linux上,版本的形式为libmylib.(current-age).release.age,其中括号表示要评估的表达式。例如,带有当前版本:修订版:年龄= 37:1:1的GLPK 4.54在Linux上安装库文件libglpk.so.36.1.1。有关更多信息,请参见,例如,<autotools.io/libtool/version.html>。

反驳:
Flameeyes虽然是一位了不起的开发者,也是Gentoo维护者之一,但他犯了错误,并创建了一个松散的libtool规范的“经验法则”解释。虽然这不会99%的时间破坏系统,但如果我们遵循更新“current”的临时方式:
处理这些值时的经验法则是:

始终增加修订值。

每当添加、移除或更改接口时,增加当前值。

仅在所做的ABI更改向后兼容时增加年龄值。

他接着说,在维护多个版本的Gtk时,最好将库版本附加到库名称中,然后简单地丢弃版本号。(就像在GTK+中所做的那样):

In this situation, the best option is to append part of the library's version information to the library's name, which is exemplified by Glib's libglib-2.0.so.0 soname. To do so, the declaration in the Makefile.am has to be like this:

lib_LTLIBRARIES = libtest-1.0.la

libtest_1_0_la_LDFLAGS = -version-info 0:0:0
这是一种毫无意义的炖锅方法来破坏动态链接和符号解析版本控制的强大功能!他的意思是只需关闭它。真是胡说八道!难怪即使经验丰富的开发人员也很难构建和维护开源项目,我们总是在每次安装库的新版本时遇到二进制文件死机(因为它们会相互覆盖)。
Libtool版本控制方法非常周密。它是一个算法,其步骤是有序的指令,每当动态链接库的代码更新时,都要按照1至6的顺序执行。
对于新的和现有的开发人员,请仔细阅读并想象您惊人软件的整个生命周期中库版本号会发生什么变化。如果您这样做,您会注意到每个以前链接的软件将始终正确使用最新和准确的库版本,并且它们永远不会相互覆盖或践踏,而且您永远不必在库名称中添加一个数字(除非出于乐趣或美学)。

这里有一个错误:在Linux上,版本的格式为libmylib.(current-age).release.age,其中括号表示要评估的表达式。例如,在Linux上使用current:revision:age = 37:1:1的GLPK 4.54安装库文件libglpk.so.36.1.1。有关更多信息,请参见https://autotools.io/libtool/version.html。(一旦答案中纠正了这个问题,我会取消我的踩票。) - equaeghe
1
零个人怎么可能认为这个答案有帮助呢?有时候这个网站真是疯狂。感谢您写下如此详细的答案,这是一个很好的教学参考。 - kevinarpe
@equaeghe 我在这里为其他人补充一下答案。 - Ahmed Masud
你的反驳与我的评论无关,我的评论指出了不区分发布版本 36.1.1 和 libtool 版本 37:1:1 的问题。(注意 :. 的区别。)这也是我提到 https://autotools.io/libtool/version.html 的原因,其中指出:“警告 一个常见的错误是假设传递给 -version-info 的三个值直接映射到库名称末尾的三个数字。[...] 对于 Linux,[...] 虽然最后两个值直接从命令行映射,但第一个值是通过从 current 中减去 age 计算得出的。[...]”。 - equaeghe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接