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后,可以通过这个全局结构体指针来调用库里面的函数。
本文参考: