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

Mar*_*ing 6 linux binding simd shared-libraries dlopen

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

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

一种想法是将executable哪些调用function链接到libloader.so不直接实现的链接function.相反,它根据cpuflags 解析(绑定?)来自另一个加载库的符号,例如libimpl_sse2.so,libimpl_avx2.so依此类推.

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

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

Jas*_*n R 5

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


实现您想要的一种方法是(ab)使用DT_SONAME共享库的 ELF 标头中的字段。这可用于更改动态加载器 ( ld-linux-so*) 在运行时加载的文件的名称,以便解析共享库依赖项。这最好用一个例子来解释。假设我libtest.so使用以下命令行编译共享库:

g++ test.cc -shared -o libtest.so -Wl,-soname,libtest_dispatch.so
Run Code Online (Sandbox Code Playgroud)

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

g++ testprog.cc -o test -ltest
Run Code Online (Sandbox Code Playgroud)

让我们检查生成的应用程序二进制文件的运行时库依赖项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)
Run Code Online (Sandbox Code Playgroud)

请注意,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
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 将应用程序链接到您的库时,将其链接到 "vanilla" libtest.so,即已DT_SONAME覆盖其指向调度程序库的那个。这使得分派对于使用您的库的任何应用程序作者来说基本上是透明的。

这应该在 Linux 上按上述方式工作。在 Mac OS 上,共享库具有类似于DT_SONAMEELF 共享库中使用的“安装名称” ,因此可以使用与上述非常相似的过程。我不确定是否可以在 Windows 上使用类似的东西。

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