链接程序和库指南

附录 B 版本控制快速参考

ELF 目标文件可使得全局符号可用,这样,其他目标文件可绑定到这些全局符号。可以将其中某些全局符号标识为提供目标文件的公共接口。其他符号是目标文件内部实现的一部分,不能在外部使用。目标文件接口可以随着软件发行版的发展而升级,最好是具有标识此发展的功能。

此外,还可能需要标识目标文件随软件发行版的发展而发生的内部实现更改。

可以通过建立内部版本定义在目标文件内记录接口和实现标识。有关内部版本控制概念的更完整介绍,请参见第 5 章,应用程序二进制接口与版本控制

共享库是内部版本控制将使用的主要目标文件。此技术定义目标文件的发展过程,在运行时处理过程中提供接口验证(请参见绑定到版本定义),同时还提供可选用的应用程序绑定(请参见指定版本绑定)。本附录中多处使用共享库作为示例。

以下各节简要概述了链接编辑器提供的应用于共享库的内部版本控制机制,可以将这些概述称为备忘单。其中的示例为共享库的版本控制提供了一些建议的约定和机制(从初始构造到多个常见的更新方案)。

命名约定

共享库遵循的命名约定包括一个编号文件后缀。 请参见命名约定。在这个共享库中,可以创建一个或多个版本定义。每个版本定义都对应于以下类别之一:

以下版本定义命名约定有助于指明定义代表上述哪个类别。

这些类别中的前三个属于接口定义。这些定义由构成接口的全局符号名称与版本定义名称关联组成。 请参见创建版本定义。共享库内的接口更改通常称为次修订。因此,此类版本定义带有一个次要版本号后缀,次版本号基于文件名的主版本号后缀。

最后一个类别指明目标文件内发生了更改。此定义由充当标号的版本定义组成,没有与之关联的符号名称。因此将其称为弱版本定义 (weak version definition)。 请参见创建弱版本定义 (weak version definition)。共享库内的实现更改通常称为微修订。因此,此类版本定义带有一个微版本号后缀,微版本号基于应用内部更改的上一个次版本号。

任何行业标准的接口都应使用能够反映此标准的版本定义名称。任何供应商接口都应使用供应商独有的版本定义名称。通常使用公司的股票代码号。

专用版本定义指明限制使用或不供使用的符号,而且“专用”一词应清晰可见。

所有版本定义都会创建关联的版本符号名称。使用唯一名称和次/微后缀约定可减少在正在生成的目标文件中出现符号冲突的几率。

以下版本定义示例说明了这些命名约定的可能用途:

SVABI.1

定义 System V 应用程序二进制接口标准接口。

SUNW_1.1

定义 Solaris 公共接口。

SUNWprivate_1.1

定义 Solaris 专用接口。

SUNW_1.1.1

定义 Solaris 内部实现更改。

定义共享库的接口

建立共享库的接口时,应首先确定此共享库提供的哪些全局符号能够与三个接口版本定义类别之一关联:

通过定义这些接口,供应商可指明此共享库的每个接口的承诺级别。行业标准接口和供应商公共接口在各个发行版中几乎保持不变。如果确定应用程序在不同的发行版中都能继续正常运作,则可安全地随意绑定到这些接口。

其他供应商提供的系统上也可能存在行业标准接口。通过将应用程序限制为使用这些接口,可以实现更高级别的二进制兼容性。

其他供应商提供的系统上也可能不存在供应商公共接口。但是,当这些接口所在的系统发展时,它们会保持不变。

供应商专用接口非常不稳定,并且在不同发行版中会不同甚至会删除。这些接口用于实现未确定用途的功能或实验功能,或者是仅用于访问特定于供应商的应用程序。如果要实现某个级别的二进制兼容性,则应避免使用这些接口。

任何不属于上述任一类别的全局符号都应该缩减到本地范围,以使其不再可见,以免进行绑定。 请参见缩减符号范围

共享库的版本控制

确定共享库的可用接口后,可以使用 mapfile 和链接编辑器的 -M 选项创建关联的版本定义。有关此 mapfile 语法的介绍,请参见定义其他符号

以下示例定义了共享库 libfoo.so.1 中的供应商公共接口:


$ cat mapfile

SUNW_1.1 {                   # Release X.

        global:

                foo2;

                foo1;

        local:

                *;

};

$ cc -G -o libfoo.so.1 -h libfoo.so.1 -z text -M mapfile foo.c

全局符号 foo1foo2 指定给共享库的公共接口 SUNW_1.1。输入文件中提供的任何其他全局符号均通过自动缩减指令 “*” 缩减到本地。 请参见缩减符号范围


注 –

每个版本定义的 mapfile 项均应带有一个注释,用于反映更新的发行版或日期。可利用此信息将多个共享库更新整合(可能由不同的开发者进行)到一个版本定义中,从而使共享库的发布作为软件发行的一部分进行。


现有(非版本化)共享库的版本控制

对现有的非版本化共享库进行版本控制需要格外小心。上一个软件发行版中提供的共享库已使其所有全局符号可供其他目标文件绑定。尽管可以确定共享库的目标接口,但其他目标文件可能已发现其他符号并绑定到这些符号。因此,删除任何符号都可能会导致应用程序无法传送新的版本化共享库。

如果可以确定并应用接口,而不中断任何现有应用程序,则可以对现有的非版本化共享库进行内部版本控制。运行时链接程序的调试功能对验证各种应用程序的绑定要求非常有用。 请参见调试库。但是,确定现有绑定要求会假定共享库的所有用户都是已知的。

如果无法确定现有的非版本化共享库的绑定要求,则应使用新的版本化名称创建一个新的共享库文件。 请参见协调版本化文件名。除了这个新的共享库外,还必须传送原始共享库,以满足任何现有应用程序的依赖性。

如果要冻结原始共享库的实现,则只需维护和传送共享库二进制文件。但是,如果原始共享库需要更新,则更适合使用从中生成共享库的备用源树。修补程序升级可能需要更新,只有升级共享库的实现才能与新平台保持兼容时也可能要更新。

更新版本化共享库

能够对可执行内部版本控制的共享库进行的唯一更改就是兼容更改。 请参见接口兼容性。任何不兼容的更改都要求使用新的外部版本化名称生成一个新的共享库。 请参见协调版本化文件名

通过内部版本控制便可进行的兼容更新分为以下三种基本类别:

前两个类别可以通过将接口版本定义与相应符号关联来实现。后一个类别可以通过创建不具有任何关联的弱版本定义 (weak version definition) 来实现。

添加新符号

任何包含新全局符号的新的共享库兼容发行版都应将这些符号指定给新的版本定义。此新版本定义应继承上一个版本定义。

以下 mapfile 示例将新符号 foo3 指定给新接口版本定义 SUNW_1.2。此新接口继承原始接口 SUNW_1.1


$ cat mapfile

SUNW_1.2 {                   # Release X+1.

        global:

                foo3;

} SUNW_1.1;



SUNW_1.1 {                   # Release X.

        global:

                foo2;

                foo1;

        local:

                *;

};

版本定义的继承可减少所有共享库用户必须记录的版本信息量。

内部实现更改

任何包含目标文件实现更新(例如错误修复或性能改进)的新的共享库兼容发行版都应带有一个版本定义。此新版本定义应继承发生更新时所用的最新版本定义。

以下 mapfile 示例生成弱版本定义 (weak version definition) SUNW_1.1.1。此新接口指明对上一个接口 SUNW_1.1 提供的实现进行了内部更改。


$ cat mapfile

SUNW_1.1.1 { } SUNW_1.1;     # Release X+1.



SUNW_1.1 {                   # Release X.

        global:

                foo2;

                foo1;

        local:

                *;

};

新符号和内部实现更改

如果在同一个发行版中发生了内部更改并添加了新接口,则应创建弱版本定义 (weak version definition) 和接口版本定义。以下示例显示了添加版本定义 SUNW_1.2 和接口更改 SUNW_1.1.1 的情况,它们是在同一个发行周期内添加的。两个接口都继承原始接口 SUNW_1.1


$ cat mapfile

SUNW_1.2 {                   # Release X+1.

        global:

                foo3;

} SUNW_1.1;



SUNW_1.1.1 { } SUNW_1.1;     # Release X+1.



SUNW_1.1 {                   # Release X.

        global:

                foo2;

                foo1;

        local:

                *;

};

注 –

SUNW_1.1SUNW_1.1.1 版本定义的注释指明它们应用于同一个发行版


将符号迁移到标准接口

有时,新的行业标准中会采用供应商接口提供的符号。创建新标准接口时,请务必维护共享库提供的原始接口定义。创建一些中间版本定义,以便根据它们生成新标准定义和原始接口定义。

以下 mapfile 示例显示了添加新的行业标准接口 STAND.1。此接口包含新符号 foo4 以及现有符号 foo3foo1,它们最初分别通过接口 SUNW_1.2SUNW_1.1 提供。


$ cat mapfile

STAND.1 {                    # Release X+2.

        global:

                foo4;

} STAND.0.1 STAND.0.2;



SUNW_1.2 {                   # Release X+1.

        global:

                SUNW_1.2;

} STAND.0.1 SUNW_1.1;



SUNW_1.1.1 { } SUNW_1.1;     # Release X+1.



SUNW_1.1 {                   # Release X.

        global:

                foo2;

        local:

                *;

} STAND.0.2;

                             # Subversion - providing for

STAND.0.1 {                  # SUNW_1.2 and STAND.1 interfaces.

        global:

                foo3;

};

                             # Subversion - providing for

STAND.0.2 {                  # SUNW_1.1 and STAND.1 interfaces.

        global:

                foo1;

};

符号 foo3foo1 被引入各自的中间接口定义(用于创建原始接口定义和新接口定义)中。

SUNW_1.2 接口的新定义引用了自身的版本定义符号。如果没有此引用,SUNW_1.2 接口将不包含任何即时符号引用,从而将归类为弱版本定义 (weak version definition)。

将符号定义迁移到标准接口中时,任何原始接口定义都必须继续表示相同符号列表。可以使用 pvs(1) 验证此要求。以下示例显示了软件发行版 X+1 中存在的 SUNW_1.2 接口的符号列表。


$ pvs -ds -N SUNW_1.2 libfoo.so.1

        SUNW_1.2:

                foo3;

        SUNW_1.1:

                foo2;

                foo1;

尽管新标准接口在软件发行版 X+2 中的引入更改了可用的接口版本定义,但每个原始接口提供的符号列表均保持不变。以下示例显示了接口 SUNW_1.2 仍然提供符号 foo1foo2foo3


$ pvs -ds -N SUNW_1.2 libfoo.so.1

        SUNW_1.2:

        STAND.0.1:

                foo3;

        SUNW_1.1:

                foo2;

        STAND.0.2:

                foo1;

应用程序可能只引用新的子版本中的一个。在这种情况下,尝试在上一个发行版中运行此应用程序将导致运行时版本控制错误。 请参见绑定到版本定义

通过直接引用现有的版本名称,可以提升应用程序的版本绑定。 请参见到其他版本定义的绑定。例如,如果一个应用程序只引用共享库 libfoo.so.1 的符号 foo1,则其版本引用为 STAND.0.2。要使此应用程序能够在以前的发行版上运行,可以使用版本控制指令 mapfile 将版本绑定提升到 SUNW_1.1


$ cat prog.c

extern void foo1();



main()

{

        foo1();

}

$ cc -o prog prog.c -L. -R. -lfoo

$ pvs -r prog

        libfoo.so.1 (STAND.0.2);



$ cat mapfile

libfoo.so - SUNW_1.1 $ADDVERS=SUNW_1.1;

$ cc -M mapfile -o prog prog.c -L. -R. -lfoo

$ pvs -r prog

        libfoo.so.1 (SUNW_1.1);

实际上,很少需要按这种方式提升版本绑定。因为很少会引入新的标准二进制接口,而且大多数应用程序都引用一个接口系列中的许多符号。