明天我们主要来谈谈Linux系统下基于动态库(.so)和静态(.a)的程序这些隐情。在这之前,我们须要了解一下源代码到可执行程序之间究竟发生了哪些神奇而美妙的事情。
在Linux操作系统中,普遍使用ELF格式作为可执行程序或则程序生成过程中的中间格式。ELF(ExecutableandLinkingFormat,可执行联接格式)是UNIX系统实验室(USL)作为应用程序二补码插口(ApplicationBinaryInterface,ABI)而开发和发布的。工具插口标准委员会(TIS)选择了正在发展中的ELF标准作为工作在32位Intel体系上不同操作系统之间可移植的二补码文件格式。本文不对ELF文件格式及其组成做太多解释,以免淡忘本文的主题,你们只要晓得如此个概念就行。之后再解读Linux中的ELF格式。源代码到可执行程序的转换时须要经历如右图所示的过程:
GCC是Linux下主要的程序生成工具,它不仅编译器、汇编器、连接器外,还包括一些辅助工具。在下边的剖析过程中我会教你们那些工具的基本使用方式,Linux的强悍之处在于,对于不太懂的命令或函数,有一个很强悍的“男人”时刻standbyyourside,有哪些不会的就去命令行终端输入:man[命令名或函数名],之后阿拉神灯都会显灵了。
对于最后编译下来的可执行程序,当我们执行它的时侯,操作系统又是怎样反应的呢?我们先从宏观上来个总体掌握,如图2所示:
作为UNIX操作系统的一种,Linux的操作系统提供了一系列的插口,这种插口被称为系统调用(SystemCall)。在UNIX的理念中,系统调用“提供的是机制,而不是策略“。C语言的库函数通过调用系统调用来实现,库函数对下层提供了C语言库文件的插口。在应用程序层,通过调用C语言库函数和系统调用来实现功能。通常来说,应用程序大多使用C语言库函数实现其功能,较少使用系统调用。
这么最后的可执行文件究竟是哪些样子呢?上面早已说过,这儿我们不深入剖析ELF文件的格式,只是给出它的一个结布光和一些简单的说明,以便捷你们理解。
ELF文件格式包括三种主要的类型:可执行文件、可重定向文件、共享库。
1.可执行文件(应用程序)
可执行文件包含了代码和数据,是可以直接运行的程序。
2.可重定向文件(*.o)
可重定向文件又称为目标文件,它包含了代码和数据(这种数据是和其他重定位文件和共享的object文件一起联接时使用的)。
*.o文件参与程序的联接(创建一个程序)和程序的执行(运行一个程序),它提供了一个便捷有效的方式来用并行的视角看待文件的内容,这种*.o文件的活动可以反映出不同的须要。
Linux下,我们可以用gcc-c编译源文件时可将其编译成*.o格式。
3.共享文件(*.so)
称作为动态库文件,它包含了代码和数据(这种数据是在联接时侯被联接器ld和运行时动态联接器使用的)。动态联接器可能称为ld.so.1linux查看库版本,libc.so.1或则ld-linux.so.1。我的CentOS6.0系统中该文件为:/lib/ld-2.12.so
一个ELF文件从联接器(Linker)的角度看,是一些节的集合;从程序加载器(Loader)的角度看,它是一些段(Segments)的集合。ELF格式的程序和共享库具有相同的结构,只是段的集合和节的集合上有些不同。
这么究竟哪些是库呢?
库从本质上来说是一种可执行代码的二补码格式,可以被载入显存中执行。库分静态库和动态库两种。
静态库:这泛型的名子通常是libxxx.a,xxx为库的名子。借助静态函数库编译成的文件比较大,由于整个函数库的所有数据就会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不须要外部的函数库支持,由于所有使用的函数都早已被编译进去了。其实这也会成为他的缺点,由于假如静态函数库改变了,这么你的程序必须重新编译。
动态库:这泛型的名子通常是libxxx.M.N.so,同样的xxx为库的名子,M是库的主版本号,N是库的副版本号。其实也可以不要版本号,但名子必须有。相对于静态函数库,动态函数库在编译的时侯并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因而动态函数库所形成的可执行文件比较小。因为函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较便捷。linux系统有几个重要的目录储存相应的函数库,如/lib/usr/lib。
当要使用静态的程序库时,联接器会找出程序所需的函数,之后将它们拷贝到执行文件,因为这些拷贝是完整的,所以一旦联接成功,静态程序库也就不再须要了。但是,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。因为动态库节约空间,linux下进行联接的缺省操作是首先联接动态库,也就是说,假如同时存在静态和动态库,不非常指定的话,将与动态库相联接。
OK,有了这种知识,接出来你们就可以弄明白我所做的事情是干哪些了。都说事例是最好老师,我们就从反例入手。
1、静态链接库
我们先制做自己的静态链接库,之后再使用它。制做静态链接库的过程中要用到gcc和ar命令。
打算两个库的源码文件st1.c和st2.c,用它们来制做库libmytest.a,如下:
静态库文件libmytest.a早已生成,用file命令查看其属性,发觉它确实是归档压缩文件。用ar-tlibmytest.a可以查看一个静态库包含了这些obj文件:
接出来我们就写个测试程序来调用库libmytest.a中所提供的两个插口print1()和print2()。
看见没,静态库的编撰和调用就那么简单,学会了吧。这儿gcc的参数-L是告诉编译器库文件的路径是当前目录,-l是告诉编译器要使用的库的名子叫mytest。
2、动态库
静态库*.a文件的存在主要是为了支持较老的a.out格式的可执行文件而存在的。目前用的最多的要数动态库了。
动态库的后缀为*.so。在Linux发行版中大多数的动态库基本都坐落/usr/lib和/lib目录下。在开发和使用我们自己动态库之前,请允许我先落里罗嗦的跟你们絮叨絮叨Linux下和动态库相关的事儿吧。
有时侯当我们的应用程序难以运行时,它会提示我们说它找不到哪些样的库,或则那个库的版本又不合它食欲了等等之类的话。这么应用程序它是如何晓得须要什么库的呢?我们上面已几个学了个很棒的命令ldd,用就是拿来查看一个文件究竟依赖了这些so库文件。
Linux系统中动态链接库的配置文件通常在/etc/ld.so.conf文件内,它上面储存的内容是可以被Linux共享的动态联库所在的目录的名子。我的系统中,该文件的内容如下:
之后/etc/ld.so.conf.d/目录下储存了好多*.conf文件,如下:
其中每位conf文件代表了一种应用的库配置内容,以mysql为例:
假若您是和我一样装的CentOS6.0的系统,这么悉心的读者可能会发觉,在/etc目录下还存在一个名叫ld.so.cache的文件。从名子来看,我们晓得它肯定是动态链接库的哪些缓存文件。
对,您说的一点没错。为了促使动态链接库可以被系统使用,当我们更改了/etc/ld.so.conf或/etc/ld.so.conf.d/目录下的任何文件,或则往这些目录下拷贝了新的动态链接库文件时,都须要运行一个很重要的命令:ldconfig,该命令坐落/sbin目录下,主要的用途就是负责搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf里所列的目录下搜索可用的动态链接库文件,之后创建处动态加载程序/lib/ld-linux.so.2所须要的联接和(默认)缓存文件/etc/ld.so.cache(此文件里保存着早已排好序的动态链接库名子列表)。
也就是说:当用户在某个目录下边创建或拷贝了一个动态链接库,倘若使其被系统共享,可以执行一下“ldconfig目录名“这个命令。此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享上去,即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库。请注意:假如此目录不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目录上面,则再度单独运行ldconfig时,此目录下的动态链接库可能不被系统共享了。单独运行ldconfig时,它只会搜索/lib、/usr/lib以及在/etc/ld.so.conf文件里所列的目录,用它们来重建/etc/ld.so.cache。
因而,等会儿我们自己开发的共享库就可以将其拷贝到/lib、/etc/lib目录里,又或则更改/etc/ld.so.conf文件将我们自己的库路径添加到该文件中,再执行ldconfig命令。
非了老半天工夫,总算把基础打好了,猴急的您已经按耐不住激情的想动手尝试了吧!哈哈。。。OK,说整咱就开整,接出来我就率领你们一步一步来开发自己的动态库,之后教你们怎样去使用它。
我们有一个头文件my_so_test.h和三个源文件test_a.c、test_b.c和test_c.c,将她们制做成一个名为libtest.so的动态链接库文件:
OK,万事俱备,只欠东风。怎样将这种文件编译成一个我们所须要的so文件呢?可以分两步来完成,也可以一步到位:
方式一:
1、先生成目标.o文件:
2、再生成so文件:
-shared该选项指定生成动态联接库(让联接器生成T类型的导入符号表,有时侯也生成弱联接W类型的导入符号),不用该标志外部程序没法联接。相当于一个可执行文件。
-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的形式来满足不同进程的须要,而不能达到真正代码段共享的目的。
方式二:一步到位。
至此,我们制做的动态库文件libtest.so即使大功告成了。
接出来,就是怎样使用这个动态库了。动态链接库的使用有两种方式:既可以在运行时对其进行动态链接linux查看库版本,又可以动态加载在程序中是用它们。接出来,我就这两种方式分别对其介绍。
+++动态库的使用+++
用法一:动态链接。
使用“-ltest”标记来告诉GCC驱动程序在联接阶段引用共享函数库libtest.so。“-L.”标记告诉GCC函数库可能坐落当前目录。否则GNU联接器会查找标准系统函数目录。
这儿我们注意,ldd的输出它说我们的libtest.so它没找到。还记得我在上面动态链接库一节刚开始时的那堆絮叨么,如今你应当很明白了为何了吧。由于我们的libtest.so既不在/etc/ld.so.cache里,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一个目录中。如何办?还用我告诉你?管你用啥办法,总之我用的ldconfigpwd搞定的:
执行结果如下:
偶忍不住又要罗嗦一句了,相信俺,我的絮叨对你们是有用处。我为何用这些技巧呢?由于我是在给你们演示动态库的用法,完了以后我就把libtest.so给删了,之后再构建ld.so.cache,对我的系统不会任何影响。如果我是开发一款软件,或则给自己的系统DIY一个特别有用的功能模块linux版qq,这么我更倾向于将libtest.so拷贝到/lib、/usr/lib目录下,或则我还有可能在/usr/local/lib/目录下新建一文件夹xxx,将so库拷贝到那里去,并在/etc/ld.so.conf.d/目录下新建一文件mytest.conf,内容只有一行“/usr/local/lib/xxx/libtest.so”,再执行ldconfig。假如你之前还是不明白如何解决那种“notfound”的问题,这么如今总该明白了吧。
方式二:动态加载。
动态加载是十分灵活的,它依赖于一套Linux提供的标准API来完成。在源程序里,你可以很自如的运用API来加载、使用、释放so库资源。以下函数在代码中使用须要包含头文件:dlfcn.h
dlsym(void*handle,char*symbol)
filename:假如名子不以“/”开头,则非绝对路径名,将按下述先后次序查找该文件。
(1)用户环境变量中的LD_LIBRARY值;
(2)动态链接缓冲文件/etc/ld.so.cache
(3)目录/lib,/usr/lib
flag表示在哪些时侯解决未定义的符号(调用)。取值有两个:
1)RTLD_LAZY:表明在动态链接库的函数代码执行时解决。
2)RTLD_NOW:表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlsym(void*handle,char*symbol)
dlsym()的用法通常如下:
void(*add)(intx,inty);/*说明一下要调用的动态函数add*/
add=dlsym("xxx.so","add");/*打开xxx.so共享库,取add函数地址*/
add(89,369);/*带两个参数89和369调用add函数*/
看我出招:
执行结果:
使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用,编译时要采用-rdynamic选项与-ldl选项(不然编译未能通过),以形成可调用动态链接库的执行代码。
OK,通过本文的指导、练习相信诸位应当对Linux的库机制有了些许了解,最主要的是会开发使用库文件了。因为本人知识所限免费linux主机,文中个别观点若果不到位或理解有误的地方还请诸位个人不吝指点。