Linux下C++20加载30个同共享库物理副本的问题解决
背景
我们有一个用于设备通信的C++库,单个库仅能与一台设备通信,且初始化时会生成日志文件。现有新项目需同时与30台设备通信。
问题
新项目基于C++20开发,运行于Linux系统。我们制作了30个无硬链接、命名依次为mylib_0.so、mylib_1.so等的库副本,希望加载全部30个实例。
参考同类问题的解决方案,使用dlmopen(LM_ID_NEWLM, "/path/to/library.so", RTLD_NOW)加载,但加载到第10个时失败,dlmopen返回NULL,且仅生成10个日志文件。尝试dlopen加RTLD_LOCAL标志时最初调用失败,修正为dlopen("/path/to/library.so", RTLD_NOW | RTLD_LOCAL)后,调用30次仅生成1个包含30条记录的日志,推测dlopen识别到库已加载而复用。
错误信息
使用dlerror获取到第10次dlmopen调用的错误:
/lib/x86_64-linux-gnu/libc.so.6: cannot allocate memory in static TLS block
项目架构
项目包含库加载器类,由主控模块创建实例;未给每个加载器分配独立线程,因库自身已使用线程,需节省线程资源。
疑问
如何实现加载30个及以上同共享库的物理副本?能否修改.so文件使dlopen认为它们是不同的库?
1. 突破静态TLS配额限制(解决dlmopen加载失败)
你遇到的错误源于Linux默认限制了进程可创建的静态TLS块数量(通常为10个),LM_ID_NEWLM创建的每个命名空间都会占用一个配额。解决方式有两种:
- 调整系统资源限制:临时执行
ulimit -l unlimited放开内存锁定限制,或者在/etc/security/limits.conf中添加以下配置并重启系统:
这种方式无需修改代码,但依赖系统配置权限。* soft memlock unlimited * hard memlock unlimited - 改造库的TLS实现:如果能修改原库代码,将静态TLS变量(如
thread_local声明的变量)替换为动态TLS实现——使用pthread_key_create、pthread_setspecific和pthread_getspecific来管理线程本地数据。动态TLS不占用静态TLS块配额,可无限制加载库实例。
2. 修改库标识让dlopen视为独立库(解决复用问题)
dlopen通过库的SONAME(共享对象名称)判断是否已加载相同库,因此可以为每个库副本设置唯一的SONAME:
- 使用
patchelf工具批量修改每个副本的SONAME:
修改完成后,每个库副本的SONAME唯一,for i in {0..29}; do patchelf --set-soname mylib_${i}.so mylib_${i}.so donedlopen("/path/to/mylib_N.so", RTLD_NOW | RTLD_LOCAL)会将它们视为独立库,加载30个实例后每个都会生成单独的日志文件。
3. 进程隔离替代方案
如果上述方法无法实施,可考虑为每个设备启动独立子进程,每个子进程加载一个库实例。这种方式彻底隔离了每个库的运行环境,避免TLS和库复用问题,但需要处理进程间通信,适合对资源开销容忍度较高的场景。
内容的提问来源于stack exchange,提问作者pSquared

