Linux动态链接之六:共享库管理和版本控制

本文探讨了共享库的兼容性问题,包括ABI兼容性的挑战及维持方法、版本管理策略,以及SO-NAME机制和符号版本管理如何提高版本兼容性和管理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 共享库的兼容性

1.兼容更新:所有的更新只是在原有的共享库基础上添加一些内容,所有原有的接口都保持不变;
2.不兼容更新:共享库更新改变了原有的接口,使用该共享库原有的接口的程序可能不能运行或运行不正常。

谈到共享库和DLL等库文件的兼容性,则不得不谈到ABI(相比于API对应于源代码级别的接口管理,二进制层次的ABI对应的则是操作系统和底层机器码)对于不同的语言来说,主要包括一些诸如函数调用的堆栈结构、符号命名、参数规则、数据结构的内存分布等方面的规则。一般导致C语言的共享库ABI兼容性改变行为有4种:
1.导出函数的行为发生改变,即计算结果和之前产生的结果不一样;
2.导出函数被删除;
3.导出数据的结构发生变化,如共享库定义的结构体变量的结构发生改变:结构成员删除、顺序改变或其他引起结构体内存布局变化的行为(不过通常来讲,往结构体的尾部添加成员不会导致不兼容);
4.导出函数的接口发生变化,如函数返回值、参数被更改。

但是即便小心地按照上面所说的规则去维护共享库,不触碰导致共享库不兼容的逆鳞,也仅仅只能保证在绝大多数情况下,C语言共享库将保持ABI兼容,但是还有很多其他的因素都可能导致共享库的ABI兼容性丧失,比如不同版本的编译器、操作系统和硬件平台等(使用不同版本的编译器或系统库可能会导致结构体的成员对齐方式不一致,从而导致ABI变化)。这也是为什么尽量不要使用C++编写共享库或DLL的原因,因为C++标准根本就没有规定ABI层级的标准,各家编译器都有自己的解读方式,所以往往使用C编写共享库更简单,而用C++编写则涉及到COM原理。

2.共享库版本管理

为了解决这种共享库升级改动带来的代码管理困难,进行版本管理是好的解决方法。采用如下方式命名共享库
Libname.so.x.y.z
其中:
x主版本号,表示库的重大升级,不同主版本号之间不兼容,依赖于旧的主版本的程序部分需要改动相应部分,重新编译才能在新版的共享库中运行。
y次版本号,表示库的增量升级,即增加一些新的接口,保持原理的符号不变,故而呈现高的次版本向后兼容。一个依赖于旧的次版本号共享库的程序可以在新的次版本号共享库中运行。
z发布版本号,表示库的一些错误的修正、性能的改进,并不添加任何新的接口。

3.SO-NAME机制

对于新的系统来说,包括Solaris和Linux,普遍采用一种叫做SO-NAME的命名机制来记录共享库的依赖关系。比如一个共享库叫做libfoo.so.2.6.1,那么它的SO-NAME叫做libfoo.so.2。

在Linux系统中,系统会为每个共享库在它所在的目录下创建一个和SO-NAME同名的软连接指向该共享库,比如系统存在一个共享库“/lib/libfoo.so.2.6.1”,那么Linux中的共享库管理程序就会产生一个软连接“/lib/libfoo.so.2”指向它。在前面介绍.dynamic段中的便存在一个DT_SONAME表项,该表项便是一个软连接/lib/ld-linux.so.2 ,始终指向着当前系统最新的ld.so动态链接器。这样系统升级时,只需要修改系统的公用软连接文档,而不需要深入到每个可执行文件中修改具体的ld.so动态链接器版本名,思路和引出GOT字表.got.plt以保证代码段指令地址无关性一样(以及常见的C语言#define宏),属于一种替代牺牲的手法,但是确实方便很多。

将这种SO-NAME机制推广一点。前面说到过.dynamic段中还有DT_NEEDED表项用以指明当前文件所依赖的共享库对象。如果DT_NEEDED表项指明当前文件依赖C语言库为/lib/libc.so.2.6.1,当前系统升级了C语言库到/lib/libc.so.2.6.2,则还必须要保留/lib/libc.so.2.6.1,否则原程序无法运行,但保留旧版本则会导致空间浪费;或者当该文件移植到另一个平台上,若该平台只含有/lib/libc.so.2.7.1高次兼容性版本,那么该程序将无法运行。这两种情形,看起来都不太舒服。所以可以采用SO-NAME机制,将DT_NEEDED表项统一指向共享库的软链接文件上,结合着前面提到的共享库版本机制,则可以更有效地管理程序的版本依赖性。

当共享库进行升级时,如果只是变动了次版本号或者发布修正版本,那么完全可以把旧版本库从系统中覆盖掉,然后更改一下当前SO-NAME软链接;若是主版本更改,意味着重大更改,则需要保留旧的SO-NAME软链接(/libfoo.2.so => /libfoo.so.2.3.1),并添加一个指向新的主版本的SO-NAME软链接(/libfoo.3.so => /libfoo.so.3.1.1),这样子旧程序和新程序都不会受到影响。

至于各共享库SO-NAME软链接的更新任务是由Linux系统下的一个“Idconfig”程序完成的,每当系统安装或更新了一个共享库,则会运行一下“Idconfig”,该程序会便利所有的默认共享库目录,如/lib, /usr/lib等,然后更新对应共享库的所有的软链接,使它们始终指向最新版本的库;如果安装了新的共享库,则“Idconfig”会创建一个新的软链接。

4. 次版本号交会问题解决方案:共享库符号版本管理

SO-NAME机制虽然很好,但是显然会出现匹配模糊的情况,即如果原程序使用了较高次版本库,而运行环境中只有低次版本号的共享库,那么SO-NAME机制是检测不出来的,这有可能导致程序运行失败。此时有两张策略:1. 直接报错,阻止本次程序运行;2. 只提出警告warning,程序继续运行。因为很多程序虽然声明依赖于高版本共享库,但是其实只用到一些常见的函数符号,这些符号在低版本中也有实现,所以完全有可能运行通过的。

因为共享库的次版本号机制只能保证向后兼容,即高次版本号能兼容低次版本号,但低次版本号提供不了一些高次版本号实现的符号,不能保证前向兼容。但是正如前面所说,很多应用程序虽然声明依赖高次版本,但是其实因为使用的符号是两者交集,故而在低次版本下也能运行,这时没必要误杀。但不误杀的关键的是知道程序使用的符号是高次版本和低次版本的交集,所以显而易见的解决方案是为每个共享库配备一个符号集说明文档,指明哪些符号是本次次版本更新引进的,哪些符号是继承于旧版本的,这样便可以得到一些类似如下的标示文档。

VERS_1.1:
    add
    subl
VERS_1.2:
    multiple
    divide
VERS_1.3:
    exponent

Solaris操作系统便是根据此机制设计出了符号版本机制,其目标便是产生类似上面的符号版本脚本文件,链接器在链接时可以记录下它所使用到最高次版本号,比如只用到divide,意味着该程序虽然在/math.so.1.3.1的环境下,但是其实只要到/math.so.1.2.1的内容,那么链接器一旦得到这样的计算结果,将会被VERS_1.2记录到程序的可执行文件内。这样,在新的环境下,链接器只需要提取出可执行文件需要的最高次版本号和当前系统内存在的共享库的次版本号做对比,便可以确定该程序能否运行。可以说符号版本机制是对SO-NAME机制的有效补充,弥补SO-NAME机制的漏洞。

GCC对Solaris符号版本机制的扩展
Linux下的GCC编译器对Solaris的符号版本机制提供了一些更丰富的扩展。第一个扩展是,采用内嵌汇编手动指定某个符号属于的次版本

asm(".symver add,  add@VERS_1.1"); //手动将add符号添加到VERS_1.1版本子集中

int add(int a, int b)
{
    return a+b;
}

第二扩展是,允许多个版本的同一符号存在一个共享库中,即提供链接层面的函数重载

asm(".symver old_printf, print@VERS_1.1");
asm(".symver new_printf, print@VERS_1.2"); 

int old_printf() {...}

int new_printf() {...}

上面可以看到print符号在VERS_1.1和VERS_1.2都有存在,但是在VERS_1.2版本及以上,外部调用print则会被重定位到new_printf(),而在VERS_1.1版本中运行print则会被重定位到old_printf()

之所以单独弄出来这么个链接层重载机制,是因为弥补共享库管理机制的不足。前面说到过一旦更改了一个共享库中的符号的输入、输出等结构,该函数符号的变更将会导致整个共享库的不兼容,如果一次性要变更的函数符号很多,那么发布一个主版本号的重大更新无可厚非,但是如果一次只想起来改一个函数符号,但受限于共享库管理标准的限制,共享库已经不兼容了,只能发布为一个主版本号更新,但这显然有点”雷声大雨点小“,所以提供这种链接层面的重载机制,当共享库发生较小的不得不为之的不兼容更新时,可以采用这种弥补措施,既不影响新功能的使用,也不影响对旧程序的向后兼容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值