龙空技术网

以linux运维开发的视角看共享库(动态库)和静态库

郑大米刚 317

前言:

而今我们对“linux静态库和共享库”可能比较关怀,姐妹们都需要剖析一些“linux静态库和共享库”的相关文章。那么小编在网络上搜集了一些有关“linux静态库和共享库””的相关资讯,希望你们能喜欢,我们快快来了解一下吧!

下面以一名运维开发的角度解释静态库,动态库的问题。

库就是存储东西的地方。

存储东西的目的必然是为了别人能来取用,也就是说库中的东西有被使用的价值,如果存储的东西没有利用的价值,这就不叫库了(也许是叫垃圾站)。

把可供复用的代码集中放在某个地方以供别人调用,这就是代码库。

最早的库就是源文件,比如别人觉得你写的代码很好,干脆拿来直接用吧,因此就把你的源文件代码复制到自己的工程里,比如把人家文件包含进来,或者直接粘代码。

再近一步,各种原因之下,你不方便把源代码直接给别人用,那就直接编译为目标文件,别人在链接的时候,把你的目标文件和他们的程序链接成一体,这也实现了代码共享。比如下面代码:

库按照存在的副本数量可被分为两大类,静态库和动态库,动态库也被称为共享库。

静态库表示被完整复制到目标程序中,它存在于每个使用它的目标程序中,即有多少个程序包含该库,该库就有多少个副本。也就是说,同时浪费了磁盘和内存空间。

共享库强调的是共享的概念,库在磁盘上只存在一份,无论有多少个程序使用该库,程序运行后,该库在内存中只存在一份副本,节省了空间。

按理来说,库就是用来被共享使用的,既然能被共享就说明这是共通的东西,因此只需要一份就够了,所以共享库更受欢迎。

静态库

静态库就是把目标文件打成一个包,别人若想用这个包中的指令,在链接时把这个包一同链接即可。

打包的工具是ar,ar专用于程序开发中的打包。注意啦,此工具专门把文件打成一个包,也叫归档文件,并不负责检查被打包的文件的类型,也就是说,源文件可以被打包,目标文件也可以被打包,就如同windows下的打包软件winrar一样,只是用来打包的,不管里面的文件类型是什么。

我们下面要打包静态库,如果没有安装glibc-static,会报错:

为避免链接器ld提示找不到lc,即找不到libc.so(本机上存在的一个库文件,不是符号链接)

需要执行yum install glibc-static

下面是打包静态库的过程。

这里要注意的是,上图中浅蓝色背景的部分提示未定义的引用,即找不到符号sharedObjTest,

这是由于链接的顺序造成的,链接的目标文件的顺序从左到右最好是调用在前,实现在后。

(gcc只是一个工具集,链接的时候会调用ld执行链接。)

linux共享库

在平时的编程工作中,除了C库,还会用到大量的库文件,其中绝大部分都是以动态库的方式来提供服务的。

一般情况下,库文件的开发者会同时提供动态库和静态库两个版本,它们都有各自的优缺点。静态库在链接阶段,会被直接链接进最终的二进制文件中,因此最终生成的二进制文件体积会比较大,但是可以不再依赖于库文件。而动态库并不是被链接到文件中的,只是保存了依赖关系,因此最终生成的二进制文件体积较小,但是在运行阶段需要加载动态库。

编写共享库代码:

使用gcc将代码编译为共享库,

-fPIC指定共享库中的符号与位置无关,

表示编译为位置独立的代码,Position Indepent Code,不用此选项的话编译后的代码是位置相关的共享库标配参数,少了会报错并提示加上-fPIC。

输出的共享库文件名须“以lib开头,以.so结尾”,这是标配,否则其它程序编译链接的时候会找不到。

主程序代码

编译主程序,链接共享库sharedTest。

-l 指定要链接的共享库文件名是什么,注意,把共享库文件名开头lib和结尾的.so去掉,系统会自动添加这个标配,加了也会报错。

-L 指定到哪个目录下去找共享库文件

执行main

提示找不到libsharedTest.so。原因是此时是在运行阶段(上面gcc的-L是指定gcc编译时到哪里去找libsharedTest.so)找不到共享库文件libsharedTest.so。很明显,main程序会调用此共享库,可以通过ldd命令查看main的依赖库。

共享库是由操作系统负责加载的,那操作系统去哪里找共享库文件呢?

默认会去/lib和/usr/lib下。系统中有配置共享库路径的命令是ldconfig,看其帮助就可以了。

上面帮助说,操作系统会去三个地方找共享库文件,

1 ldconfig命令后跟的目录名,如 ldconfig xx目录

2 /etc/ld.so.conf中所指定的配置文件中的目录

3 /lib和/usr/lib目录

注意,共享库是程序在运行时根据符号表来动态查找的,因此为了在执行时查找的更快,系统会为以上三个目录中最新的共享库生成了cache。ldconfig命令就是用来生成cache,位置是在/etc/ld.so.cache。如果不执行ldconfig的话,这个cache文件是不存在的。如下所示。

查看系统中所有共享库文件(即查看ldconfig所生成的cache)的方式是ldconfig -p, -p即print。

下面开始测试。

测试第一种方案,把当前目录也加入到cache中,由于此方案会往系统目录生成cache,因此要root权限:

执行没错。

在测试第二种之前,把当前目录从cache中去掉,这样便于判断程序执行成功不是第一种的效果。清掉cache很容易,其实就是重建cache。执行ldconfig就行了,它会去/etc/ld.so.conf中读取。这恰恰是我们要测试的第二种方式。

重建cache后,退到work账号下重新执行main,提示找不到共享库。然后用ldconfig -p来查看是否有libsharedTest库,果然没有,符合预期。

下面测试第二种方式。见/etc/ld.so.conf,

包含了目录ld.so.conf.d中所有以.conf结尾的文件,这说明我们只需要在该目录下生成.conf文件,并把我们的路径写入该文件即可。

我们在该目录下生成test.conf,内容是libsharedTest.so所在的目录。

下面执行ldconfig,系统会把test.conf中指定目录下的文件生成cache。然后我们用ldconfig -p来查看。

这说明已经加载到cache中了。再执行main,正常,结果如下。

接着测试第三种方式,把共享库文件放到/lib或/usr/lib下,继续之前,依然把libsharedTest.so从cache中去掉,只要我们把test.conf从ld.so.conf.d目录中去掉即可,如删除,移动,或者使文件名不以.conf结尾。

再次检查,确实没有该库文件了。

把libsharedTest.so放到/usr/lib下吧,就测试这一个。

重建cache后已经看到了libsharedTest.so,再执行main就没问题了,不测试了。

以上三种方式相对来说麻烦一些,毕竟都是在修改cache。其实还有第四种方式,用LD_LIBRARY_PATH环境变量,也就是说,此方式并不会把环境变量所指向的目录加到cache中。

既然它是环境变量,就只对当前shell及其子进程生效。

如上所示,利用环境变量并不会把环境变量所指向的目录加到cache中。

以上只是从运维方面解决运行时链接共享库的问题,下面从代码层面看看。

我们可以在代码中手动加载共享库,这可以用dlopen函数,参数filename便是共享库文件名,这得写以lib开头,以.so结尾的完整名。

。。。略

dlopen用来打开一个.so共享库文件,它的flag有RTLD_LAZY、RTLD_NOW等。这个flag的作用是描述所引用的共享库若也引用了其它共享库中的符号,该如何解析这种二次引用。

RTLD_NOW表示在dlopen返回之前就把库文件所依赖其它库文件中的符号解析,也就是地址重定位。

RTLD_LAZY表示在执行过程中,程序所依赖的共享库中用到其它共享库中哪个符号,就去现场解析哪个,懒惰。

我们想使用共享库中哪个符号呢?比如想调用其中的某个函数。

dlsym用来返回共享库中的一个符号的地址,“相当于”导入了符号,并不完全是,只是获取了该符号的内存地址而已,若为函数地址,接下来可以调用。

dlclose用来关闭一个共享库,参数是dlopen返回的共享库句柄,千万不要是用dlsym导入的符号。

代码如下:

编译:

执行结果符合预期。

注意,前面的主函数代码main_extern.c中是用extern声明了所调用的函数是外部的,即在我们共享库文件libsharedTest.so中,因此在编译时要用-l指定共享库名,以满足“链接”的需求,注意,这里是链接,并不是运行阶段。而在main_dlopen中,我们是在“运行”阶段用dlopen手动以路径的形式打开它,也就是说共享库相当于是我们程序运行时内部的一个参数,因此不需要编译链接时以-l去指定该共享库名了。

注意,这里依然用-ldl指定要使用共享库libdl.so,这个libdl.so便是dlopen等函数所在的目标文件中,默认链接时ld并不会自动找这个库,因此必须在编译链接时显式加上。

好了,这就是一位运维开发对库的理解。

标签: #linux静态库和共享库