如何规避dlopen()缓存?

cho*_*ger 1 c linker posix dlopen

根据其手册页,dlopen()不会加载相同的库两次:

如果使用dlopen()再次加载相同的共享对象,则返回相同的对象句柄.动态链接器维护对象句柄的引用计数,因此动态加载的共享对象在释放dlclose()之前不会被释放,因为dlopen()已经成功执行了多次.任何初始化返回(见下文)都只调用一次.但是,后续的dlopen()调用使用RTLD_NOW加载相同的共享对象可能会强制对早先使用RTLD_LAZY加载的共享对象进行符号解析.

(强调我的).

但实际上是什么决定了共享对象的身份?我试着查看代码,但没有走得太远.是吗:

  • 某种形式的规范化路径名称(例如realpath?)
  • inode?
  • 图书馆的内容?

我很确定我可以排除最后一点,因为实际的文件系统副本会产生两个不同的句柄.

解释这个问题背后的动机:我正在使用一些具有静态全局变量的代码.我需要该代码的多个实例以线程安全的方式运行.我目前的方法是编译并将所述代码链接到动态库并多次加载该库.通过一些链接器魔术,它似乎创建了全局变量的几个副本,并将每个库中的访问权限解析为它自己的副本.唯一的问题是我的原型复制生成的库n次n次并发使用.这不仅有些难看,而且我还怀疑它可能会在不同的平台上破裂.

那么dlopen()根据POSIX标准的确切行为是什么?

编辑:因为它出现在评论和答案中,所以不能重构代码绝对不是一种选择.这将涉及数月甚至数年的工作,并可能首先牺牲使用代码的所有好处.有存在,可能在一个更清洁的方式解决这一问题的正在进行的研究项目,但它是实际的研究,并可能失败.我现在需要一个解决方案.

edit2:因为人们似乎仍然不相信usecase实际上是有效的.我正在研究一种纯函数式语言,它应该嵌入到更大的C/C++应用程序中.因为我需要一个带有垃圾收集器的原型,一个经验证的类型检查器,以及ASAP的合理性能,我使用OCaml作为中间代码.现在,我编译一个源模块放入OCaml的模块,链接生成的目标代码(包括启动等)到共享库与OCaml的运行时间和dlopen()的共享库.每个.so都有自己的运行时副本,包括几个全局变量(例如指向年轻代的指针),也就是说,应该是完全正常的.该库正好暴露了两个函数:初始化程序和单个导出,它执行原始模块要执行的任何操作.不会导出/共享OCaml运行时的符号.当我加载库时,其内部符号按预期重新定位,我现在唯一的问题是我实际上需要在运行时为每个作业实例复制.so文件.

关于线程局部存储:这实际上是一个有趣的想法,因为对运行时的修改确实相当简单.但问题是OCaml编译器生成的机器代码,因为它不能发出tls符号的加载指令(但是?).

Art*_*Art 5

POSIX说:

即使在引用该文件时多次调用dlopen(),并且即使使用不同的路径名来引用该文件,也只会将一个目标文件的副本带入地址空间.

所以答案是"inode".复制库文件"应该工作",但硬链接不会.除了.因为它们将暴露相同的全局符号,并且当发生这种情况时,所有(可移植性)投注都是关闭的.您处于弱定义行为的中间,这些行为是通过错误修复而不是良好的设计而发展的.

当你在一个洞里时,不要深入挖掘.添加额外的可怕黑客以使基本上破坏的库工作的方法只会导致额外的破坏.只花几个小时来修复库不使用全局变量而不是花费数天时间来破解动态链接(最好是不可移植的).