1.序言
Incomputerscience,alibraryisacollectionofnon-volatileresourcesusedbycomputerprograms,oftenforsoftwaredevelopment.Thesemayincludeconfigurationdata,documentation,helpdata,messagetemplates,pre-writtencodeandsubroutines,classes,valuesortypespecifications.
在计算机科学中,库(library)是用于开发软件的子程序集合。库和可执行文件的区别是,库不是独立程序,她们是向其他程序提供服务的代码。
2.基础
程序库的引入使程序愈加模块化化,编译更快linux软件工程师,并且更容易更新。
2.1linux中库的分类
程序库可以分为三种:静态库(staticlibraries)和共享库(shanredlibraries)。
静态库是程序在编译时,将库内容加入到可执行程序中,在linux中静态库一般以.a结尾。共享库是可以被多个程序共享使用,而不是在生成程序的时侯被链接器拷贝到可执行程序中,共享库在linux中以.so结尾。
而另外存在的动态加载库(dynamicallyloadedlibraries)在程序执行时,按照须要动态加载共享库。
2.2库文件在文件系统中的路径
大多数系统都倾向于遵循GNU的标准,即默认置于/usr/local/lib目录下。FHS(FilesystemHierarchyStandard)推荐雨林木风linux,大多数的库文件应当装入/usr/lib目录下,系统启动须要的库文件置于/lib目录下,非系统库文件的置于/usr/local/lib下。虽然两者并不冲突,GNU推荐是的开发者的源码,而FHS推荐的是发布者,发布者可以通过系统的包管理工具选择性地覆盖源码。注意,假若我们的库文件只能通过其他库文件调用,我们的库文件应当置于/usr/local/libexec或/usr/libexec。
非常须要注意的是,基于RedHat的系统并没有将/usr/local/lib目录引入到默认的库搜索路径下,因而我们的centos也会出现这样的问题。可以在/etc/ld.so.conf加入前面的路径,也可以通过设置环境变量LD_LIBRARY_PATH来解决。
以centos为例,库文件通常在以下几个地方存在,倘若是64位系统,下边路径可能就会存在*/lib64/。
另外,在linux中usr并不是user的意思,而是unixsystemresrouces的简写,本人早已被欺骗了多年。
/lib/
/usr/lib/
/usr/local/lib/
# /var/lib/
2.3库文件的版本
表示、文件名、版本、elf共享库以lib为前缀,比如libhello.so.x.y.z表示共享库hello。前面x.y.z是版本号,x是主版本号(MajorVersionNumber),y是次版本号(MinorVersionNumber),z是发布版本号(ReleaseVersionNumber)。
主版本号(不兼容):重大升级,不同主版本的库之间的库是不兼容的。所以假如要保证向后兼容就不能删掉旧的动态库的版本。
次版本号(向上兼容):增量升级,降低一些新的插口但保留原有插口。高次版本号的库向后兼容低次版本号的库。
发布版本号(互相兼容):库的一些例如错误更改、性能改进等,不添加新插口,也不修改插口。主版本号和次版本号相同的前提下,不同发布版本之间完全兼容。
Linux采用SO-NAME(Shortforsharedobjectname)的命名机制来记录共享库的依赖关系。每位共享库都有一个对应的“SO-NAME”(共享库文件名去除次版本号和发布版本号)。例如共享库名为libhello.so.3.8.2,这么它的SO-NAME就是libhello.so.3。
在Linux系统中,系统会为每位共享库所在的目录创建一个跟SO-NAME相同的而且指向它的软联接(SymbolLink)。这个软联接会指向目录中主版本号相同、次版本号和发布版本号最新的共享库。也就是说,例如目录中有两个共享库版本分别为:/lib/libtest.so.3.8.2和/lib/libtest.so.3.7.5,这么软联接/lib/libtest.so.3指向/lib/libtest.so.3.8.2。
构建以SO-NAME为名子的软联接的目的是,致使所有依赖某个共享库的模块,在编译、链接和运行时,都使用共享库的SO-NAME,而不须要使用详尽版本号。在编译生产ELF文件时侯,假如文件A依赖于文件B,这么A的链接文件中的”.dynamic”段中会有DT_NEED类型的数组,数组的值就是B的SO-NAME。这样当动态链接器进行共享库依赖文件查找时,都会根据系统中各类共享库目录中的SO-NAME软联接手动定向到最新兼容版本的共享库。
当我们在编译器里使用共享库的时侯,如用GCC的“-l”参数链接共享库libtXXX.so.3.8.1,只须要在编译器命令行指定-lXXX即可,省略了前缀和版本信息。编译器会按照当前环境,在系统中的相关路径(常常由-L参数指定)查找最新版本的XXX库。这个XXX就是共享库的“链接名”。不同类型的库可能有相同的链接名,例如C语言运行库有静态版本(libc.a)也动态版本(libc.so.x.y.z)的区别,假如在链接时使用参数”-lc”,这么联接器都会按照输出文件的情况(动态/静态)来选择合适版本的库。eg.ld使用“-static”参数时吗,”-lc”会查找libc.a;假如使用“-Bdynamic”(默认),会查找最新版本的libc.so.x.y.z。
2.4常见的库文件
libc是Linux下的ANSIC函数库。
glibc是Linux下的GUNC函数库。
libc++是针对clang编译器非常重画的C++标准库,那libstdc++自然就是gcc的事儿了。clang与libc++的关系如同libstdc++与gcc。
再谈谈libstdc++,glibc的关系。libstdc++与gcc是捆绑在一起的,也就是说安装gcc的时侯会把libstdc++装上。那为何glibc和gcc没有捆绑在一起呢?相比glibc,libstdc++其实提供了c++程序的标准库,但它并不与内核打交道。对于系统级别的风波,libstdc++首先是会与glibc交互,能够和内核通讯。相比glibc来说unix系统是什么意思,libstdc++就变得没这么基础了。(本段内容待确认)
2.5常用常量
LD_LIBRARY_PATH
LD_PRELOAD
LD_DEBUG
2.6一些相关工具2.6.1readelf
ELF(ExecutableandLinkingFormat)定义了目标文件内部信息怎样组成和组织的文件格式。内核会按照这种信息加载可执行文件,依照该文件可以晓得从文件那里获取代码,从那里获取初始化数据,在那里应当加载共享库等信息。
readelf就是linux下展示elf文件内容的命令。本文中使用-d--dynamicDisplaythedynamicsection(ifpresent)参数展示库的使用情况。
$ readelf -d demo_share
Dynamic section at offset 0x798 contains 21 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libhello.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x400488
...
其中标记为NEEDED的就是依赖的库。
2.6.2ldd
ldd-printsharedobjectdependencies.
同样,ldd命令也是拿来查看程序的依赖。
$ ldd demo_share
linux-vdso.so.1 => (0x00007ffc18fea000)
libhello.so.0 => ./libhello.so.0 (0x00007f490b5b1000)
libc.so.6 => /lib64/libc.so.6 (0x00007f490b21d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f490b7b2000)
ldd同样也能见到程序依赖的库文件。
2.6.3ldconfig
ldconfig-configuredynamiclinkerrun-timebindings.
ldconfig是拿来配置运行时动态链接的绑定。
2.6.4strace
strace-tracesystemcallsandsignals
这个后续会专门总结。
2.6.5nm
nm-listsymbolsfromobjectfiles
Thenm(1)commandcanreportthelistofsymbolsinagivenlibrary.Itworksonbothstaticandsharedlibraries.Foragivenlibrarynm(1)canlistthesymbolnamesdefined,eachsymbol’svalue,andthesymbol’stype.Itcanalsoidentifywherethesymbolwasdefinedinthesourcecode(byfilenameandlinenumber),ifthatinformationisavailableinthelibrary(seethe-loption).
3.自定义库示例
有了前面的理解和基础,我们实际去,反例来自tldp
首先是文件libhello.c和libhello.h
/* libhello.c - demonstrate library use. */
#include
void hello(void) {
printf("Hello, library world.n");
}
/* libhello.h - demonstrate library use. */
void hello(void);
之后是调用库文件的程序demo.c
/* demo.c -- demonstrate direct use of the "hello" routine */
#include "libhello.h"
int main(void) {
hello();
return 0;
}
3.1编撰并使用静态库
编译打包生成静态库文件
$ gcc -Wall -g -c -o libhello-static.o libhello.c
$ ar rcs libhello-static.a libhello-static.o
使用静态库
$ gcc -Wall -g -c demo.c -o demo.o
$ gcc -g -o demo_static demo.o -L. -lhello-static
$ ./demo_static
Hello, library world.
3.2编撰并使用共享库
编译生成共享库文件
$ gcc -fPIC -Wall -g -c libhello.c
$ gcc -g -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0 libhello.o -lc
安装、链接共享库
$ /sbin/ldconfig -n .
$ ln -sf libhello.so.0 libhello.so
使用共享库
$ gcc -Wall -g -c demo.c -o demo.o
$ gcc -g -o demo_share demo.o -L. -lhello
$ LD_LIBRARY_PATH="." ./demo_share
Hello, library world.
3.3使用动态加载库
使用动态加载库源码
/* demo_dynamic.c -- demonstrate dynamic loading and
use of the "hello" routine */
/* Need dlfcn.h for the routines to
dynamically load libraries */
#include
#include
#include
/* Note that we don't have to include "libhello.h".
However, we do need to specify something related;
we need to specify a type that will hold the value
we're going to get from dlsym(). */
/* The type "simple_demo_function" describes a function that
takes no arguments, and returns no value: */
typedef void (*simple_demo_function)(void);
int main(void) {
const char *error;
void *module;
simple_demo_function demo_function;
/* Load dynamically loaded library */
module = dlopen("libhello.so", RTLD_LAZY);
if (!module) {
fprintf(stderr, "Couldn't open libhello.so: %sn",
dlerror());
exit(1);
}
/* Get symbol */
dlerror();
demo_function = dlsym(module, "hello");
if ((error = dlerror())) {
fprintf(stderr, "Couldn't find hello: %sn", error);
exit(1);
}
/* Now call the function in the DL library */
(*demo_function)();
/* All done, close things cleanly */
dlclose(module);
return 0;
}
编译使用动态库
$ gcc -Wall -g -c demo_dynamic.c
$ gcc -g -o demo_dynamic demo_dynamic.o -ldl
$ LD_LIBRARY_PATH="." ./demo_dynamic
Hello, library world.
3.4一些对比
程序在使用静态库的时侯,会将静态库编译到程序中,会造成编译后的程序比较大。
而程序在使用动态库非静态编译时,并没有将库文件加入到编译后的程序unix系统是什么意思,才能相对节约空间。
下边我们通过工具来瞧瞧具体的情况。
3.4.1程序使用静态库编译
通过readelf工具可以看见demo_static不依赖
$ readelf -d demo_static
Dynamic section at offset 0x6f8 contains 20 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
...
3.4.2程序使用动态库动态编译
通过readelf工具可以看见demo_share还依赖libhello.so
$ readelf -d demo_share
Dynamic section at offset 0x798 contains 21 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libhello.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
...
3.4.3程序使用动态库静态编译
使用readelf查看demo_dynamic的依赖库和前面共享库一致。
3.4.4程序全静态编译
$ gcc -g -o demo_static_compile_static demo.o -L. -lhello-static -static
$ ./demo_static_compile_static
Hello, library world.
3.4.5各类形式编译后程序对比
至此,我们如今编译下来的有四个可执行程序,即静态编译:demo_static;共享编译:demo_share;动态加载编译:demo_dynamic;全静态编译:demo_static_compile_static;
剖析她们大小可以看见:全静态编译生成的文件最大,由于他将c的库以及libhello都编译进去了;共享库编译生成的文件最小,比静态编译生成的文件要小一些,虽然静态编译会将libhello编译到可执行程序中。
$ ls -lh demo_*
-rwxrwxr-x 1 gongmh gongmh 11K Nov 17 08:12 demo_dynamic
-rwxrwxr-x 1 gongmh gongmh 7.5K Nov 17 08:07 demo_share
-rwxrwxr-x 1 gongmh gongmh 7.9K Nov 17 08:05 demo_static
-rwxrwxr-x 1 gongmh gongmh 746K Nov 17 14:16 demo_static_compile_static
$ size demo_*
text data bss dec hex filename
1902 548 24 2474 9aa demo_dynamic
1382 508 16 1906 772 demo_share
1205 492 16 1713 6b1 demo_static
678611 5792 10464 694867 a9a53 demo_static_compile_static
4.总结
实际中使用库的方式可能各有不同,不一定哪种方式就好,哪种就差。须要具体依据实际的须要选择使用。全静态编译不一定就差,虽然相对于现在动辄几十G显存的服务器来说,库文件导致的程序大小降低,可以忽视不计了。本文只是总结梳理了一下库文件的一些基础知识和普通使用方法,具体还是须要依照实际项目须要去选择。个人水平有限,有问题欢迎交流见谅。
5.参考(computing)