【程序员的自我修养】第8章 Linux共享库的组织
来源:互联网 发布:手机淘宝5.8.4版本 编辑:程序博客网 时间:2024/06/05 05:13
第8章 Linux共享库的组织
共享库版本
Linux下的共享库就是普通的ELF共享对象。
共享库也有自己的ABI(Application Binary Interface),不同的语言对应接口的兼容性不同。导致ABI改变的行为有:
l 导出函数的行为发生改变
l 导出函数被删除
l 导出数据的结构发生变化
l 导出函数的接口变化,如函数返回值、参数等
如果你要开发一个导出接口为C++的共享库,注意以下几点,以防止ABI不兼容:
l 不要在接口类中使用虚函数,虚函数表会以为子类的实现而发生变化
l 不要改变类中任何成员变量的位置和类型
l 不要删除非内嵌的public或protected成员函数
l 不要将非内嵌的成员函数改变内嵌成员函数
l 不要改变成员函数的访问权限
l 不要在接口中使用模版
l 更重要的是,不要改变接口的任何部分或者干脆不要使用C++作为共享库接口
解决共享库的兼容性问题,有效办法之一就是使用共享库版本的方法,Linux有一套规则来命名系统的每一个共享库:
libname.so.x.y.z
lib 固定前缀;
name 库的名字;
.so 固定后缀;
x 主版本号 表示重大升级,不同主版本号的库之间不兼容;
y 次版本号 库的增量升级,即增加一些新的接口符号,且保持原来的符号不变。在主版本号不变的情况下,高的次版本号的库向后兼容低的此版本号的库。
z 发布版本号 库的一些错误的修正、性能的改进等,并不添加任何新的接口,也不对接口进行更改。相同主版本号、次版本号的共享库,不同的发布版本号之间完全兼容。
共享库的主板本号和次版本号决定了一个共享库的接口。
SO-NAME: 每个共享库都有一个对应的SO-NAME,这个SO-NAME即共享库的文件名去掉次版本号和发布版本号,保留主版本号。例如,共享库libfoo.so.2.6.1的SO-NAME是libfoo.so.2 。 在Linux系统中系统会为每个共享库在它所在的目录创建一个跟SO-NAME相同的并且指向它的软链接(Symbol Link)。
为什么搞一个SO-NAME呢: 实际上SO-NAME这个软链接会指向目录中主版本号相同、次版本号和发布版本号最新的共享库。
在Linux系统里面,动态链接器是 /lib/ld-linux.so.X ,程序所依赖的共享对象全部由动态链接器负责装载和初始化。任何一个动态链接的模块所依赖的模块路径保存在 “.dynamic”段里面,由DT_NEED类型的项表示。动态链接器对于模块的查找有一定的规则:
如果DT_NEED保存的是绝对路径,那么动态链接器就按照该路径去查找
如果DT_NEED保存的是相对路径,那么动态链接器会在 /lib 、/usr/lib 和由 /etc/ld.so.conf配置文件指定的目录中查找共享库。
Linux系统中有一个叫做ldconfig的程序,它为共享库目录下的各个共享库创建、删除或更新相应的SO-NAME,这样每个共享库的SO-NAME都能指向正确的共享库文件;该程序还会将这些SO-NMAE收集起来,集中存放到 /etc/ld.so.catch文件里面,并建立一个SO-NAME的缓存,加快了共享库的查找过程。
文件A的.dynamic段中会有DT_NEED类型的字段,字段的值是它所依赖的所有文件(共享库)的SO-NAME。当共享库进行升级时,如果只是进行增量升级,即保持主版本号不变,只改变次版本号或发布版本号,那么我们可以直接将新版的共享库替换掉旧版,并且修改SO-NAME的软链接指向新版本共享库,即可实现升级;当共享库的主版本号升级时,系统就会存在多个SO-NAME,由于这些SO-NAME并不相同,所以已有的程序并不受影响。总之,SO-NAME是用来管理共享库依赖关系的。
符号版本
基于符号的版本机制,让每个导入导出的符号都有一个相关联的版本号,这种做法类似于名称修饰。比如将lib.so.1.2升级到lib.so.1.3,仍然将lib.so.1这个SO-NAME保持,但在1.3这个新版中添加的全局符号打上一个标记,比如“VERS_1.3”
共享库系统路径
目前大多数包括Linux在内的开源操作系统都遵守一个叫 FHS (File Hierarchy Standard)的标准,这个标准规定了一个系统中的系统文件应该如何存放。FHS规定,一个系统中主要有3个存放共享库的位置:
l /lib 系统最关键和基础的共享库(如动态链接器、C语言运行库、数学库等);
l /usr/lib 非系统运行时所需要的关键性的共享库,主要是开发时用到的共享库(用户程序或shell一般不用);
l /usr/local/lib 主要是第三方应用程序的库。
共享库查找顺序
动态链接器查找某个动态链接模块A所依赖的模块(保存在A的.dynamic段的DT_NEED字段里)。
1. 如果DT_NEED里面保存的是绝对路径,那么动态链接器就按照这个路径去查找;
2. 如果DT_NEED里面保存的是相对路径,则按照下面的顺序查找:
2.1. 由环境变量LD_LIBRARY_PATH指定的路径
2.2. 由路径缓存文件/etc/ld.so.cache指定的路径
2.3. 默认共享目录/usr/lib
2.4. 默认共享目录/lib
环境变量
LD_LIBRARY_PATH 可以临时改变应用程序的共享库查找路径,而不会影响系统中的其他程序;
LD_PRELOAD 指定预先装载的一些共享库或目标文件,会在动态链接器按照固定规则搜索共享库之前装载
LD_DEBUG 打开动态链接器的调试功能,动态链接器在运行时打印出各种有用信息
共享库的创建和安装
创建
$gcc -shared -fPIC -W1, -soname, libfoo.so.1 -o libfoo.so.1.0.0 \
libfoo1.c libfoo2.c \
-lbar1 -lbar2
-shared 输出为共享类型;
-fPIC 使用地址无关代码;
-W1 将指定参数传递给链接器(-soname, libfoo.so.1 指定输出共享库so-name);
-lbar1 -lbar2 生成的这个共享库依赖于libbar1.so和libbar2.so两个共享库。
安装
$su root
$ldconfig -n shared_library_directory
-n 仅扫描命令行指定的目录,不扫描默认目录/usr/lib和/lib,也不扫描配置文件/etc/ld.so.conf所列的目录
GCC提供了一种共享库的构造函数,只要在函数声明时加上 “__attribute_((constructor))”的属性,即指定该函数为共享库构造函数,拥有这种属性的函数会在共享库加载时被执行,即在程序的main函数之前执行。如果我们使用dlopen()打开共享库,共享库构造函数会在dlopen()返回之前被执行,与之相反的析构函数:
void __attribute__((constructor)) init_func(void);
void __attribute__((distructor)) fini_func(void);
多个构造函数的情况下,执行顺序是没有规定的。我们可以指定构造/析构函数的优先级:
void __attribute__((constructor(2))) init_func(void);
void __attribute__((distructor(5))) fini_func(void);
对构造函数来讲,优先级数字越小优先级最高,析构函数相反。这符合先申请的资源后释放的一般规则。
- 【程序员的自我修养】第8章 Linux共享库的组织
- 《程序员的自我修养》读书笔记4 -- Linux共享库组织
- 【程序员的自我修养】第11章 运行库
- 【程序员的自我修养】第1章 温故而知新
- 【程序员的自我修养】第2章 编译和链接
- 【程序员的自我修养】第4章 静态链接
- 【程序员的自我修养】第5章 Windows PE/COFF
- 【程序员的自我修养】第7章 动态链接
- 【程序员的自我修养】第10章 内存
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 对话框工具栏信息提示的实现
- 在Android隐藏launcher应用图标由其他应用启动
- 网站数据分析的维度和指标
- Flex Swf 访问本地文件,本地安全沙箱问题
- vs2005操作word详解
- 【程序员的自我修养】第8章 Linux共享库的组织
- linux下用c语言实现约瑟夫环游戏
- PHP zend framework学习心得
- 【程序员的自我修养】第9章 Windows下的动态链接
- 事务与锁定
- 【程序员的自我修养】第10章 内存
- 直接路径法和在线重定义分区比较
- Web开发的发展史---Web开发技术的演变
- 笔记本禁用自带键盘攻略-------针对shift默认按下的解决方案