Hello,小伙伴们,大家好!最近有小伙伴问我程序库相关的问题。程序库的存在很大程度上提高了程序的复用性、可维护性,但是程序库的应用往往对于初学者来说有些摸不清头脑,所以这一期本文从Linux的角度谈谈Linux下的程序库。
关注公人从号Linux兵工厂
,领取海量Linux学习资料,更有好多干货文章供学习
1. 什么是库
库文件一般就是编译好的二进制文件,用于在链接阶段同目标代码一起生成可执行文件,或者运行可执行文件的时候被加载,以便调用库文件中的某段代码。库文件无法直接执行,因为它的源代码中没有入口主函数,而只是一些函数模块的定义和实现,所以无法直接执行。程序库使程序更加模块化,重新编译更快,更新更容易
- 说起库,对于软件开发人员来说都不陌生,而且应该是必须掌握的一项技术。在windows平台和linux平台下都大量存在着很多库。因此库文件是为了方便升级、维护或二次开发,而发布的一组可以单独与应用程序在编译时或运行时链接的二进制可重定位目标码文件。
- 实际开发中我们所编写的程序需要依赖很多基础的底层库,因此库的存在有很大的意义,避免每次编码都要从头开始
- 本质上库是可执行代码的二进制形式,这个文件可以在编译时由编译器直接链接到可执行程序中,也可以在运行时根据需要动态加载到内存中
- Windows和Linux系统的本质不同,所以这两个系统库的格式不同,同样也是不兼容的,本文不讲Windows下的库,我们只关注Linux下的程序库
- 例如我们常用的标准C/C++库、Qt库、GTK库等
2. 库的种类
为了便于理解,将程序库可以分为三种类型:静态库、共享库和动态加载(DL)库
2.1 静态库
Linux下静态库以.a结尾的库文件
- 静态库实际上是一些目标文件的集合,在生成可执行文件阶段进行链接。Linux下编译源码时源文件经过编译生成.o的目标文件,.o的目标文件在链接阶段经过链接生成可执行程序。所以在链接阶段可以链接.o的目标文件,也可以把所有.o的目标文件进行打包,统一进行链接,因此打包.o文件生成的文件,就是
静态库
。 - 静态库只在程序链接阶段被链接使用,链接器会将程序中使用到代码段和数据段从库文件中拷贝进来。当链接完成并生成可执行程序后,在程序执行阶段就不需要静态库了。因为使用静态库的应用程序需要拷贝所用到的代码段、数据段等,所以链接静态库生成的可执行程序会增大。当多个程序连接相同的静态库时,运行时所占用内存空间较大,但是由于程序运行的时候不再动态加载静态库,所以速度相比于共享库会快一些。
2.2 共享库
Linux下共享库以.so结尾的库文件
- 共享库在程序链接的时候不会像静态库那样从库中拷贝使用的代码段和数据段到生成的可执行程序中,而只是做相应的标记,在程序开始执行时,动态地加载所需的库。因此,可执行程序在运行的时候需要共享库的支持。调用共享库的可执行程序比静态库链接出来的可执行程序要小,当多个程序调用共享库时,运行时所占用内存空间比静态库的方式要小。
共享库命名
在Linux系统中我们经常看到同一个共享库还有软连接文件指向共享库。一般来说一个共享库有三个名字:soname、real-name、linker-name
- soname是一个软连接,用来区分版本的名字,如果real-name文件存在的话,它是指向real-name的软链接文件,名称的形式一般是lib*.so.X.Y(其中X,Y就是代表版本号),每当接口改变时它都会递增。在工作系统上,完全限定的 soname 只是指向共享库“真实姓名”的符号链接
- real-name每个共享库还有一个“真实名称”,即包含实际库代码的文件名。真实姓名在
soname
上加上一个小数点、一个小号、另一个小数点和发布号。最后期间和版本号是可选的。次要编号和版本号通过让你确切知道安装了哪些版本的库来支持配置控制。请注意,这些数字可能与文档中用于描述库的数字不同 - linker-name是传递给连接器的名字,应用程序调用时用于链接的搜索,一般它可能就是指向soname的连接,名称的形式一般是lib*.so。换句话说,它只是没有任何版本号的soname
Linux系统上这样做的目的主要是系统中允许不同版本的库文件共存,一般在命名库文件的时候通常与soname相同
如何装载共享库
-
ldconfig命令,在Linux中,运行一个可执行程序时,程序装载器会被自动装载并运行。这个程序装载器就是/lib/ld-linux.so.X(X是版本号)。该加载程序依次查找并加载该程序使用的所有其他共享库。被搜索的目录保存在/etc/ls.so.conf文件中,但如果某个所使用的库的路径不在搜索之内,手动添加上。为了避免程序每次启动都搜索一边,Linux系统对共享库采用了缓存管理之ldconfig工具,其默认读取/etc/ld.so.conf文件,对所有共享库按照一定规范建立符号连接,然后将信息写入/etc/ld.so.cache。每次搜索的时候实际是通过ld.so.cache这个缓存文件进行搜索,/etc/ld.so.cache的存在大大加快了程序的启动速度。每次修改ld.so.conf文件之后,运行ldconfig命令便把信息更新到缓存文件中。
-
环境变量,可以通过设置环境变量LD_LIBRARY_PATH来设置ld的装载路径。这样装载器就会首先搜索该变量的目录,然后搜索默认目录。
-
传参数,如果您不想设置LD_LIBRARY_PATH环境变量,在 Linux 上可以直接调用程序加载器并向其传递参数。
例如,以下将使用给定的PATH而不是环境变量LD_LIBRARY_PATH的内容,并运行给定的可执行文件:
/lib/ld-linux.so.2 --library-path 可执行路径
- 在Linux系统上或嵌入式Linux系统上装载库一般通过下面三种方式:
1.拷贝库到默认的库搜索路径/usr/lib中
2.设置环境变量LD_LIBRARY_PATH,在其中添加库的路径
3.修改配置文件/etc/ld.so.conf加入库所在的路径,并刷新缓存ldconfig
4.木荣君常用1、2两种方法
2.3 动态加载库
动态加载库(dynamically loaded (DL) libraries)是指在程序运行过程中加载的函数库。而不是像共享库一样在程序启动的时候加载。
在Linux中,动态库的文件格式跟共享库没有区别,主要区别在于共享库是程序启动时加载,而动态加载库是运行的过程中加载。可以理解为动态加载库是共享库的另一种调用方式。
DL对于实现程序模块化很有用处,因为它可以让程序在运行时进行模块升级。
动态加载库如何实现
在Linux系统中,实现动态加载库的调用,有一个用于打开库、查找符号、处理错误和关闭库的API。C程序需要包含头文件<dlfcn.h>才能使用这些API,具体相关的API使用我们放在下一节详解
3. 三种库对比
库 | 特点 |
---|---|
静态库 | 静态链接库在程序编译时会被链接到目标代码中,目标程序运行时将不再需要库,移植方便,但是体积较大,因为所有相关的库内容都被链接合成一个可执行文件,这样导致可执行文件的体积较大 |
共享库 | 动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,因为可执行文件体积较小。有了动态库,程序的升级会相对比较简单,只需要替换动态库的文件,而不需要替换可执行文件 |
动态加载库 | 动态库的文件格式跟共享库没有区别,主要区别在于共享库是程序启动时加载,而动态加载库是运行的过程中加载。可以理解为动态加载库是共享库的另一种调用方式 |
4. Linux下库文件制作
本节只针对Linux下库的概念及分类做详细阐述,下一节将详细讲解如何通过程序创建属于我们自己的库文件,包括创建静态库、共享库、动态加载库的实现等