Linux下静态库与动态库
2015-10-05 12:50
330 查看
Linux下静态库_库的基本概念;如何生成静态库动态库;nm查看库中包含那些函数、ar生成静态库,查看库中包含那些.o文件、ldd查看程序依赖的.so文件;gcc/g++与库相关的参数-L,-l,-fPIC,-shared;静态库链接时搜索过程;动态库链接时,加载时搜索的过程;动态库找不到的问题;库的依赖问题;动态库升级问题与步骤
一、基本概念
1.1、什么是库在windows平台和linux平台下都大量存在着库。
本质上来说库是一种可执行的二进制代码(但不可以独立执行),可以被操作系统载入内存执行。
由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。
本文仅限于介绍linux下的库。
1.2、
库的种类
linux
下的库有两种:静态库和共享库(动态库)。
二者的不同点在于代码被载入的时刻不同:
静态库的代码在编译过程中已经被载入可执行程序,因此生成的可执行程序体积较大。静态用.a为后缀,
例如:libhello.a
共享库(动态库)的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此生成的可执行程序代码体积较小。
动态通常用.so为后缀,例如:libhello.so
共享库(动态库)的好处是::不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如:libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。
ln-slibhello.so.1.0libhello.so.1
ln-slibhello.so.1libhello.so
1.3、静态库,动态库文件在linux下是如何生成的:
以下面的代码为例,生成上面用到的hello库:
/*hello.c*/
#include"hello.h"
voidsayhello()
{
printf("hello,world");
}首先用gcc编绎该文件,在编绎时可以使用任何合法的编绎参数,例如-g加入调试代码等:
gcc-chello.c-ohello.o1、生成静态库生成静态库使用ar工具,其实ar是archive的意思
arcqslibhello.ahello.o
2、生成动态库用gcc来完成,由于可能存在多个版本,因此通常指定版本号:[code]gcc-shared-olibhello.so.1.0hello.o
1.4、库文件是如何命名的,有没有什么规范:
在linux下,库文件一般放在/usr/lib和/lib下,
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称;
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号
1.5、可执行程序在执行的时候如何定位共享库(动态库)文件:
当系统加载可执行代码(即库文件)的时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器(dynamiclinker/loader)
对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的DT_RPATH段-->环境变量LD_LIBRARY_PATH—->/etc/ld.so.cache文件列表-->/lib/,/usr/lib目录找到库文件后将其载入内存
如:exportLD_LIBRARY_PATH=’pwd’
将当前文件目录添加为共享目录。
1.6、使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库:
ldd命令可以查看一个可执行程序依赖的共享库,
例如#ldd/bin/lnlibc.so.6
=>/lib/libc.so.6(0×40021000)/lib/ld-linux.so.2
=>/lib/ld-linux.so.2(0×40000000)
可以看到ln命令依赖于libc库和ld-linux库
1.7、使用nm工具,查看静态库和动态库中有那些函数名;
(T类表示函数是当前库中定义的,U类表示函数是被调用的,在其它库中定义的,W类是当前库中定义,被其它库中的函数覆盖)。:
有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的涉及到的所有符号,这里的库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种::
T类:是在库中定义的函数,用T表示,这是最常见的;
U类:是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
W类:是所谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
例如,假设开发者希望知道上文提到的hello库中是否引用了printf():
nmlibhello.so|grepprintf发现printf是U类符号,说明printf被引用,但是并没有在库中定义。
由此可以推断,要正常使用hello库,必须有其它库支持,使用ldd工具查看hello依赖于哪些库:
lddlibhello.so
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2(0x40000000)
从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以goon
1.8、使用ar工具,可以生成静态库,同时可以查看静态库中包含那些.o文件,即有那些源文件构成。
可以使用ar-tlibname.a来查看一个静态库由那些.o文件构成。
可以使用arqlibname.axxx1.oxxx2.oxxx3.o...xxxn.o生成静态库
1.9、如何查看动态库和静态库是32位,还是64位下的库:
如果是动态库,可以使用file*.so;
如果是静态哭,可以使用objdump-x*.a
[/code]
Linux下进行程序设计时,关于库的使用:
一、gcc/g++命令中关于库的参数:
-shared:该选项指定生成动态连接库;
-fPIC:表示编译为位置独立(地址无关)的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时,是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L:指定链接库的路径,-L.表示要连接的库在当前目录中
-ltest:指定链接库的名称为test,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
-Wl,-rpath:记录以来so文件的路径信息。
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用/sbin/ldconfig来达到同样的目的,
不过如果没有root权限,那么只能采用修改LD_LIBRARY_PATH环境变量的方法了。
调用动态库的时候,有几个问题会经常碰到:
1、有时,明明已经将库的头文件所在目录通过“-I”include进来了,库所在文件通过“-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
二、静态库链接时搜索路径的顺序:
1.ld会去找gcc/g++命令中的参数-L;
2.再找gcc的环境变量LIBRARY_PATH,它指定程序静态链接库文件搜索路径;
exportLIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib
3.再找默认库目录/lib/usr/lib/usr/local/lib,这是当初compilegcc时写在程序内的。
三、动态链接时、执行时搜索路径顺序:
1.编译目标代码时指定的动态库搜索路径;
2.环境变量LD_LIBRARY_PATH指定动态库搜索路径,它指定程序动态链接库文件搜索路径;
exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。
四、静态库和动态链接库同时存在时,gcc/g++默认链接的是动态库:当一个库同时存在静态库和动态库时,比如libmysqlclient.a和libmysqlclient.so同时存在时:[/code]
在Linux下,动态库和静态库同事存在时,gcc/g++的链接程序,默认链接的动态库。
可以使用下面的方法,给连接器ld传递参数,看是否链接动态库还是静态库。
-Wl,-Bstatic-llibname//指定让gcc/g++链接静态库
使用:
gcc/g++test.c-otest-Wl,-Bstatic-llibname-Wl,-Bdynamic-lm-lc
-Wl,-Bdynamic-llibname//指定让gcc/g++链接动态库
使用:
gcc/g++test.c-otest-Wl,-Bdynamic-llibname
如果要完全静态加在,使用-static参数,即将所有的库以静态的方式链入可执行程序,这样生成的可执行程序,不再依赖任何库,同事出现的问题是,这样编译出来的程序非常大,占用空间。
如果不适用-Wl,-Bdynamic-lm-c会有如下错误:
[chenbaihu@build17lib]$ls
libtest.alibtest.sott.cctest.cctest.htest.o
[chenbaihu@build17lib]$g++-Wall-gt.cc-ot-L./-Wl,-Bstatic-ltest-Wl,-Bdynamic-lm-lc
[chenbaihu@build17lib]$g++-Wall-gt.cc-ot-L./-Wl,-Bstatic-ltest
/usr/bin/ld:cannotfind-lm
collect2:ld返回1
参考:
http://lists.gnu.org/archive/html/help-gnu-utils/2004-03/msg00009.html
五、有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
六、库的依赖问题:
比如我们有一个基础库libbase.a,还有一个依赖libbase.a编译的库,叫做libchild.a;在我们编译程序时,一定要先-lchild再-lbase。如果使用-lbase-lchild,在编译时将出现一些函数undefined,而这些函数实际上已经在base中已经定义;
为什么会有库的依赖问题?
一、静态库解析符号引用:
链接器ld是如何使用静态库来解析引用的。在符号解析阶段,链接器从左至右,依次扫描可重定位目标文件(*.o)和静态库(*.a)。
在这个过程中,链接器将维持三个集合:
集合E:可重定位目标文件(*.o文件)的集合。
集合U:未解析(未定义)的符号集,即符号表中UNDEF的符号。
集合D:已定义的符号集。
初始情况下,E、U、D均为空。
1、对于每个输入文件f,如果是目标文件(.o),则将f加入E,并用f中的符号表修改U、D(在文件f中定义实现的符号是D,在f中引用的符号是U),然后继续下个文件。
2、如果f是一个静态库(.a),那么链接器将尝试匹配U中未解析符号与静态库成员(静态库的成员就是.o文件)定义的符号。如果静态库中某个成员m(某个.o文件)定义了一个符号来解析U中引用,那么将m加入E中,
同时使用m的符号表,来更新U、D。对静态库中所有成员目标文件反复进行该过程,直至U和D不再发生变化。此时,静态库f中任何不包含在E中的成员目标文件都将丢弃,链接器将继续下一个文件。
3、当所有输入文件完成后,如果U非空,链接器则会报错,否则合并和重定位E中目标文件,构建出可执行文件。
到这里,为什么会有库的依赖问题已经得到解答:
因为libchild.a依赖于libbase.a,但是libbase.a在libchild.a的左边,导致libbase.a中的目标文件(*.o)根本就没有被加载到E中,所以解决方法就是交换两者的顺序。当然也可以使用-lbase-lchild-lbase的方法。
参考文章:http://pananq.com/index.php/page/3/
七、动态库升级问题:
在动态链接库升级时,
不能使用cpnewlib.sooldlib.so,这样有可能会使程序core掉;
而应该使用:
rmoldlib.so然后cpnewlib.sooldlib.so
或者
mvoldlib.sooldlib.so_bak然后cpnewlib.sooldlib.so
为什么不能用cpnewlib.sooldlib.so?
在替换so文件时,如果在不停程序的情况下,直接用cpnew.soold.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃。
解决方法:
解决的办法是采用“rm+cp”或“mv+cp”来替代直接“cp”的操作方法。
linux系统的动态库有两种使用方法:运行时动态链接库,动态加载库并在程序控制之下使用。
1、为什么在不停程序的情况下,直接用cp命令替换程序使用的so文件,会使程序崩溃?
很多同学在工作中遇到过这样一个问题,在替换so文件时,如果在不停程序的情况下,直接用cpnew.soold.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃,退出。
这与cp命令的实现有关,cp并不改变目标文件的inode,cp的目标文件会继承被覆盖文件的属性而非源文件。实际上它是这样实现的:
stracecplibnew.solibold.so2>&1|grepopen.*lib.*.so
open("libnew.so",O_RDONLY|O_LARGEFILE)=3
open("libold.so",O_WRONLY|O_TRUNC|O_LARGEFILE)=4
在cp使用“O_WRONLY|O_TRUNC”打开目标文件时,原so文件的镜像被意外的破坏了。这样动态链接器ld.so不能访问到so文件中的函数入口。从而导致Segmentationfault,程序崩溃。ld.so加载so文件及“再定位”的机制比较复杂。
2、怎样在不停止程序的情况下替换so文件,并且保证程序不会崩溃?
答案是采用“rm+cp”或“mv+cp”来替代直接“cp”的操作方法。
在用新的so文件libnew.so替换旧的so文件libold.so时,如果采用如下方法:
rmlibold.so//如果内核正在使用libold.so,那么inode节点不会立刻别删除掉。
cplibnew.solibold.so
采用这种方法,目标文件libold.so的inode其实已经改变了,原来的libold.so文件虽然不能用"ls"查看到,但其inode并没有被真正删除,直到内核释放对它的引用。
(即:rmlibold.so,此时,如果ld.so正在加在libold.so,内核就在引用libold.so的inode节点,rmlibold.so的inode并没有被真正删除,当ld.so对libold.so的引用结束,inode才会真正删除。这样程序就不会崩溃,因为它还在使用旧的libold.so,当下次再使用libold.so时,已经被替换,就会使用新的libold.so)
同理,mv只是改变了文件名,其inode不变,新文件使用了新的inode。这样动态链接器ld.so仍然使用原来文件的inode访问旧的so文件。因而程序依然能正常运行。
(即:mvlibold.so***后,如果程序使用动态库,还是使用旧的inode节点,当下次再使用libold.so时,就会使用新的libold.so)
到这里,为什么直接使用“cpnew_exec_fileold_exec_file”这样的命令时,系统会禁止这样的操作,并且给出这样的提示“cp:cannotcreateregularfile`old':Textfilebusy”。
这时,我们采用的办法仍然是用“rm+cp”或者“mv+cp”来替代直接“cp”,这跟以上提到的so文件的替换有同样的道理。
但是,为什么系统会阻止cp覆盖可执行程序,而不阻止覆盖so文件呢?
这是因为Linux有个DemandPaging机制,所谓“DemandPaging”,简单的说,就是系统为了节约物理内存开销,并不会程序运行时就将所有页(page)都加载到内存中,而只有在系统有访问需求时才将其加载。“DemandPaging”要求正在运行中的程序镜像(注意,并非文件本身)不被意外修改,因此内核在启动程序后会锁定这个程序镜像的inode。
对于so文件,它是靠ld.so加载的,而ld.so毕竟也是用户态程序,没有权利去锁定inode,也不应与内核的文件系统底层实现耦合。
相关文章推荐
- linux awk
- linux常用命令总结
- centos在设置时区
- centos 安装smplayer
- linux下常用命令以及文件
- linux下安装JDK支持java程序运行
- CentOS6.5安装python2.7.9
- Linux文件系统管理
- linux md5sum与sha1sum
- 在ubuntu下对stm32开发板的编译、烧写、调试
- Centos后台运行jar
- Centos后台运行jar
- Linux进程端口指令
- embed linux development - nfs
- 字符设备驱动、平台设备驱动、设备驱动模型、sysfs的比较和关联
- 编译arm-linux-gdb和远程调试arm板程序
- VMWare下虚拟机NAT共享方式上网的配置说明
- 解决linux中mysql乱码问题
- linux下mysql配置文件my.cnf详解
- Linux下的socket编程实践(三)端口复用和 P2P多进程服务器