前言
在使用第三方库时,我们会发现第三方库会提供一组文件,他们的后缀一般是.so
(如libname.so
),.so.x
和.so.x.y.z
。本文讨论他们之间的关系。
共享库版本号
共享库一般会由于修复bug或增加接口等原因不断更新,有些更新是向下兼容的,有些则不是。一旦不向下兼容,那么当共享库更新后,依赖该库的程序将无法运行,需要重新编译。
为了避免上述情况,就要对共享库进行版本控制。根据更新内容的不同可以划分不同的版本号:
- 主版本号(Major Version Number):主版本号表示库的重大升级,即更新的内容会导致不再与旧版本兼容(如接口变更),需要用户做出代码上的修改来适应新版本(或者仍使用旧版的共享库)。
- 次版本号(Minor Version Number):次版本号表示库的增量升级,即更新的内容向下兼容,不会影响用户程序,但提供了额外的功能或改进。用户不需要做出代码更改仍可继续使用该库。
- 发布版本号(Release Version Number):发布版本号表示库的一些错误的修正、性能的改进等,接口不做变化,不添加新功能。向下兼容。
不同的版本号在文件命名上就可以体现。
对于一个名为aaa
的库,它的共享库文件名可能为:libaaa.so.x.y.z
,其中:
lib
:固定前缀aaa
:库名称.so
:共享库固定后缀.x
:主版本号.y
:次版本号.z
:发布版本号
例如libjsoncpp.so.1.7.4
就代表着jsoncpp
的共享库文件,版本号为1.7.4
共享库命名机制
然而若一个共享库改变了版本号并更新文件。那么对于使用旧版本共享库的用户程序来说,运行时就无法找到共享库文件了(因为名称已改变),还需要重新编译链接才可以。这就这大大增加了系统维护的复杂度和成本。
于是就诞生了SONAME命名机制,方便管理共享库的版本。
此机制设计了3类命名方式:
realname
- 形如
libname.so.x.y.z
,x,y,z
分别代表主版本号,次版本号和发布版本号。 - 一般拥有此名称的文件就是共享库的源文件
- 在库文件生成时使用下面命令可以指定realname:
gcc -shared -o $(realname) $(dependencies) $(flags)
soname
- 形如
libname.so.x
,x
代表主版本号 - 作用于用户程序运行时的加载阶段,动态链接器会根据用户程序编译时记录的soname查找对应的共享库文件
- 通常是
$(realname)
文件的软链接,在库安装或更新后由库的维护者或系统管理员通过包管理器更新软链接的指向,一般不由单个用户手动进行软链接。 - 在库文件生成时使用下面命令可以指定其soname:
gcc -shared -o $(realname) $(dependencies) $(flags) −Wl,−soname,$(soname) - 对于一个共享库文件,我们可以通过
readelf -d
命令查看其soname
linkname
- 形如
libname.so
,是没有任何版本编号的文件名 - 作用于用户程序编译阶段,链接器使用linkname来寻找对应的共享库(GCC中使用-l选项指定库,如
-laaa
,链接器就会去找libaaa.so
),然后将共享库的soname记录在用户程序的动态链接信息中。 - 通常是
$(realname)
文件或$(soname)
文件的软链接,在库安装或更新后由库的维护者或系统管理员通过包管理器更新软链接的指向,一般不由单个用户手动进行软链接。
总结
总的来说,对于Linux下的用户程序,soname命名机制主要参与了以下两个过程:
①链接阶段:链接器按照搜索路径优先级,根据linkname去找对应的.so
文件,如果找到了就会在生成的可执行文件中记录.so
文件指向的共享库文件的soname;如果没有找到就会去找静态库文件选择静态链接。
②加载阶段:程序运行时,动态链接器按照搜索路径优先级,根据可执行文件中记录的soname去找对应的*.so.x
文件,如果找到了就会加载其指向的共享库;没找到就报错。
这样的处理确保了应用程序在运行时能够找到合适的库版本,同时允许系统管理员在不影响已有应用程序的情况下更新库文件。
参考文章
1.Linux下动态链接库文件的realname、soname和linkname
2.Program Library HOWTO-Shared Libraries
3.Shared objects: sonames, real names, and link names
4.Linux 共享库的 soname 命名机制