一、静态库
静态库实际上就是一个目标文件的集合。
1.1、创建静态库
使用 ar命令能够创建静态库:
$gcc -g -c mod1.c mod2.c mod3.c //-g使用gdb调试,-c只编译不链接
$ar r libdemo.a mod1.o mod2.o mod3.o //创建静态库libdemo.a
$rm mod1.o mod2.o mod3.o //创建完成后可删除.o目标文件
1.2、显示静态库中所包含的目标文件
$ar tv libdemo.a
rw-r--r-- 1000/100 1001016 NOV 15 12:26 2009 mod1.o
…
…
1.3、从静态库中删除某个目标文件
删除libdemo.a中的mod1.o目标文件:
$ar d libdemo.a mod1.o
1.4 、链接静态库和主程序
链接静态库libdemo.a和主程序prog:
$gcc -g -c prog.c //编译用户程序生成prog.o
$gcc -g -o prog prog.o -ldemo //链接libdemo.a与用户程序prog.o
-l 选项指定了库名(库的文件名去除了 lib 前缀和.a 后缀)
在链接时链接器会在一个标准目录中搜索静态库(如/usr/lib),如果静态库不在标准目录中,使用-L参数指定链接器搜索这个额外的目录:
$gcc -g -o prog prog.o -Lmylibdir -ldemo
1.5、静态库的缺点
1、 浪费磁盘空间和虚拟内存空间。因为每一个可执行文件都会包含被链接进来的所有目标文件的副本,程序在运行时也会独立地在虚拟内存中保存一份目标模块的副本。
2、 如果需要修改一个静态库中的一个目标模块,那么所有使用那个模块的可执行文件都必须要重新进行链接以合并这个变更。
二、共享库
2.1、共享库的特点
由于静态库的缺点,共享库被更广泛的使用,共享库的特点:
1、 目标模块不会被复制到链接的可执行文件中,当第一个需要共享库中的模块的程序启动时,库的单个副本就会在运行时被加载进内存。当后面使用同一共享库的其他程序启动时,它们会使用已经被加载进内存的库的副本。
2、 目标模块没有被复制进可执行文件中,而是在共享库中集中维护的,因此在修改目标模块时,无需重新链接程序就能够看到变更。
3、 共享库在编译时必须要使用位置独立的代码,在运行时必须要执行符号重定位,符号重定位在之前有解释过。
2.2、soname符号链接
在创建共享库的时候通常还会用到一个soname的东西,它是共享库的别名,引入soname 的目的是为了提供一层间接,使得可执行程序能够在运行时使用与链接时使用的库不同的(但兼容的)共享库,如果共享库拥有一个 soname,那么在静态链接阶段会将 soname 嵌入到可执行文件中,而不会使用真实名称,同时后面的动态链接器在运行时也会使用这个 soname 来搜索库。(共享库中静态链接和动态链接概念很重要,静态链接仅仅是将soname嵌入到可执行文件中,动态链接是指在程序运行的过程中,动态链接器会去标准目录(例如\lib和\usr\lib目录)搜索共享库)。
2.3、主次版本号
共享库的真实名称一般如下:
libdemo.so.1.0.1
libdemo.so.1.0.2
libdemo.so.2.0.0
so后面的第一个数字代表主版本号,第二个数字代表次要版本,第三个数字表示次要版本的补丁号或修订号。主版本号相同,次要版本号或补丁号不同表示两个库可兼容,主版本号不同表示两个库不能兼容。
(如何判定两个库是否兼容?)
1、每个函数的参数列表不变并且对全局变量和返回参数产生的影响不变,同时返回同样的结果值
2、没有删除库的公共 API 中的函数和变量,添加不影响兼容性
3、每个函数中分配的结构以及每个函数返回的结构保持不变
通常,创建的soname只包含库的名称和主版本号:
libdemo.so.1 -> libdemo.so.1.0.1 //左侧为soname,右侧为对应的真实库名称
libdemo.so.2 -> libdemo.so.2.0.0
链接在可执行文件中的soname 会指向该主要版本中最新的次要版本,因此可以确保可执行文件在运行时能够加载库的最新的次要版本。
2.4、链接器名称
链接器名称指向soname,只包含库的名称:
libdemo.so -> libdemo.so.1 //左侧为链接器名称,右侧为soname
总结起来就是:链接器名称指向soname,soname指向真实库名称。
2.5、创建、链接共享库的步骤
第一步:(在编译服务器端完成):
创建库目标文件、创建共享库、定义soname:
$gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c //创建库的目标文件
$gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o
//创建共享库并定义其sonname。
-fPIC 选项指定编译器应该生成位置独立的代码。使代码可以在运行时被放置在任意一个虚拟地址处,代码在访问全局、静态和外部变量,访问字符串常量,获取函数的地址时方式会发生改变,需要确定其在虚拟内存中的实际运行时位置,即符号重定位。
第二步:(在编译服务器端):
创建软链接,使链接器名称指向soname,soname指向真实库名称:
$ln -s libdemo.so.1.0.1 libdemo.so.1
$ln -s libdemo.so.1 libdemo.so
第三步:(在编译服务器端):
将共享库与prog程序链接:
$gcc -g -Wall -o prog prog.c -L. -ldemo
-L.表示静态链接时搜索库的目录为当前目录
第四步:(设备端):
在嵌入式设备中,gcc可以是交叉编译器,生成的prog程序可运行在嵌入式设备上。
将链接好的可执行程序prog以及共享库放在设备的flash的某个挂载目录上,运行可执行程序prog:
$ LD_LIBRARY_PATH=. ./prog
(改变动态链接的搜索目录)
1、上面的LD_LIBRARY_PATH=.表示运行prog程序时,在当前目录加载库,也就是改变动态链接时的搜索路径,默认从标准库路径中加载(\lib或\usr\lib),所以一般将库、soname、链接器名称都放在设备端的标准库路径中。
2、改变动态链接器的搜索路径还有一种方法,就是加-rpath选项。在上面的第三步链接中,可以加上rpath选项:
gcc -g -Wall -Wl ,-rpath,/home/lib -o prog prog.c -L. -ldemo
于是程序加载时,动态链接器会从/home/lib目录加载共享库,记住-L是指定静态链接的目录,-rpath是指定动态链接的目录。
3、在 rpath 中使用 ORIGIN 可以使主程序运行时,共享库放在任意目录都能被加载:
gcc -g -Wall -Wl ,-rpath,‘$ORIGIN’/lib -o prog prog.c -L. -ldemo
2.6、如何安装新库以及ldconfig
如何升级一个主版本号不同的库?
假设运行的共享库版本为1.0.0,要升级为2.0.0:
$gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
$gcc -g -shared -Wl,-soname,libdemo.so.2 -o libdemo.so.2.0.0 mod1.o mod2.o mod3.o
$mv libdemo.so.2.0.0 /usr/lib
$ldconfig //ldconfig一下
$cd usr/lib
$ln -sf libdemo.so.2 libdemo.so //要重新指定链接器名称,升级次版本号不需要。
如何升级一个次版本号不同的库?
假设运行的共享库版本为1.0.0,升级到1.0.1:
$gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
$gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o
$mv libdemo.so.1.0.1 /usr/lib
$ldconfig //ldconfig一下
不过我在嵌入式里的busybox里面好像没有找到ldconfig这个命令,可能嵌入式设备没有在程序运行过程中升级共享库的需求吧…(QAQ好多东西也是一知半解啊),所以这一部分先不多说了。
2.7、静态库、共享库的优先级
静态链接时如果链接目录里存在同名的静态库和共享库(如libdemo.a和libdemo.so),–ldemo链接将会优先使用共享库
强制使用静态库需要在第三步链接时在gcc 中指定-static 选项。
$gcc -g -Wall -o prog prog.c -L. -ldemo -static