在不少服务器应用中,会采用插件化或者模块化的体系实现具体的业务功能,比如mysql支持插件化体系,nginx采用模块化体系。总得来说,很多时候,因为扩展性,系统会采用动态加载so的方式扩展业务功能,而主框架不需要每次新增功能就不得不重新编译,很多时候,对于二进制发行的应用来说,不可能这么做。
最近抽空研究了下,Linux提供了一套dlXXX的API来动态装载库。
- dlopen,打开一个库,并为使用该库做些准备。
dlopen打开模式如下:
RTLD_LAZY 暂缓决定,等有需要时再解出符号
RTLD_NOW 立即决定,返回前解除所有未决定的符号。
- dlsym,在打开的库中查找符号的值。
- dlclose,关闭库。
- dlerror,返回一个描述最后一次调用dlopen、dlsym,或dlclose的错误信息的字符串。
C/C++语言用户需要包含头文件dlfcn.h(该头文件实际上是c语言编写的,不是c++,所以下面会提到,so中的函数需要增加链接指示extern "C",否则在加载so的时候,会提示找不到符号表Undefined symbols when loading shared library with dlopen())才能使用上述API。glibc还增加了两个POSIX标准中没有的API:
- dladdr,从函数指针解析符号名称和所在的文件。
- dlvsym,与dlsym类似,只是多了一个版本字符串参数。
在Linux上,使用动态链接的主应用程序需要和库libdl.so一起链接,也就是使用选项-ldl。首先看个例子:
dynso.cpp
使用g++ -fpic -shared选项编译。因为__register只有声明,没有定义,因此正常编译的时候会报undefined reference to `__register(__test*)'。要解决这个问题,就要给链接器加上参数-E将主程序中所有全局符号放到动态符号表中即可, 由于生成可执行文件一般都是gcc直接生成, 因此可以使用gcc -Wl,-E来将-E参数传给ld来完成创建一个可以被动态链接的可执行文件,参见下面主程序的编译部分。TODO待解决
参考http://www.cppblog.com/markqian86/archive/2017/09/27/215269.html。
再看主程序:
使用g++ -ldl -rdynamic编译。
-rdynamic类似于-g选项,只不过相比-g选项, -rdynamic 却是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。
当一个库通过dlopen()动态打开或以共享库的形式打开时,如果_init在该库中存在且被输出出来,则_init函数(如果使用g++编译,需要使用extern "C"使得对外可见)会被调用。如果一个库通过dlclose()动态关闭或因为没有应用程序引用其符号而被卸载时,_fini函数会在库卸载前被调用。当使用你自己的_init和_fini函数时,需要注意不要与系统启动文件一起链接。可以使用GCC选项 -nostartfiles 做到这一点。
上面是动态调用so中函数的方式,还有一种典型的用法是主框架定义了接口,具体的so实现特定的接口以扩展主程序的功能,也就是插件化体系。
动态模块包含c++ 11特性
g++ -std=c++11 -g -I./include -fPIC -shared -nostartfiles -o libdynso_cpp.so dynso.cpp
/tmp/ccoMSNmQ.o: In function `__static_initialization_and_destruction_0(int, int)':
/usr/include/c++/4.8.2/iostream:74: undefined reference to `__dso_handle'
/usr/bin/ld: /tmp/ccoMSNmQ.o: relocation R_X86_64_PC32 against undefined hidden symbol `__dso_handle' can not be used when making a shared object
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
make: *** [all] Error 1
真正解决方法:在源文件前面加上 extern "C"{ void * __dso_handle = 0 ;}
如果不生效,那就去掉 -nostartfiles,原因待查,参见https://gcc.gnu.org/onlinedocs/gcc/gcc-command-options/options-for-linking.html。
如果包含c++ 14特性,还得加个编译选项-fno-use-cxa-atexit。如下:
g++ -std=c++14 -g -Wall -fno-use-cxa-atexit -I./include -fPIC -shared -nostartfiles
参考:
http://www.tuicool.com/articles/EvIzUn
c函数本身调用的代价
ld本身是不能基于c主程序链接c++ object文件的,原因可以参见:https://jingyan.baidu.com/article/3c343ff7e9f1840d377963ea.html。
https://www.jb51.net/article/101744.htm
http://blog.sina.com.cn/s/blog_664ffc6b01014ctj.html(特别感谢,运行的时候报找不到符号表就是通过该帖子的提示解决)
http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html
https://linux.die.net/man/3/dladdr
https://www.169it.com/tech-qa-linux/article-11063969113097593705.html c++中_init未被调用