这两天做的工作当中,遇到了这样一个需求。在Linux环境下,要为一个daemon程序的一个动态库进行升级,不不打断这个daemon的运行。这个动态 库的函数会被daemon的多个线程调用。在升级时,对于已经使用了这个动态库的线程要毫无影响,直到这样的线程再次调用动态库的API时,再使用新的动 态库。换句话说,在升级时,新旧两个动态库可以同时被这个daemon的线程调用。
为了实现这个功能,毫无疑问,不能让daemon自动加载动态库,而是要通过使用dlopen和dlsym来加载动态库,以及获得相应的 symbol。对于动态库的句柄和指向其symbol的指针,需要使用引用计数,来检查是否还有线程在引用它们。当更新动态库时,对于其句柄和那些指针使 用RCU机制进行释放。这样就保证了更新动态库时,已被引用的旧的动态库资源仍然可以继续使用。直到所有的资源都被解引用时,资源才会被释放。
为了验证自己的思路,我写了一个简单的程序来测试同时加载两个含有同样symbol的动态库,是否可以完全正常工作。下面是这个程序的简单示意。
void *lib_handle1 = dlopen("libmy_lib.so", RTLD_NOW);
if (NULL == lib_handle1) {
//error handler
}
void *fp1 = dlsym(lib_handle1, "my_api");
if (NULL == fp1) {
//error handler
}
void *lib_handle2 = dlopen("libmy_new_lib.so", RTLD_NOW);
if (NULL
== lib_handle2) {
//error handler
}
void *fp2 = dlsym(lib_handle2, "my_api");
if (NULL == fp2) {
//error handler
}
首先要说明的是,第二次调用dlopen时,第二个动态的名字必须与第一个不同,否则程序会认为该动态库已经加载,那么第二个dlopen就会直接返回lib_handle1的地址。这在man手册中也有清晰的说明。
我这里的第二个动态库的名字已经与第一个有了区别。第二次dlopen返回了一个新的加载地址,但是第二个dlsym却返回了与fp1相同的地址。也就是说,程序根本没有取得libmy_new_lib.so中的my_api的地址。
我上网搜索了一下,没有找到准确的解释。但是有人说因为symbol也已经被加载了,所以同名的symbol不会被第二次加载。
于是,我想将libmy_new_lib.so中的my_api改名——当然不能通过修改源代码的方式。于是我使用objcopy来修改它的名 字。具体命令是objcopy --redefine-sym my_api=my_new_api libmy_lib.so libmy_new_lib.so。修改后使用objdump查看,发现已经成功修改了。
可是在测试中,却发现dlsym失败,报告找不到my_new_api。于是我又开始研究这个问题,最终发现objcopy的修改symbol 名字的功能不支持动态库,可man手册上并没有写到。大家可以上网搜到,甚至可以找到objcopy的开发mailist中,有人就已经发现了这个问题, 但是不知道为什么一直没有解决。
绕了半天,又回到了原点。最后又思索了半天,发现了最终的解决方案。在编译参数中使用“-fPIC”即可解决这个问题。在man手册中,对于-fPIC的解释如下:
-fPIC
If supported for the target machine, emit position-independent code, suitable for dynamic linking and
avoiding any limit on the size of the global offset table. This option makes a difference on the m68k,
PowerPC and SPARC.
Position-independent code requires special support, and therefore works only on certain machines.
When this flag is set, the macros "__pic__" and "__PIC__" are defined to 2.
当使用-fPIC时,生成的代码时与位置无关的代码。那么在加载动态库时,就不会加载到固定位置,那么每个symbol都可以加载成功。当没有-fPIC时,程序会发现该位置已有对应的symbol,自然就不会二次加载了。
这么一个小问题也耽误了我近一个下午的时间。原因有二:一是对gcc的编译选项不够熟悉;二是被objcopy的bug给耽误了不少时间。