动态加载
-
对动态库的加载分为自动加载和动态加载两种
-
自动加载
- 程序在开始执行的时候, 将依赖的动态库文件加载到内存中, 再进行函数的链接, 称为自动加载 (之前讲动态库讲过)
-
动态加载
- 程序在执行期间, 需要使用到某个动态库中的文件的时候, 可以向动态链接器发出请求, 请求将动态库文件加载到内存中. 这种行为称为动态加载
1. 动态加载API
- 动态链接器为动态库加载提供了相应的API(动态链接器也是一个封装好的库, 下面讲库中提供的四个常用的API)
- dlopen(3) dlclose(3) dlerror(3) dlsym(3)
- man 3 dlopen 可以查看函数细节
1. dlopen(3)
#include <dlfcn.h>
void *dlopen(const char *filname, int flags);
- 功能: 将动态库加载到内存
- 参数:
- filename: 共享路径. 如果只给定文件的名字, 按照动态链接器的搜索路径去找. LD_LIBRARY_PATH指定的路径, 或者默认路径下(/usr/lib | /lib)找
- flags: 加载方式, 可取以下值
- RTLD_LAZY - 延迟加载, 使用共享库中的符号(如调用库中的函数)时才加载
- RTLD_NOW - 立即加载, 函数返回的时候, 已经加载到内存
- 返回值:
- 成功: 返回动态库加载到内存的地址
- 失败: NULL 可以使用delerror(3)函数诊断错误
- 注意:
- 动态链接器的API需要使用到dl动态库文件, 所以链接的时候要用 -ldl
2. dlclose(3)
#include <dlfcn.h>
int dlclose(void *handle);
- 功能: 关闭动态库, 仅仅使动态库的引用计数减一, 并不一定从内存中移除, 只有引用计数为0时候, 才从内存中移除.
- 参数:
- handle: 动态库加载到内存地址( dlopen(3)的返回值 )
- 返回值:
- 成功: 0
- 失败: 非o 可以使用dlerror(3)诊断错误的原因
3. dlerror(3)
#include <dlfcn.h>
char *dlerror(void);
- 功能: 获取dlopen API的错误信息
- 返回值:
- 有错误: 返回错误原因字符串的首地址
- 没错误: NULL
4. dlsym(3)
- dlsym(3) 在加载到内存中动态库中,找到函数的地址
- 符号包括,全局变量,静态局部变量,函数名等
2. 代码实例
p_math.h (定义各个模块间的接口)
// 如果没有定义了这个宏, 就定义这个宏,紧接着进行函数声明, 最后endif. 如果定义过, 就直接跳到endif
#ifndef P_MATH_H_ //为了避免头文件重复包含
#define P_MATH_H_
//函数声明
int t_add(int, int);
int t_sub(int, int);
int t_mul(int, int);
int t_div(int, int);
#endif
add.c (加法模块)
#include "p_math.h"
int t_add(int x, int y){
return x + y;
}
int t_sub(int x, int y){
return x - y;
}
mul.c (乘法模块)
#include "p_math.h"
int t_mul(int x, int y) {
return x * y;
}
int t_div(int x, int y) {
return x / y;
}
dynamic.c(动态加载模块)
#include <stdio.h>
#include <dlfcn.h>
//定义一个函数指针, int (*fun_t)(int, int);
// 加上typedef, fun_t 就从函数指针变成了成函数指针类型pfun_t
typedef int (*pfun_t)(int, int);
// 通过命令行的第一个参数传递要动态加载的动态库的名字
int main(int argc, char *argv[]){
void *handle = dlopen(argv[1],RTLD_NOW);
if(handle==NULL){
printf("load failed... %s\n", dlerror());
return -1;
}
printf("load sucess...\n");
void *f = dlsym(handle, "t_mul");
if(f==NULL){
printf("dlsym: %s\n", dlerror());
return -1;
}
// 不能直接用f, 因为f是 void*(无类型指针不能直接使用)
// 这里应该做了将void* 到 int* 的类型隐式转换?
pfun_t fn=f;
printf("3 * 5 = %d\n", fn(3, 5));
dlclose(handle);
return 0;
}
- 操作步骤
$ gcc -shared -o libp_math.so add.o mul.o
$ sudo mv libp_math.so /lib/.
$ gcc dynamic.c -c
# 动态链接器的API需要使用到dl动态库文件, 链接的时候要用 -ldl
$ gcc dynamic.o -ldl
# 上面两边合起来是: gcc dynamic.c -ldl
$ a.out libp_math.so
load sucess...
3 * 5 = 15
# 没有 t_mul 函数的标识符, 只有4个库函数(自动加载)
# 链接时-ldl 动态链接, dynamic.c 中只用到dl中4个函数, 这里也就显示4个函数名
$ nm a.out
....
0000000000400690 t deregister_tm_clones
U dlclose@@GLIBC_2.2.5
U dlerror@@GLIBC_2.2.5
U dlopen@@GLIBC_2.2.5
U dlsym@@GLIBC_2.2.5
....
0000000000400756 T main
U printf@@GLIBC_2.2.5
U puts@@GLIBC_2.2.5
00000000004006d0 t register_tm_clones
0000000000400660 T _start
0000000000601060 D __TMC_END__
# 而且 a.out 也并不依赖于 libp_math.so, 依赖是 libdl.so.2(因为dl库在这里属于自动加载)
# a.out 加载到内存执行的时候,将依赖的动态库ld加载到内存, 再进行函数的链接, 这属于自动加载
# 而使用ld库中的函数去加载 libp_math.so动态库中的函数到内存中的这种行为称为动态加载
$ ldd a.out
linux-vdso.so.1 => (0x00007fff6b8c2000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fea1e539000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fea1e16f000)
/lib64/ld-linux-x86-64.so.2 (0x00007fea1e73d000)