如何在Linux上基于CPU能力进行运行时绑定

是否有可能有一个Linux库(例如“libloader.so”)加载另一个库来解决任何外部符号?

我有一大堆的代码被有条件编译为支持SIMD级别(SSE2,AVX,AVX2)。 如果构build平台与运行时平台相同,此工作正常。 但它妨碍了不同处理器世代的重用。

一个想法是有一个调用function链接到libloader.so executable ,它不直接实现function 。 相反,它parsing(绑定?)该符号从另一个加载的库,例如libimpl_sse2.solibimpl_avx2.so等取决于cpuflags。

有数百个函数需要以这种方式进行dynamic绑定,因此更改声明或调用代码是不现实的。 程序链接相当容易改变。 运行时环境variables也可以改变,但我不想。

我已经得到尽可能做一个可执行文件,并通过ld标志 – --unresolved-symbols=ignore-allbuild立并开始与未parsing的外部符号(UES)。 但是随后的impl lib的加载不会将UES函数的值从NULL改变。

编辑:我后来发现,下面描述的技术将只在有限的情况下工作。 具体而言,您的共享库只能包含函数,而不包含任何全局变量。 如果库中存在全局变量,那么最终会出现运行时动态链接器错误。 发生这种情况是因为全局变量在调用共享库构造函数之前被重新定位 。 因此,在这里描述的调度方案有机会运行之前,链接器需要尽早解决这些引用。


实现你想要的一种方法是(AB)在共享库的ELF头中使用DT_SONAME字段。 这可以用来改变动态加载器( ld-linux-so* )在运行时加载的文件的名称,以解决共享库依赖性问题。 这个最好用一个例子来解释。 说我用下面的命令行编译共享库libtest.so

 g++ test.cc -shared -o libtest.so -Wl,-soname,libtest_dispatch.so 

这将创建一个文件名为libtest.so的共享库,但是其DT_SONAME字段被设置为libtest_dispatch.so 。 让我们看看当我们连接一个程序时会发生什么:

 g++ testprog.cc -o test -ltest 

我们来检查运行时库依赖关系的结果应用程序二进制test

 > ldd test linux-vdso.so.1 => (0x00007fffcc5fe000) libtest_dispatch.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd1e4a55000) /lib64/ld-linux-x86-64.so.2 (0x00007fd1e4e4f000) 

请注意,代替查找libtest.so ,动态加载器不是要加载libtest_dispatch.so 。 你可以利用这个来实现你想要的调度功能。 以下是我将如何做到这一点:

  • 创建您的共享库的各种版本。 我认为有一些“通用”版本可以随时使用,其他优化版本可以在运行时使用。 我会用“普通”库名称libtest.so来命名通用版本,然后命名其他选项(例如libtest_sse2.solibtest_avx.so等)。

  • 链接库的通用版本时,请将其DT_SONAME重写为其他内容,如libtest_dispatch.so

  • 创建一个名为libtest_dispatch.so的调度程序库。 当调度程序在应用程序启动时加载时,它负责加载适当的库实现。 下面是libtest_dispatch.so实现的伪代码:

     #include <dlfcn.h> #include <stdlib.h> // the __attribute__ ensures that this function is called when the library is loaded __attribute__((constructor)) void init() { // manually load the appropriate shared library based upon what the CPU supports // at runtime if (avx_is_available) dlopen("libtest_avx.so", RTLD_NOW | RTLD_GLOBAL); else if (sse2_is_available) dlopen("libtest_sse2.so", RTLD_NOW | RTLD_GLOBAL); else dlopen("libtest.so", RTLD_NOW | RTLD_GLOBAL); // NOTE: this is just an example; you should check the return values from // dlopen() above and handle errors accordingly } 
  • 在将应用程序链接到库时,将其与“vanilla” libtest.so链接,将其DT_SONAME重写为指向调度程序库。 这使得调度对于任何使用你的库的应用程序作者来说基本上是透明的。

这应该像Linux上面描述的那样工作。 在Mac OS上,共享库具有类似于ELF共享库中使用的DT_SONAME的“安装名称”,因此可以使用与上述类似的过程。 我不确定是否可以在Windows上使用类似的东西。

注意:上面提到了一个重要的假设:库的不同实现之间的ABI兼容性。 也就是说,你的库应该被设计成在运行时使用一个优化的版本(例如libtest_avx.so )来链接最通用的版本是安全的。