Linux系统中的“动态库”和“静态库”

Linux系统中的“动态库”和“静态库”

       在Linux操作系统中,普遍使用ELF格式作为可执行程序或者程序生成过程中的中间格式。ELF(Executable and Linking Format,可执行连接格式)是UNIX系统实验室(USL)作为应用程序二进制接口(Application BinaryInterface,ABI)而开发和发布的。工具接口标准委员会(TIS)选择了正在发展中的ELF标准作为工作在32位Intel体系上不同操作系统之间可移植的二进制文件格式。本文不对ELF文件格式及其组成做太多解释,以免冲淡本文的主题,大家只要知道这么个概念就行。以后再详解Linux中的ELF格式。源代码到可执行程序的转换时需要经历如下图所示的过程:

可执行程序的生成过程

可执行程序的生成过程

  1. 编译

           编译是指把用高级语言编写的程序转换成相应处理器的汇编语言程序的过程。从本质上讲,编译是一个文本转换的过程。对嵌入式系统而言,一般要把用C语言编写的程序转换成处理器的汇编代码。编译过程包含了C语言的语法解析和汇编码的生成两个步骤。编译一般是逐个文件进行的,对于每一个C语言编写的文件,可能还需要进行预处理。
  2. 汇编

           汇编是从汇编语言程序生成目标系统的二进制代码(机器代码)的过程。机器代码的生成和处理器有密切的联系。相对于编译过程的语法解析,汇编的过程相对简单。这是因为对于一款特定的处理器,其汇编语言和二进制的机器代码是一一对应的。汇编过程的输入是汇编代码,这个汇编代码可能来源于编译过程的输出,也可以是直接用汇编语言书写的程序。
  3. 连接

           连接是指将汇编生成的多段机器代码组合成一个可执行程序。一般来说,通过编译和汇编过程,每一个源文件将生成一个目标文件。连接器的作用就是将这些目标文件组合起来,组合的过程包括了代码段、数据段等部分的合并,以及添加相应的文件头。

       GCC是Linux下主要的程序生成工具,它除了编译器、汇编器、连接器外,还包括一些辅助工具。在下面的分析过程中我会教大家这些工具的基本使用方法,Linux的强大之处在于,对于不太懂的命令或函数,有一个很强大的“男人”时刻stand by your side,有什么不会的就去命令行终端输入:man [命令名或函数名],然后阿拉神灯就会显灵了。

可执行程序的执行过程

        作为UNIX操作系统的一种,Linux的操作系统提供了一系列的接口,这些接口被称为系统调用(System Call)。在UNIX的理念中,系统调用”提供的是机制,而不是策略”。C语言的库函数通过调用系统调用来实现,库函数对上层提供了C语言库文件的接口。在应用程序层,通过调用C语言库函数和系统调用来实现功能。一般来说,应用程序大多使用C语言库函数实现其功能,较少使用系统调用。

ELF文件格式包括三种主要的类型:

  • 可执行文件
    • 可执行文件包含了代码和数据,是可以直接运行的程序。
  • 可重定向文件

    • 可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。
    • *.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些*.o文件的活动可以反映出不同的需要。Linux下,我们可以用gcc -c编译源文件时可将其编译成*.o格式。
  • 共享库

    • 也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为ld.so.1,libc.so.1或者 ld-linux.so.1。

一个ELF文件从连接器(Linker)的角度看,是一些节的集合;从程序加载器(Loader)的角度看,它是一些段(Segments)的集合。ELF格式的程序和共享库具有相同的结构,只是段的集合和节的集合上有些不同。

从连接器和加载器的角度分别看待库

那么到底什么是库呢?

动态库和静态库

库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行,库分为两种

  • 静态库

           这类库的名字一般是libxxx.a,xxx为库的名字。利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
  • 动态库

           这类库的名字一般是libxxx.M.N.so,同样的xxx为库的名字,M是库的主版本号,N是库的副版本号。当然也可以不要版本号,但名字必须有。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。

说明:

       当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。

OK,有了这些知识,接下来大家就可以弄明白我所做的事情是干什么了。都说例子是最好老师,我们就从例子入手。

静态库链接

       我们先制作自己的静态链接库,然后再使用它。制作静态链接库的过程中要用到gcc和ar命令。准备两个库的源码文件stlib1.c和stlib2.c,用它们来制作库libtest.a,如下:

       为了不报警告,我加了头文件

       静态库的生成

       静态库文件libmytest.a已经生成,用file命令查看其属性,发现它确实是归档压缩文件。

       静态库的属性

       用ar -t libmytest.a可以查看一个静态库包含了那些obj文件:

       静态库的查看

       接下来我们就写个测试程序来调用库libtest.a中所提供的两个接口print_1()和print_2(),当然为了不报警告,加上头文件。

       静态库的使用

这就是静态库的创建以及使用,刚才的编译选项中使用的-L选项代表告诉编译器链接的时候在哪里去寻找库,-l选项代表使用哪个库

动态库

动态库的后缀为*.so。在Linux发行版中大多数的动态库基本都位于/usr/lib和/lib目录下。

动态库相关的事:

       有时候当我们的应用程序无法运行时,它会提示我们说它找不到什么样的库,或者哪个库的版本又不合它胃口了等等之类的话。那么应用程序它是怎么知道需要哪些库的呢?这里介绍一个很棒的命令ldd,就是用来查看一个文件到底依赖了那些so库文件。

Linux系统中动态链接库的配置文件一般在/etc/ld.so.conf文件内,它里面存放的内容是可以被Linux共享的动态联库所在的目录的名字。我的系统中,该文件的内容如下:

动态库配置文件

很明显文件里面的ls.so.conf.d是一个目录,然后打开/etc/ld.so.conf.d/目录发现其下存放了很多*.conf文件,如下:

动态库路径文件

其实在/etc目录下还存在一个名叫ld.so.cache的文件。从名字来看,我们知道它肯定是动态链接库的什么缓存文件。
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命令,这样才能链接的上。

开始动态库的操作:

我们将刚才的两个源文件shlib1.c、shlib2.c,制作成一个名为libtest.so的动态库,要生成一个动态库有两种办法:

方法1:

       首先将文件进行编译,不过需要加上-fPIC 选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性,注意:fPIC是编译选项

       然后将两个文件进行链接并生成动态库,不过需要加上-shared 选项,告诉gcc生成动态库而不是可执行文件。
动态库生成方法一

方法1:

       直接将源文件的编译链接放在一起执行,不过要同时加上-shared-fPIC 选项
动态库生成方法二

至此,我们的动态库就生成完成了,接下来就是使用我们的动态库了:

动态库的使用:

       这里和上面的main文件是一样和静态库是一样的,在链接的时候需要加上-L-l选项,便于编译器找到我们的库

       动态库的使用方法1

注意:这里我们很清楚的发现此次生成的可执行文件直接就可以执行了,但其实在使用动态库的时候,一般库和调用库的文件是不在一起的,这样才能起到共享的作用,然后当我们把库文件挪到其它位置的时候,编译链接是可以通过的,但是运行的时候就出错了

以下为截图:

       动态库运行链接出错
       发现提示信息是annot open shared object….,这种错误明显就是没有找到库的错误,但是有人就问了,我刚才编译的时候不是都指定了库的位置了么,现在为什么又找不到了,其实我们在刚开始就说过,动态库与静态库不一样的地方有一点就是动态库并不会将库的内容在编译的时候,加入到可执行文件中,也就是可执行文件生成之后,里面并没有动态库的代码,而使用静态库编译出来的可执行文件里面就含有静态库的的代码。那么既然代码中既然没有代码,所以在运行的时候需要借助一些动态库来运行喽,可是库运行的时候在哪儿呢?刚才编译的时候还给我说哪里有一个动态库,可是现在运行的时候,我上哪儿去找这个动态库啊?

       所以在Linux系统中,有默认的动态库的存放位置的标识文件,所有可执行程序如果需要用到这些库的时候直接去这个文件中找就行了,而这个文件就是就是etc/ld.so.cache 里面的内容,然后再回到上面就知道怎样将我们的动态库添加到etc/ld.so.cache里面了。

其实也不止这一种方法,因为系统的动态库都有自己的默认的目录,像上面说的/lib/或者usr/lib/,也就只说你可以直接将你的动态库放到系统的默认的目录里面即可

       当然还有第三种办法,其实一个可执行程序在执行的时候不光et/ld.so.cache里卖弄读取动态库的位置,还会从LD_LIBRARY_PATH环境变量中读取,所以我们可以将此环境变量进行设置,让其找到我们的动态库。

最后一点注意事项:

       如果你将动态库按照以上三种方法存放,那么也就说系统可以找到你的动态库所在的位置,此时你在编译的时候使用此库,就不用加上-L选项,但是-l选项还是要加的

转载:https://blog.csdn.net/u010977122/article/details/52958330#commentBox

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值