再探Linux动态链接 -- 关于动态库的基础知识(Dynamic Linking on Linux Revisited)
在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考。
编译时和运行时
纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台相关目标文件这一层次)和链接(Linking,指目标文件到最终形成可执行文件这一层次),这个总的过程可称为编译时;就动态链接而言,还存在一个运行时,即程序在被操作系统加载的过程中,系统将该程序需要的动态库加载至内存到程序开始运行的这一段过程。明确这两个过程在一般linux开发中的地位,以及了解每个“时”所遇要的文件、所运行的工具对理解linux动态链接很重要。
Linux共享库命名规范
回顾linux对动态库的命名规则,在一般情况下,均命名为libname.so.x.y.z。各版本号意义与兼容性标准如下表所示,这些也是程序员进行库程序设计是应该尽量遵守的设计规范。
版本号 |
意义 |
x |
主版本号,表示重大升级,不同主版本的库之间是不兼容的 |
y |
次版本号,表示增量升级,高次版本向后兼容低次版本号的库 |
z |
发布版本号,表示一些错误修正,性能改进,不同发布版本号之间完全兼容 |
这样的设计很容易带来一个问题,即动态库如何进行有效地升级,而不要求依赖它的应用程序重新编译?比如demo.out依赖libsmath.so.1.0.0(该名称在编译已指定,储存在ELF文件的DT_NEEDED段中),现对该库升级至1.1.0,但demo.out程序已经无法使用新版本的库。为解决这样的问题,Linux引入一种新的机制SO-NANE.
SO-NAME版本控制
由上可知,共享库的主版本和次版本决定该共享库的接口,则对于依赖某库的应用程序,最小限度上只需记录该库的主版本号,具体的版本即次版本和发布版本,可以交给动态链接器(一般为ld-linux.so)来选择,这样便可以在一定程度上解决上述的问题。
具体设计实现方面,对于libname.so.x.y.z,省略其y和z,只记录成libname.so.x即为该共享库的SO-NAME,在需要该库的应用程序的DT_NEEDED段中记录这个SO-NAME,同时在动态链接器的库搜索目录添加这个库的软链接,指定为libname.so.x(可通过设置/etc/ld.so.conf或设置环境变量LD_LIBRARY_PATH)。这样对于libname.so.x.y.z的次版本和发布版本的升级,只需更新该软链接就行,应用程序无须重新编译。
同时,SO-NAME也会被记录在该动态库内,供ldconfig工具自动更新软链接所用,这也就是每次安装新软件包后一般会运行该工具的原因。
以上这些均是对于运行时而言的,在编译时同样需要一些该库的信息,指定所需要的库,即gcc的-lname选项。该选项主要是传递给链接器(ld,而非运行时所用的ld-linux.so),链接器会在库搜索目录(可通过设置-rpath参数或LIBRARY_PATH环境变量)搜索libname.so文件,所以为了能使用该库进行开发,需要在以上目录设定指向libname.so.x或libname.so.x.y.z的软链接,名称为libname.so.这项工作一般需手动完成。
示例
(下述例子中,smath.h、libsmath.so为编译时需要文件;libsmath.so.1、libsmath.so.1.0.0为运行时所需文件)
假设需要自定义编写一套数学库,其中有Add和Sub等函数
1 //smath.h 2 3 int Add(int,int); 4 5 int Sub(int,int);
1 //smath.c 2 3 int Add(int a,int b) 4 5 { 6 7 return a+b; 8 9 } 10 11 int Sub(int a,int b) 12 13 { 14 15 return a-b; 16 17 }
①编译
按照gcc操作手册
执行
gcc -shared -fPIC -Wl, -soname,libsmath.so.1 -o libmath.so.1.0.0 smath.c
(若不指定-soname,则该库没有SO-NAME,DT_NEEDED中记录为空)
②安装
复制头文件
cp smath.h ~/include
安装至自定义目录(非/lib、/usr/lib等),假设安装至~/lib下
cp libsmath.so.1.0.0 ~/lib cd ~/lib ln -s libsmath.so.1.0.0 libsmath.so.1 ln -s libsmath.so.1 libsmath.so
③使用
保证smath.h 、libsmath.so在相关搜索目录下(编译链接用)
export C_INCLUDE_PATH=$HOME/include
export LIBRARY_PATH=$HOME/include
(也可设置gcc相关参数设置)
则现在可使用gcc编译如下代码
main.c #include <stdio.h> #include <smath.h> int main() { int a=3,b=5; printf(“%d + %d = %d\n”,a,b,Add(a,b)); return 0; }
编译
gcc -omain.out -lsmath main.c
④运行
保证libsmath.so.1在相关搜索目录下(运行链接用)
export LD_LIBRARY_PATH=$HOME/lib
(也可设置/etc/ld.so.conf目录)
即可运行
参考
《程序员的自我修养——链接、装载与库》 第8章
Working with libraries and the linker -- http://bottomupcs.sourceforge.net/csbu/x4012.htm