Linux共享库相关知识

1. 前言:

        构建程序最简单的方式就是将一个个源文件编译成目标文件,然后将这些目标文件链接在一起组成一个可执行程序。我们都知道编译程序会经过编译、汇编、链接等几步才最终生成可执行程序。这里的链接实际上是通过一个单独的链接器程序ld来完成的。当我们gcc main.c编译一个程序时,gcc编译器会在幕后调用ld。在linux应该总是通过gcc来间接调用ld。

        当我们在实际工程项目中,模块和文件众多,我们很多时候会将某一个模块的多个源文件的一组目标文件打包成一个单元,使之成为一个集合体,便于我们编译和管理,这个集合就是库,分为静态库和动态库(共享库)。

2. 静态库:

静态库又称归档文件,我们可以将一组通用的目标文件组织为一个静态库文件,好处有二:

其一:这样当这些源文件的目标文件不变的情况下,我们可以直接用这个静态库来参与编译可执行程序,而无需重新编译静态库文件对应源文件,提高了编译效率。

其二:另外经多个目标文件归档为一个库文件,在链接的时候只需要链接这一个静态库文件,而无需一个个地链接目标文件,便于管理。

使用ar命令来生成静态库,格式为:ar options archive obj-file...

静态库的常用命令:

(1)ar r:将目标文件插入到静态库文件中,并取代其中的同名目标文件。

(2)ar t:显示目标文件中的目标文件项列表;v显示静态库文件中各个目标文件的其他信息。

(3)ar d:用于删除静态库文件中的目标文件。

将程序与静态库链接起来有两种方式:

(1)在编译的时候告诉链接器静态库的名称

(2)如果将静态库放在了标准目录中(如/usr/lib),则在编译时可以直接 -l 选项指定库名。如果静态库不在系统默认的库搜索路径中(标准目录),那么可以使用 -L 和 -l 选项,-L 用于指定静态库的搜索路径。如-L.表示当前路径。

静态库的缺点:

(1)可执行程序会包含静态库中目标文件的副本,这会使得可执行程序的变大,占用更多RAM。

(2)如果几个使用了静态库中同一模块的程序同时运行,那么在各个程序独立的虚拟内存空间内,都会有一份目标模块的副本,提高了虚拟内存的使用量。

(3)一旦静态库中目标模块有更新,那么使用这个模块的所有可执行程序必须重新连接以合入更新。

3. 动态库

3.1特点和使用方法

动态库也称共享库,解决了静态库的诸多缺点。目标模块不会被链接进可执行程序,目标模块的副本被所有使用它的可执行程序共享。当第一个需要目标模块的可执行程序运行时,他会将目标模块的副本载入内存中,后面再有使用它的程序运行时,他会直接共享这个副本。节省了磁盘空间和虚拟内存空间。

共享库的代码是被多个可执行程序共享的,但是变量却不是,变量在每个可执行程序中的虚拟内存空间中独有。

共享库可以单独维护和更新,而无需同步更新可执行程序,除非接口存在重大变更。

动态库的创建和使用:

 gcc -fPIC:指定编译器要生成位置独立的代码,使得共享库代码可以在运行时被放置在任意虚拟地址处。(可以说这个选项是必须的,因为在运行时共享库代码所处的内存位置是未知的)。

为了使用共享库,需要做以下两件事情:

(1)静态连接阶段:可执行程序不再包含所需目标文件的副本,所以在链接阶段会将共享库的名称嵌入到可执行文件中,使其知道运行时所依赖的共享库。在ELF文件中程序所依赖的所有共享库列表称为程序的动态依赖列表。

(2)动态连接阶段:程序在运行时,动态链接器会检查程序所依赖的共享库清单,并找出可执行文件中的符号名称所对应的共享库文件,如果共享库还未载入内存的话就将其载入内存。

3.2 共享库的搜索路径

 程序在运行过程中,发现所用符号是来自动态库中,而还没将库加载到内存中的话,动态连接器就会按着规则指定的目录中依次搜索共享库,然后将其加载到内存中。

共享库的路径搜索规则如下:

(1)标准路径:

        1)/usr/lib

        2)/lib:应将系统启动时用到的库放在该目录下,因为系统启动时/usr/lib目录可能还未挂载。

        3)/usr/local/lib

        4)/etc/ld.so.conf文件中所列出的目录项

其中/etc/ld.so.conf文件所列出的目录项之间用换行、空格、制表符、逗号或冒号分隔。/usr/local/lib目录项也应该包含其中,如不在,需要手动添加进去。/etc/ld.so.conf可以嵌套,如,下图中的/etc/ld.so.conf中列出了/etc/ld.son.conf.d目录中的所有.conf文件中列出的目录项。

(2)使用LD_LIBRARY_PATH环境变量来指定库的搜索路径:

LD_LIBRARY_PATH中指定的路径会在系统默认的标准路径之前进行查找。

结果:

(1)将动态库放在/usr/lib下

(2)在LD_LIBRARY_PATH环境变量中指定搜索路径

(3)在/etc/ld.so.conf文件中新增库搜索路径条目

3.3 ldconfig

通常共享库的soname、及其符号链接,与共享库本体文件都在同一目录中。

共享库可以根据规则位于各种目录中,如果动态连接器要通过搜索所有的这些目录,来找出并加载这个库,会非常耗时。而且当删除或更新了库,那么soname和符号链接就不是最新的了。

ldconfig会做的事:

(1)ldconfig命令会搜索一组标准的目录,创建或更新/etc/ld.so.cache缓存文件,是其包含所有在这些目录中的主要库版本列表,(ldconfig会先搜索/etc/ld.so.conf中的目录、再搜索/usr/lib和/lib)。

ldconfig -p:会显示/etc/ld.so.cache文件的当前内容

(2)检查每个库的各个主要版本的最新次要版本,以找出嵌入的soname,然后在同一目录为每个soname创建相对的符号链接。

当安装了一个新的库、更新或删除了既有的库,以及修改了/etc/ld.so.conf后,都应该运行ldconfig。

3.4 库的命名规范

1) 真实名称:libx1.so.2.0.1

2)soname:libx1.so.2,如果一个库有soname,那么在静态连接阶段是将soname嵌入到可执行文件中,而不是库的真实名称。在运行时,动态连接器搜索的也是soname。

 3.5 -rpath链接器选项

除了3.2介绍的共享库的搜索路径外,在静态编译阶段通过 -rpath 链接器选项,实现在可执行文件中插入一个在运行时搜索共享库的目录列表。

 通过-rpath链接器选项指定搜索路径 

假设main.c 所编译的程序prog,依赖./add/libadd.so, libadd.so又依赖 ./print/libprint.so,那么使用 -rpath编译的方法如下:

3.6 --as-needed选项与库的链接顺序中存在的问题

GCC/G++提供了 -Wl,--as-needed 和 -Wl,--no-as-needed 两个选项,这两个选项一个是开启特性,一个是取消该特性。

    在生成可执行文件的时候,通过 -lxxx 选项指定需要链接的库文件。以动态库为例,如果我们指定了一个需要链接的库,则连接器会在可执行文件的文件头中会记录下该库的信息。而后,在可执行文件运行的时候,动态加载器会读取文件头信息,并加载所有的链接库。在这个过程中,如果用户指定链接了一个毫不相关的库,则这个库在最终的可执行程序运行时也会被加载,如果类似这样的不相关库很多,会明显拖慢程序启动过程。

    这时,通过指定 -Wl,--as-needed 选项,链接过程中,链接器会检查所有的依赖库,没有实际被引用的库,不再写入可执行文件头。最终生成的可执行文件头中包含的都是必要的链接库信息。-Wl,--no-as-needed 选项不会做这样的检查,会把用户指定的链接库完全写入可执行文件中。(此部分转载自 https://blog.csdn.net/qianniuwei321/article/details/123483889)

可以看到g++编译器默认是开启"--as-needed"选项的。

 

 

 

4. 最后补充

 -Wl,options :把参数(options)传递给链接器ld 。参数可以有多个,彼此用逗号分隔。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值