nginx从1.9.11开始,支持运行时动态加载模块了,浏览了下代码实现,在类unix系统上是用dlopen,dlsym和dlclose实现的。下面看看这几个函数的功能。
dlopen
功能:
打开一个动态链接库。
包含头文件:
#include <dlfcn.h>
函数定义:
void * dlopen(const char * pathname, int mode);
函数描述:
dlopen()以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。使用dlclose()来卸载打开的库。
mode的取值:
RTLD_LAZY 暂缓决定,等有需要时再解出符号。
RTLD_NOW 立即决定,返回前解除所有未决定的符号。
RTLD_LOCAL
RTLD_GLOBAL 允许导出符号。
RTLD_GROUP
RTLD_WORLD
返回值:
打开错误返回NULL
成功,返回库引用
编译时候要加入 -ldl (指定dl库)
dlsym
功能:
根据动态链接库操作句柄与符号,返回符号对应的地址。
包含头文件:
#include <dlfcn.h>
函数定义:
void*dlsym(void* handle,const char* symbol);
函数描述:
dlsym根据动态链接库操作句柄handle与符号symbol,返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。
handle是由dlopen打开动态链接库后返回的指针,symbol就是要求获取的函数或全局变量的名称。
dlclose
功能:
关闭指定句柄的动态链接库。
包含头文件:
#include <dlfcn.h>
函数定义:int dlclose(void* handle);
函数描述:
关闭由dlopen打开的动态链接库句柄,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
生成动态库:
hello.c:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
typedef struct {
const char *module;
int (*getvalue)(char *val);
int (*printfhello)(void);
} hello_dl_api;
int getvalue(char *val)
{
int retval = -1;
if (val) {
retval = sprintf(val, "remain smile");
printf("%s, %d, val = %s\n", __FUNCTION__, __LINE__, val);
}
return retval;
}
int printfhello(void)
{
int retval = -1;
printf("%s, %d, hello world\n", __FUNCTION__, __LINE__);
return 0;
}
const hello_dl_api hello = {
.module = "hello",
getvalue,
printfhello
};
编译成动态库:gcc -shared -o hello.so hello.c
上面由一个全局的结构体hello指向两个函数。在dlsym的描述可知,返回的句柄不仅可以获取函数的地址,还可以获取全局变量的地址。
测试文件:
hello_dlopen.c
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
typedef struct {
const char *module;
int (*getvalue)(char *val);
int (*printfhello)(void);
} hello_dl_api;
int main(int argc, char **argv)
{
hello_dl_api *hello;
int i = 0;
void *handle;
char value[20] = {0};
handle = dlopen("hello.so", RTLD_LAZY);
if (!handle) {
printf("%s, %d, handle == NULL\n", __FUNCTION__, __LINE__);
return -1;
}
dlerror();
hello = dlsym(handle, "hello");
if (!hello) {
printf("%s, %d, handle == NULL\n", __FUNCTION__, __LINE__);
return -1;
}
if (hello && hello->printfhello)
i = hello->printfhello();
printf("%s, %d, i = %d\n", __FUNCTION__, __LINE__, i);
if (hello && hello->getvalue)
i = hello->getvalue(value);
if (hello && hello->module)
printf("%s, %d, module = %s\n", __FUNCTION__, __LINE__, hello->module);
dlclose(handle);
return 0;
}
编译指令:gcc -o hello_test hello_dlopen.c -ldl
执行结果:
./test
printfhello, 28, hello world
main, 37, i = 0
getvalue, 18, val = remain smile
main, 42, module = hello
由此可见,dlsym找到全局结构体hello后,可以通过这个全局结构体指针来调用库里面的函数。
本文参考: