DLCLOSE并没有真正卸载共享对象,无论它被调用多少次
dlclose doesn't really unload shared object, no matter how many times it is called
我的程序使用dlopen
加载共享对象,然后使用dlclose
卸载它。有时会再次加载这个共享对象。我注意到静态变量没有重新初始化(这对我的程序至关重要),所以我在dlclose
之后添加了一个测试(dlopen
与RTLD_NOLOAD
),看看库是否真的卸载了。果然,它还在内存中。
然后我尝试反复调用dlclose
,直到库真正卸载,但我得到的是一个无限循环。这是我用来检查库是否被卸载的代码:
dlclose(handles[name]);
do {
void *handle = dlopen(filenames[name], RTLD_NOW | RTLD_NOLOAD);
if (!handle)
break;
dlclose(handle);
} while (true);
我的问题是,我的共享对象在dlclose
之后没有被卸载的可能原因是什么,因为我的dlopen
调用是加载它的唯一地方。你能提出一个找出问题根源的行动方案吗?另外,为什么重复调用dlclose
没有效果,它们都在减少引用计数,不是吗?
EDIT:刚刚发现只有当我用gcc编译时才会发生这种情况。使用clang,一切都很好。
POSIX标准实际上不要求dlclose
从地址空间中卸载库:
虽然不需要dlclose()操作来删除结构来自地址空间,也不禁止实现这样做。
来源:The Open Group Base Specifications Issue 6
这意味着除了使句柄无效之外,dlclose
根本不需要做任何事情。
有时卸载也被系统延迟,它只是将库标记为"要删除",并将在稍后的时间实际执行该操作(为了效率或因为现在根本不可能执行该操作)。但是,如果在执行dlopen
之前再次调用它,则该标志将被清除,并且仍然加载的库将被重用。
在某些情况下,系统肯定知道库中的一些符号仍在使用,在这种情况下,它不会从地址空间中卸载它,以避免悬空指针。在某些情况下,系统不能确定它们是否在使用,但它也不可能确定它们没有被使用,安全总比后悔好,在这种情况下,系统永远不会真正从内存中删除该库。
还有其他更模糊的情况取决于操作系统类型,通常也取决于版本。例如,一个常见的Linux问题是,如果你创建了一个使用STB_GNU_UNIQUE符号的库,该库被标记为"不可卸载",因此将永远不会被卸载。看看这里,这里(DF_1_NODELETE
表示不可卸载)和这里。所以它也可以取决于编译器生成的符号。尝试在库中运行readelf -Ws
,并查找标记为UNIQUE
的对象。
一般来说,您不能真正依赖dlclose
来像您期望的那样工作。在实践中,在过去的十年里,我看到它"失败"的次数比"成功"的次数要多(好吧,它从来没有真正失败过,它只是经常没有从内存中卸载库;然而,它按照标准的要求工作)。
这不是您所有问题的答案,但这是可以帮助您避免dlclose
问题的解决方案。这个问题提示了一个关于如何影响重新加载共享库的行为的线索:你可以使用编译器标志-fno-gnu-unique
。
gcc
/g++
的man page:
-fno-gnu-unique
在最近使用GNU汇编器和C库的系统上,c++编译器使用"STB_GNU_UNIQUE"绑定来确保模板静态数据成员和内联函数中的静态局部变量的定义是唯一的,即使存在"RTLD_LOCAL";这是必要的,以避免由两个不同的"RTLD_LOCAL"插件使用的库的问题,这取决于其中一个的定义,因此与另一个不同意符号的绑定。但这会导致"dlclose"在受影响的dso中被忽略;如果你的程序依赖于通过"dlclose"answers"dlopen"重新初始化DSO,你可以使用-fno-gnu-unique。
是否默认使用-fno-gnu-unique
取决于GCC的配置:--disable-gnu-unique-object
默认启用该标志,--enable-gnu-unique-object
禁用该标志。
在Windows中使用等效的ifdef
与WIN或LINUX:
-
LoadLibrary()
=dlopen()
-
FreeLibrary()
=dlclose()
-
GetProcAddress()
=dlsym()
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
if (!handle) {
fputs (dlerror(), stderr);
exit(1);
}
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fputs(error, stderr);
exit(1);
}
printf ("%fn", (*cosine)(2.0));
dlclose(handle);
动态库加载有很多奇怪的地方。依赖于操作系统初始化静态变量充满了问题。你最好要么完全避免它,要么使用一个插件加载器来为你处理所有的特殊情况。
我建议您查看glib模块。Glib提供了一种与平台无关的加载动态库的方式。你可以使用这些回调:
- GModuleCheckInit ()
- GModuleUnload ()
它们可以处理分配和释放任何资源。你可以动态地分配你所需要的,而不是依赖于操作系统以一种可靠的方式为你分配静态数据。
你所需要做的就是在你的动态库中定义这些函数,然后用: 加载和卸载它们。- g_module_open ()
- g_module_close ()
这可以通过用RTLD_LOCAL
调用dlopen
来修复(也许不是在所有情况下)。
我遇到了同样的问题,其中没有调用析构函数,但是如果我使用RTLD_LOCAL
打开共享对象,那么dlclose
的行为如预期的那样,并调用析构函数。
在我的WSL上,我有一个dlclose没有调用库析构函数的问题,我的"direct_refcount"无论我执行多少次dlclose
相同的句柄,在每次加载命令后,lib的值都继续上升。
但是,当我更改dlsym
命令以使用从dlopen返回的句柄而不是使用RTLD_DEFAULT
时,它被修复了,因为我认为我可以遍历作用域以找到该符号。
我不知道有什么不同,但这在我的设置中解决了这个问题。
安装WSL, Ubuntu 20, GCC 9.4, GNU ld 2.34, GLIBC 2.31
- 在c++中多次调用方法
- 如何使用运算符在同一行中多次调用函数
- 在C++中多次调用相同的 lambda
- 函数被多次调用
- getopt_long_only第二次调用时返回 -1
- 在 C++ AMP 数组中复制数据多少次?
- 读取文件在第二次调用时返回INVALID_HANDLE
- C++ 检查结果数组中有多少次数字
- 我如何使它,无论用户用空白字符串按 Enter 多少次,它总是打印"开始"字符串?
- QLibrary 函数在第一次调用时工作缓慢
- 多次调用本机方法时出现致命错误
- 如何计算整数链中使用了多少次数字?
- 当再次触发信号时,从Qt插槽执行的功能被第二次调用时会发生什么?
- 如果函数按值传递并按值返回,将调用复制构造函数多少次
- 复制构造函数被调用了多少次?
- 此函数调用了多少次
- MPI_REDUCE调用的减少操作是多少次
- 复制构造函数在这个C++代码段中被调用了多少次
- DLCLOSE并没有真正卸载共享对象,无论它被调用多少次
- for(int i=0;i<myVector.size();++i) size() 被调用了多少次?