共享库---共享对象的集合
1.产生原因
随着软件规模的越来越大,我们的函数越来越多,为了简化这些极为庞大的共享对象。所以就将这些函数根据相应的需求规划成一些集合,进行一些处理生成共享库文件,这样可以极大的减少函数的数量便于管理和升级。
2。共享库的版本
更新:
兼容更新。所有的更新只是在原有的共享库基础上添加一些内容,所有的接口都保持不变。
不兼容跟新。改变了所有的原有接口,更新后原有的程序可能不能运行
就是ABI 接口改变,一般二进制接口改变,程序是不能运行的。
3。ABI改变情况以及兼容性分析
。导出函数的行为发生改变,也就是说调用这个函数以后产生的结果与以前不一样,不再满足旧版本规定的函数行为准则。
。导出函数被删除
。导出数据的结构发生变化,比如共享库定义的结构变量的结构发生改变。
。当然一般改变结构体,例如给结构体增加一些项目只要是动态分配的都是没有太大影响的。
。导出函数的接口发生变化,如函数返回值,参数被更改
。对于C++来说,ABI变化就是灾难性的
如果需要开发一个C++的共享库,需要注意以下事项。
不要在接口中使用虚函数,万不得已时,不要随意删除,添加或在子类中添加新的实现函数,这样会导致类的函数表发生变化。
不要改变任何成员变量的位置和类型
不要删除非内嵌的Public 或 protected 成员函数
不要将非嵌入式的成员函数改变成内嵌成员函数
不要改变成员函数的访问权限
不要在接口中使用模版
最重要的是,不要改变接口的任何部分或干脆不要使用C++作为共享库接口,使用C吧少年!
4。共享库的版本名
Linux有一套较为完善的命名机制。
Libname.so.x.y.z
这就是我们经常看到的共享库的标准命名形式,最前面是lib 中间是库的名字 最后是 .so 后缀
最后是它的版本号。
X:主版本号,不同版本号之间是不能兼容的。改变后需要重新编译。
Y:次版本号,表示库的增量升级,即增加一些新的接口符号,且保持原来的接口不变,一般是高的次版本号向后兼容底的版本号。
Z:发布版本号,表示库的一些错误的修正,性能的改进等,并不需要添加任何新的接口,也不对接口进行更改。相同主版本号,次版本号的共享库,不同发布版本号之间完全兼容。是完全可以运行的。
5.SO-NAME
共享库的主版本号和次版本号决定了一个共享库的接口。程序中必须包含被依赖的共享库的名字和主版本号。由于历史原因,动态链接库和C语言的共享对象文件名规则不按Linux标准的共享库命名方法,但是C语言的SO-NAME是按照正常的规则。
实际上这个软链接会指向目录中主版本号相同的,次版本号和发布版本号最新的共享库。保证了所有的以SO—NAME 这样就保证了所有的以SO—NAME 为名的软链接都指向系统中最新的共享库。
一般这个软链接在的 dynamic 段的DT_NEED类型项中。
6.共享库的系统路径
目前大多数的Linux都遵循一项标准,FHS的标准,这个标准规定了一个系统中的系统文件如何存放
包括各个目录的组织,结构和作用。这个标准规定一个系统主要有三个存放共享库的位置。
Lib ,这个位置主要存放系统最关键和基础的共享库,比如动态链接器,C语言库运行库,数学库,这些库主要支持/bin/和/sbin 需要用到的库。
/usr/local/lib,这个位置放置了一些跟操作系统本身无关的库,主要是第三方应用程序的库,
/usr/lib,这个目录主要保存的是一些非系统运行时所需要的关键性的共享库。
7.共享库查找过程
动态链接的ELF 文件在启动时会同时启动动态链接器。程序所依赖的的模块路径保存在 dynamic
段里面,由DT_NEED类型的项表示,动态链接器对于模块的查找有一定的规则;如果DT_NEED里面保存的时绝对路径,那么动态链接器就会按照这个路径去查找;如果DT_NEED里面保存的是相对路径,那么动态链接器就会在/lib/,/usr/lib 和由/etc/ld.so.conf 配置文件指定的目录中查找共享库。为了程序的可以移植性和兼容性,共享库的路径往往是相对的。
两个环境变量:
LD_LIBRARY_PATH 这是一个由若干条路径组成的环境变量,每个环境变量由冒号隔开。默认情况下,这个路径为空,进程在启动的时候会首先查找共享库时,会首先查找由这个变量指定的路径。
例如我们想使用一个修改过的libc.so.6 可以将这个库放到/home/usr/下通过改变变量指向就可以达成改变链接指向的目的
$ LD_LIBRARY_PATH = /home/user /bin/ls
也可以直接时用动态链接器
$ /lib/ld-linux.so.2 -library-path /home/user /bin/ls
动态链接器会按照下列顺序依次装载或查找共享对象
由环境变量LD_LIBRARY_PATH 指定的路径
由路径缓存文件/etc/ld.so.cache 指定的路径
默认共享库目录,先是/usr/lib 然后/lib
其实也可以使用GCC 的——L 参数
LD_PRELOAD
系统中另外还有一个环境变量叫做LD_PRELOAD,这个文件中我们可以指定预先加载的一些共享库或是目标文件。在这个文件里面指定的文件会在动态链接器按照固定规则搜索共享库之前装载,它比上一个路径里面锁指定的目录中的共享库还要优先。由于全局符号介入这个机制的存在,LD_PRELOAD里面指定的共享库或目标文件文件中的全局符号就会覆盖后面加载的同名全局符号,就可以使我们方便的改写标准C 库中的某几个函数或影响某几个其它函数。
另外还有一个有用的全局变量:LD_DEBUG
使用方法:
$LD_DEBUG = files ./helloword.out
直接在命令行使用就可以看到这个可执行文件的的装载整个过程。
Bindings : 显示-动态链接与符号的全过程
files : 显示装载过程
libs : 显示共享库的查找过程
versions : 显示符号的版本依赖关系
reloc : 显示重定位的过程
symbols : 显示符号表的查找的过程
statistics: 显示动态链接过程中的各种统计信息
all : 显示以上的所有信息
help : 显示上面的各种可选值的帮助信息
共享库的创建与安装:
创建共享库:
首先GCC 有一个参数-wl 参数这个参数可以将指定的参数传递给链接器
例如:
gcc -shared -fPIC -Wl,-soname,my_soname -o libray_name source_files
library_files
比如,我们拥有连各个C文件我们想把他们做成库文件。
两种方法:
gcc -shared -fPIC -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0.0 libfoo1.c libfoo2.c
-lbar1 -lbar2
也可以分开制作:
gcc -c -g -Wall -o libfoo1.o libfoo1.c
gcc -c -g -Wall -o libfoo2.o libfoo1.c
ld -shared -soname libfoo.so.1 -o libfoo.so.1.0.0 libfoo1.o libfoo2.o -lbar1 -lbar2
几个注意事项:
不要把输出共享库中的符号和调试信息去掉,也不要使用gcc 的 -fomit-frame-pointer 选项
有时候如果需要调试程序但是又不希望影响现有的程序正常运行。使用LD_LIBRARY_PATH是一个很好的方法。一可以使用-rpath 这是链接器参数,GCC 参数(-Wl ,rpath)指定链接产生的目标程序的共享库查找路径
$ld -rapth /home/mylib -o program.out program.o -lsomelib
这样产生的输出可执行文件program.out 在被动态链接器装载时,动态链接器会首先在/home/mylib下查找动态库。
还有一个放止反向引用失败的参数 -Wl ,-export-dynamic
8.清除符号信息
正常情况下编译出来的共享库或者可执行文件里面带有符号信息和调试信息,这些信息在调试时非常有用,但是对于最终发布版本来说,这些符号信息用处并不大,并且使得文件尺寸边大。我们可以使用一个叫做strip 工具 。
$strip libfoo.so
也可以使用链接器的-S -s 两个参数,-S消除调试符号信息,-s消除所有符号信息,GCC 使用-Wl,-s
或者-Wl ,-S
9.共享库的安装
首先将库复制到某个标准的库目录中,然后使用ldconfig即可。
9.共享库构造函数与析构函数
很多时候我们希望在加载的时候能执行一些函数,其实只要在函数声明时加上__attribute__((constructor)) 的属性,就是指定该函数为共享库构造函数,这种函数就会在加载时执行,就是在main ( )函之前执行。如果打开共享库,就会在打开函数返回之前执行。
相对应的就是析构函数,同样也会私一个参数__attribute__((destoructor)),析构函数会在关闭共享库函数返回前执行完毕。
eg.void __attribute__((constructor)) add(int a ,int b)
当有多个函数的时候是有优先级之分的。
例如:
void __attribute__((constructor(5)) init_fun1(void)
数子5就是优先级。
对于构造函数来说,属性中优先级数子越小的函数将会在优先级大的函数之前执行,对于析构函数正好向反。
版权声明:本文为博主原创文章,未经博主允许不得转载。