前言:
而今我们对“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静态库和共享库