运行时(动态)加载

    动态库的加载涉及到4个API接口:打开动态库(dlopen)、查找符号(dlsym)、错误处理(dlerror)以及关闭动态库(dlclose),程序可以通过这几个API对动态库进行操作。

1.dlopen()

    dlopen()函数用来打开一个动态库,并将其加载到进程地址空间,完成初始化的过程,它的C原型定义为:

void * dlopen(const char *filename, int flag);

    第一个参数是加载动态库的路径,如果这个路径是绝对路径(以"/"开始的路径),则该函数将尝试直接打开该动态库;如果是相对路径,那么dlopen()会尝试在以一定的顺序去查找该动态库文件:

1.查找有环境变量 LD_LIBRARY_PATH 指定的一系列目录。

2.查找由/etc/ld.so.cache里面所指定的共享库路径。

3./lib、/usr/lib。

    当然,这在理论上不应该成为一个问题,因为所有的库都应该只存在于某个目录中,而不应该在多个目录中有不同的副本,这将会导致系统变得极为不可靠。

    很有意思的是,如果我们将filename这个参数设置为0,那么dlopen返回的将是全局符号表的句柄,也就是说我们可在运行时找到全局符号表里面的任何一个符号,并且可以执行它们,这有些类似高级语言反射(Reflection)的特性。全局符号表包括了程序的可执行文件本身、被动态链接器加载到进程中的所有共享模块以及在运行时通过dlopen打开且使用了RTLD_GLOBAL方式的模块中的符号。

    第二个参数flag表示函数符号的解析方式,常量RTLD_LAZY表示使用延迟绑定,当函数第一次被调用到时才进行绑定,即PLT机制;而RTLD_NOW表示当模块被加载时即完成所有函数的绑定工作,如果有任何未定义符号的引用导致绑定工作没法完成,那么dlopen()会返回错误。上面的两种绑定方式必须选其一。另外还有一个常量RTLD_GLOBAL可以跟上面的两者中的任意一个仪器使用(通过常量的“或”操作),它表示将被加载模块的全局符号合并到进程的全局符号表中,使得以后加载的模块可以使用这些符号。(使用 RTLD_NOW会导致加载动态库的速度变慢)

    dlopen的返回值是被加载模块的句柄,这个句柄dlsym或者dlclose会用到。如果加载模块失败,则返回NULL。如果模块已经通过dlopen被加载过了,那么返回的是同一个句柄。另外如果被加载模块有依赖关系,比如模块A依赖于模块B,那么程序员需手工加载被依赖的模块,比如先加载B,再加载A。

 

2.dlsym()

    dlsym函数基本上是运行时装载的核心部分,我们可以通过这个函数找到所需要的符号。它的定义如下:

void * dlsym(void *handle, char *symbol);

    第一个参数是有dlopen返回的动态库句柄;第二个参数即所要查找的符号的名字,一个以“\0”结尾的C字符串。如果dlsym()找到了相应的符号,则返回该符号的值;没找到相应的符号,则返回NULL。dlsym()返回值对于不同类型的符号意义是不同的。如果查找的符号是个函数,那么它返回函数的地址;如果是个变量,它返回变量的地址;如果这个符号是个常量,那么它返回的是该常量的值。这里有一个问题是:如果常量的值刚好是NULL或者0呢?我们该如何判断dlsym()是否找到了该符号呢?通过dlerror()函数可以确认,如果符号找到了,那么dlerror()返回NULL,如果没找到,dlerror()就会返回相应的错误信息。

 

3.dlerror()

    在调用dlopen()、dlsym()、dlclose()以后,我们都可以调用dlerror()函数来判断上一次调用是否成功。dlerror()的返回值类型是char*,如果返回NULL,则表示上一次调用成功;如果不是,则返回相应的错误信息。

 

4.dlclose()

    dlclose()的作用跟dlopen()刚好相反,它的作用是将一个已经加载的模块卸载。系统会维持一个加载引用计数器,每次使用dlopen()加载某个模块时,相应的计数器加一;每次使用dlclose()卸载某个模块时,相应的计数器减一。只有当计数器值减到0时,模块才被真正地卸载掉,相应的符号从符号表中去除。

你可能感兴趣的:(动态加载,dlopen,dlclose,dlsym,dlerror,Linux)