如何根据Linux上的CPU功能进行运行时绑定

时间:2015-04-08 19:56:07

标签: linux binding shared-libraries simd dlopen

是否有可能拥有一个linux库(例如" libloader.so")加载另一个库来解析任何外部符号?

我已经获得了一大堆代码,这些代码有条件地编译为支持SIMD级别(SSE2,AVX,AVX2)。如果构建平台与运行时平台相同,则此方法可以正常工作。但它阻碍了不同处理器代的重用。

一种想法是让executable调用function链接到libloader.so,但不直接实现function。相反,它解析(绑定?)来自另一个加载库的符号,例如libimpl_sse2.solibimpl_avx2.so等等,具体取决于cpuflags。

有数百个函数需要以这种方式动态绑定,因此更改声明或调用代码是不切实际的。 程序链接很容易改变。运行时环境变量也可以更改,但我不愿意。

我已经通过ld标志--unresolved-symbols=ignore-all创建了一个可执行文件,可以使用未解析的外部符号(UES)构建和启动。但后续加载impl lib并不会将UES函数的值从NULL更改。

1 个答案:

答案 0 :(得分:4)

编辑:我后来发现,下面描述的技术只能在有限的情况下使用。具体来说,您的共享库必须只包含函数,不包含任何全局变量。如果要在其中分配库中的全局变量,则最终会出现运行时动态链接器错误。这发生在because global variables are relocated before shared library constructors are invoked。因此,链接器需要在此处描述的调度方案有机会运行之前尽早解析这些引用。


实现所需内容的一种方法是(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。您可以利用它来实现所需的调度功能。以下是我将如何做到这一点:

  • 创建各种版本的共享库。我假设有一些"泛型"可以始终使用的版本,并在运行时使用其他优化版本。我会用" plain"来命名通用版本。库名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
    }
    
  • 将应用程序与您的图书馆相关联时,请将其链接到&#34; vanilla&#34; libtest.so,重写DT_SONAME以指向调度程序库的DT_SONAME。这使得调度对使用您的库的任何应用程序作者基本上都是透明的。

这应该如上所述在Linux上工作。在Mac OS上,共享库具有&#34;安装名称&#34;这类似于ELF共享库中使用的libtest_avx.so,因此可以使用与上述非常类似的过程。我不确定在Windows上是否可以使用类似的东西。

注意:上面有一个重要的假设:库的各种实现之间的ABI兼容性。也就是说,您的库应该设计为在运行时使用优化版本(例如{{1}})时,在链接时链接最通用的版本是安全的。