Background
我们有一个用于与设备通信的 C++ 库。 1 个库只能与 1 台设备通信。此库还会在启动时创建日志文件。我们现在有一个新项目,需要同时与 30 台设备通信。
问题
这个新项目是c++20的,将在linux上运行,我们希望能够将其中的 30 个库加载到程序中,所以我制作了这个 .so 文件的 30 个副本,该文件没有硬链接,其名称递增如mylib_0.so
、mylib_1.so
等。
还有其他与此类似的问题
- 装入共享库的多个副本
- 加载共享库的多个副本
都建议使用
dlmopen(LM_ID_NEWLM, "/path/to/library.so", RTLD_NOW);
作为解决方案,但是当我尝试这样做时,它似乎上升到10,然后停止。我知道这一点,因为每个 lib 在正确启动时都会创建自己的日志文件,我只看到创建了 10 个日志文件,而且dlmopen
调用在第10 次调用后返回 null。
我还尝试在dlopen
调用中使用RTLD_LOCAL
标志,但这会使它始终失败。
详
这个新项目的架构是这样的,我们有一个 lib 加载器,它将加载 lib 的所有 API 调用,我们将这个加载器作为一个类和一个可以创建这个加载器对象的实例的主节点。每个加载器没有唯一的线程,因为库已经使用了线程并且加载了这么多线程,我们正在尽可能节省线程资源。
问题
如何将相同 so 文件的副本加载到 30 个甚至更多实例?
更新 1
正如Jakob Stark所建议的那样,我使用了dlerror
并得到了以下消息: 在第 10 次调用中使用dlmopen
时/lib/x86_64-linux-gnu/libc.so.6: cannot allocate memory in static TLS block
。
另外,事实证明我用错了dlopen
,我现在这样做:
dlopen("/path/to/library.so", RTLD_NOW | RTLD_LOCAL);
如果我这样做,dlopen
似乎被调用了 30 次,但我只看到 1 个日志文件,其中包含所有 30 个条目。我认为这里发生的事情是dlopen
认识到 .so 已经加载并返回 true,因此只有一个 .so 加载。
是否有可能使 .so 足够不同,以便dlopen
认为它们是不同的 .so 文件?
此类问题的一个常见解决方案是将库包装在一个单独的帮助程序中,该进程通过 IPC 与主进程通信。 在 Linux 中,您可能可以使用popen
来实现这一点。
在C++端,将表示设备的 C++ 对象替换为实现相同接口的存根类。由于此存根仅包含 IPC 句柄,因此拥有 30 个副本没有问题。
该函数每次dlmopen(LM_ID_NEWLM,...)
将共享库加载到新的命名空间中。这意味着库的每个新实例都会拉入一些标准库,例如libc.so
.我找不到答案,为什么在您的情况下正好是 10 是极限,但手册说:
glibc 实现最多支持 16 个命名空间。
线程本地存储可能出了不同的问题,但我们已经可以得出结论,将dlmopen
与新命名空间一起使用不是这里的方法。
这给我们留下了普通的dlopen()
函数。如果尝试加载同一个库两次,则会得到两次相同的句柄,这通常是一件好事,因为共享库是要共享的。然而,这不是我们在这里想要的。
我能想到的最有希望的解决方案是以不同的名称加载相同的库。符号链接不起作用,因为dlopen()
跟随它们并最终到达完全相同的文件。这里的诀窍是使用硬链接。如果您的库被调用mylib.so
您可以使用ln
创建多个硬链接:
for i in {0..29}; do ln mylib.so mylib$i.so ; done
现在在您的程序中,您可以使用例如
dlopen("mylib0.so", RTLD_NOW);
您将在同一命名空间中获得不同的实例,不同的硬链接几乎没有使用额外的磁盘空间。