/********************************************\
| 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! |
\********************************************/
Linux静态、共享和动态库之编程
作者: Jerry Cat
时间: 2006/04/21
链接: http://www.cppblog.com/jerysun0818/archive/2006/04/22/6065.html
一.库的分类
有两种说法, 如果熟悉WIN平台下的DLL, 相信不难理解:
库可以有三种使用的形式:静态、共享和动态.静态库的代码在编译时就已连接到开发人员开发的应用程序中, 而共享库只是在程序开始运行时才载入, 在编译时, 只是简单地指定需要使用的库函数.动态库则是共享库的另一种变化形式.动态库也是在程序运行时载入, 但与共享库不同的是, 使用的库函数不是在程序运行开始, 而是在程序中的语句需要使用该函数时才载入.动态库可以在程序运行期间释放动态库所占用的内存, 腾出空间供其它程序使用.由于共享库和动态库并没有在程序中包括库函数的内容, 只是包含了对库函数的引用, 因此代码的规模比较小.
Linux下的库文件分为共享库和静态库两大类, 它们两者的差别仅在程序执行时所需的代码是在运行时动态加载的, 还是在编译时静态加载的.区分库类型最好的方法是看它们的文件后缀, 通常共享库以.so(Shared Object的缩写)结尾, 静态链接库通常以.a结尾(Archive的缩写).在终端缺省情况下, 共享库通常为绿色, 而静态库为黑色.
已经开发的大多数库都采取共享库的方式.ELF格式的可执行文件使得共享库能够比较容易地实现, 当然使用旧的a.out模式也可以实现库的共享.Linux系统中目前可执行文件的标准格式为ELF格式.
.a的是为了支持较老的a.out格式的可执行文件的
.so的是支持elf格式的可执行文件的库.
.a是静态库文件, 可以用ar 命令生成.
.so是动态库文件, 编译时加上指定的选项即可生成, 具体选项看相应的系统文档了.
二.库的命名规则
GNU库的使用必须遵守Library GNU Public License(LGPL许可协议).该协议与GNU许可协议略有不同, 开发人员可以免费使用GNU库进行软件开发, 但必须保证向用户提供所用的库函数的源代码.
系统中可用的库都存放在/usr/lib和/lib目录中.库文件名由前缀lib和库名以及后缀组成.根据库的类型不同, 后缀名也不一样.共享库的后缀名由.so和版本号组成, 静态库的后缀名为.a.采用旧的a.out格式的共享库的后缀名为.sa.
libname.so.major.minor
libname.a
这里的name可以是任何字符串, 用来唯一标识某个库.该字符串可以是一个单字、几个字符、甚至一个字母.数学共享库的库名为libm.so.5, 这里的标识字符为m, 版本号为5.libm.a则是静态数学库.X-Windows库名为libX11.so.6, 这里使用X11作为库的标识, 版本号为6.
三.库操作命令
Linux库操作可以使用命令完成, 目前常用的命令是ldd和ldconfig.
1.ldd
ldd是Library Dependency Display缩写, 它的作用是显示一个可执行程序必须使用的共享库.
$ ldd /usr/bin/mesg
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7eaf000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb7feb000)
2.ldconfig
库安装到系统以后, 为了让动态链接库为系统所认识及共享, 就需要运行ldconfig.ldconfig命令的用途, 主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下, 搜索出可共享的动态链接库(格式如lib*.so*), 进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache, 此文件保存已排好序的动态链接库名字列表, ldconfig通常在系统启动时运行, 而当用户安装了一个新的动态链接库时,就需要手工运行这个命令.
(1)命令格式
ldconfig [选项] [libs]
(2)主要选项
-v或--verbose ldconfig将显示正在扫描的目录、搜索到的动态链接库, 以及它所创建的连接的名字.
-f CONF 指定动态链接库的配置文件为CONF, 系统默认为/etc/ld.so.conf.
-C CACHE 指定生成的缓存文件为CACHE, 系统默认的是/etc/ld.so.cache,文件存放已排好序的可共享的动态链接库的列表.
-p或--print-cache 让ldconfig打印出当前缓存文件所保存的所有共享库的名字.
-r ROOT 改变应用程序的根目录为ROOT.
-n ldconfig仅扫描命令行指定的目录, 不扫描默认目录(/lib、/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.
运行没有选项的ldconfig命令时, 用于更新高速缓冲文件.这个命令主要用于高速缓冲DNS服务器(Caching DNS Server).高速缓冲DNS服务器的原理是提供查询的历史记录, 并且利用这些记录来提高查询的效率.
当某个查询是第一次被发送到高速缓冲DNS服务器时, 高速缓冲DNS服务器就将此查询的整个过程记录下来, 在一定的时期内用它来回答所有相同的查询, 从而减少整个DNS系统的负担并且提高查询速度.
四.库的升级
Linux系统软件更新很快, 新的核心几乎每几个星期就公布一次, 其它软件的更新也是非常频繁.多数情况下, 盲目跟随潮流的升级并不必要, 如果确实需要新版本的特性时再升级.换句话说, 不要为了升级而升级.Linux系统中多数软件都是用共享库来编译的, 其中包含了在不同程序之间共享的公用子例程.
在运行某个程序时, 如果看到如下信息:“Incompatible library version.”则表明需要将该库升级到程序所需要的版本.库是向下兼容的, 也就是说, 用老版本库编译的程序可以在新安装的版本库上运行, 反之则不行.
Linux库函数的升级是一项重要的工作, 往往与其它软件包的升级有一定关联作用, 所以操作前一定要备份文件.下面看一下如何把Glibc 2.2.4.13升级至2.3.2版本, 其过程如下:
1.下载.gz压缩文件并解压
在GUN C网站下载的四个.gz压缩文件, 解压至一临时目录中:
cd /usr/caolinux
tar xzvf glibc-2.3.2.tar.gz
cd glibc-2.3.2
tar xzvf ../glibc-linuxthreads-2.3.2.tar.gz
tar xzvf ../glibc-crypt-2.3.2.tar.gz
tar xzvf ../glibc-localedata-2.3.2.tar.gz
2.建立库函数的安装目录
mkdir /usr/higlibc
cd /usr/higlibc
3.建立编译目录
mkdir cao
cd cao
./configure --enable-add-ons=linuxthreads,crypt,localedata -prefix=/usr/higlibc
4.编译与安装
make
make check
make install
5.改变数据库的链接
ln -s /usr/higlibc/lib/ld-linux.so.2 /lib/ld-linux.so.2
然后, 修改/etc/ld.so.conf, 加入一行/usr/higlibc/lib, 执行下面代码:
ldconfig -v
更新/etc/ld.so.cache的内容, 列出每个库的版本号, 扫描目录和所要创建及更新的链接.
6.更改GCC设置
cd /usr/lib/gcc-lib
cp -r i386-redhat-linux higlibc
7.更新符号链接
cd /usr/higlibc/include
ln -s /usr/src/linux/include/linux
ln -s /usr/src/linux/include/asm
ln -s /usr/X11R6/include/X11
8.测试并完成
五.高级共享库特性
1. soname
共享库的一个非常重要的, 也是非常难的概念是 soname——简写共享目标名(short for shared object name).这是一个为共享库(.so)文件而内嵌在控制数据中的名字.如前面提到的, 每一个程序都有一个需要使用的库的清单.这个清单的内容是一系列库的 soname, 如同 ldd 显示的那样, 共享库装载器必须找到这个清单.
soname 的关键功能是它提供了兼容性的标准.当要升级系统中的一个库时, 并且新库的 soname 和老的库的 soname 一样, 用旧库连接生成的程序, 使用新的库依然能正常运行.这个特性使得在 Linux 下, 升级使用共享库的程序和定位错误变得十分容易.
在 Linux 中, 应用程序通过使用 soname, 来指定所希望库的版本.库作者也可以通过保留或者改变 soname 来声明, 哪些版本是相互兼容的, 这使得程序员摆脱了共享库版本冲突问题的困扰.
查看/usr/local/lib 目录, 分析 MiniGUI 的共享库文件之间的关系
2. 共享库装载器
当程序被调用的时候, Linux 共享库装载器(也被称为动态连接器)也自动被调用.它的作用是保证程序所需要的所有适当版本的库都被调入内存.共享库装载器名字是 ld.so 或者是 ld-linux.so, 这取决于 Linux libc 的版本, 它必须使用一点外部交互, 才能完成自己的工作.然而它接受在环境变量和配置文件中的配置信息.
文件 /etc/ld.so.conf 定义了标准系统库的路径.共享库装载器把它作为搜索路径.为了改变这个设置, 必须以 root 身份运行 ldconfig 工具.这将更新 /etc/ls.so.cache 文件, 这个文件其实是装载器内部使用的文件之一.
3. 使用 dlopen
另外一个强大的库函数是 dlopen().该函数将打开一个新库, 并把它装入内存.该函数主要用来加载库中的符号, 这些符号在编译的时候是不知道的.比如 Apache Web 服务器利用这个函数在运行过程中加载模块, 这为它提供了额外的能力.一个配置文件控制了加载模块的过程.这种机制使得在系统中添加或者删除一个模块时, 都不需要重新编译了.
可以在自己的程序中使用 dlopen().dlopen() 在 dlfcn.h 中定义, 并在 dl 库中实现.它需要两个参数:一个文件名和一个标志.文件名可以是我们学习过的库中的 soname.标志指明是否立刻计算库的依赖性.如果设置为 RTLD_NOW 的话, 则立刻计算;如果设置的是 RTLD_LAZY, 则在需要的时候才计算.另外, 可以指定 RTLD_GLOBAL, 它使得那些在以后才加载的库可以获得其中的符号.
当库被装入后, 可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数, 以获得符号在库中的地址.使用这个地址, 就可以获得库中特定函数的指针, 并且调用装载库中的相应函数.
六、LINUX下动态链接库的使用
重要的dlfcn.h头文件
LINUX下使用动态链接库, 源程序需要包含dlfcn.h头文件, 此文件定义了调用动态链接库的函数的原型.下面详细说明一下这些函数.
1. dlerror
原型为: const char *dlerror(void);
当动态链接库操作函数执行失败时, dlerror可以返回出错信息, 返回值为NULL时表示操作函数执行成功.
2. dlopen
原型为: void *dlopen (const char *filename, int flag);
dlopen用于打开指定名字(filename)的动态链接库, 并返回操作句柄.
filename: 如果名字不以/开头, 则非绝对路径名, 将按下列先后顺序查找该文件.
(1) 用户环境变量中的LD_LIBRARY值;
(2) 动态链接缓冲文件/etc/ld.so.cache
(3) 目录/lib, /usr/lib
flag表示在什么时候解决未定义的符号(调用).取值有两个:
1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决.
2) RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号, 一旦未解决, dlopen将返回错误.
dlopen调用失败时, 将返回NULL值, 否则返回的是操作句柄.
3. dlsym : 取函数执行地址
原型为: void *dlsym(void *handle, char *symbol);
dlsym根据动态链接库操作句柄(handle)与符号(symbol), 返回符号对应的函数的执行代码地址.由此地址, 可以带参数执行相应的函数.
如程序代码: void (*add)(int x,int y); /* 说明一下要调用的动态函数add */
add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */
add(89,369); /* 带两个参数89和369调用add函数 */
4. dlclose : 关闭动态链接库
原型为: int dlclose (void *handle);
dlclose用于关闭指定句柄的动态链接库, 只有当此动态链接库的使用计数为0时,才会真正被系统卸载.
posted on 2006-04-22 01:32
Jerry Cat 阅读(3399)
评论(1) 编辑 收藏 引用