静态库
在开始讨论动态库之前,首先对静态库做一个简短的介绍。
在静态库产生之前,编译需要链接各种.o文件,随着.o文件数量的增多,管理和引用.o文件也变得困难,于是unix决定把常用的.o文件组成一个单元,这样每次只引用一个归档单元就可以了,这种方式就是静态库。
管理静态库
# 创建/更改静态库
ar r libdemo.a mod1.o mod2.o mod3.o
# 查看静态库内容
ar tv libdemo.a
# 删除动态库的一个模块
ar d libdemo.a mod3.o
使用静态库
# 全名引用
gcc main.c libdemo.a
# 简化引用
gcc main.c -ldemo
创建动态库
静态库的缺点:
- 使用静态库链接时,会将静态库的整个副本链接到可执行文件中,如果多个静态库中都包含相同的目标文件,就会造成冗余,占用内存空间。
- 修改静态库后,可执行文件需要重新链接动态库。
动态库也叫动态库,就是为了解决静态库的缺点而提出的,动态库在内存中只存在一个副本,并且是运行时加载,这就解决了空间占用和重新链接的问题。
# 合并创建
gcc -fPIC mod1.c mod2.c mod3.c -shared -o libfoo.so
# 分步创建:1. -fPIC会创建位置无关代码;2. -shared会生成动态库
gcc -fPIC mod1.c mod2.c mod3.c
gcc -shared -o libfoo.so mod1.o mod2.o mod3.o
注:位置无关代码,通常是使用 相对路径+动态符号决议 来实现的。
使用动态库
gcc hello.c -L</path> -l<.so>
在使用g++命令时,一般先列举源文件,然后再列举动态库。这是因为链接器是按照输入文件的顺序来处理的,因此应该先列举需要链接的源文件,再列举需要链接的库文件。如果
库文件中有未定义的符号,链接器可以在后面的库文件中查找,直到找到为止。因此,如果先列举库文件,链接器可能会找不到所需的符号,从而导致链接错误。
由于可执行文件不再包含动态库的副本(静态库会被包含进elf中),就需要通过某种机制找出运行时所需的动态库。这是通过将动态库的名称嵌入到可执行文件中来完成的,在ELF文件中,库依赖性是记录在可执行文件DT_NEDDED标签中的,称为动态库依赖列表。
第二件事是运行时解析内嵌的库名,这个任务是动态链接器完成的/lib/ld-linux.so.2 ,它本身也是动态链接库。
配置动态库
动态库的查找顺序
动态库的函数查找顺序是:
- 编译时使用-rpath指定动态库路径;
- 使用 LD_LIBRARY_PATH 环境变量可以增加动态库的搜索目录;
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径,配置后要运行 ldconfig命令才能生效
- /lib , /usr/lib
以搜索到的第一个为准。
rpath
gcc -Wl,-rpath,/home/mtk -o prog prog.c libdemo.so
Wl用于给链接器传参数。
-Wl,-rpath,/data/workroom/libs/lib 会被解释成:ld -rpath /data/workroom/libs/lib
LD_LIBRARY_PATH
export LD_LIBRARY_PATH=<动态库路径>
ldconfig
ldconfig会在/etc/ld.so.cache中建立缓存,加速动态库的搜索过程;
liconfig -p会显示/etc/ld.so.cache的内容;
ldconfig还会检测次要版本的最新版本,并创建/更改soname链接到最新版本。
每次安装、更新、删除动态库都应该运行ldconfig,来更新缓存和soname链接。
已经运行着的程序会继续使用就版本,直到重启。
ldconfig会自动更新soname,链接器则需要手动链接。
1. 在 /etc/ld.so.conf 中添加动态库搜索路径
2. 运行 pkg-config 重建动态库缓存。
pkg-config
–cflags用于查找头文件,–libs用于查找动态库,使用dpkg-config的前提是有.pc文件存在,否则还是使用 dpkg -L 吧。
gcc -o program program.c `pkg-config --cflags --libs glib-2.0`
管理动态库
动态库有三个名称:真实名称、soname和链接器名称。
动态库会存在多个版本,而且版本号也是名字的一部分,为了提供一个统一的调用名称,需要抽象出一个兼容层soname,来代表动态库。
soname通常指代最新真实名称的次要版本,但是如果想引用旧版本,就需要在链接时使用真实名称或soname名称。
链接器名通常指向最新的soname。
# 真实名称格式,例如:libdemo.so.1.2
libname.so.major-id.minor-id
# soname格式,例如:libdemo.so.1
libname.so.major-id
# 链接器名,例如:libdemo.so
libname.so
# 嵌入soname,生成so
gcc -c -fPIC mod1.c mod2.c mod3.c -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0.1
# 创建soname
ln -s libfoo.so.1.0.1 libfoo.so.1
# 创建链接器
ln -s libfoo.so.1 libfoo.so
# 查看soname
objdump -p libfoo.so | grep SONAME
# 使用链接器
gcc -c main.c -lbar
延迟加载
在运行elf文件时,动态链接器会加载动态依赖列表中的所有动态库,但延迟加载比较有用,只在使用的时候加载。
使用dlopen()函数,可以即时加载动态库并搜索库中的函数。
#include<dlfcn.h>
void dlopen(const char *libfilename,int flags);
void* dlsym(void* handle, const char* symbol);
int dlclose(void* handle);
char* dlerror(void);
flags可以是RTLD_LAZY和RTLD_NOW的二者之一,前者是延迟加载,只有执行到函数代码时才加载,后者是立刻加载,会在dlopen调用后加载。
调试动态库
第一种是使用dlopen,
第二种是使用python:
import ctypes
# 加载动态链接库
mylib = ctypes.cdll.LoadLibrary('/path/to/mylib.so')
# 调用动态链接库中的函数
result = mylib.my_function(arg1, arg2)
其他动态库特性
符号可见性
gcc提供了一个特性声明,用于控制符号的可见性。
void __attribute__((visibility("hidden"))) func(){
...
}
这个作用是,将func限定为只对当前库可见,对库外不可见。
类似的是static关键字,将对象限定在本文件内,文件外不可见。
初始化和终止函数
在动态库加载和卸载时执行的函数。
void __attribute__ ((constructor)) foo1(void){
...
}
void __attribute__ ((destructor)) foo2(void){
...
}