RenderScript

RenderScript 是一个在 Android 上以高性能运行计算密集型任务的框架。它专为与数据并行计算配合使用而设计,尽管串行工作负载也可能受益。RenderScript 运行时在设备上可用的处理器(如多核 CPU 和 GPU)之间并行化工作,使开发者能够专注于表达算法,而不是调度工作。RenderScript 对于执行图像处理、计算摄影或计算机视觉的应用尤其有用。

运行 Android 8.0 及更高版本的设备使用以下 RenderScript 框架和供应商 HAL

图 1. 供应商代码链接到内部库。

与 Android 7.x 及更低版本中的 RenderScript 的区别包括:

  • 进程中 RenderScript 内部库的两个实例。一组用于 CPU 回退路径,直接来自 /system/lib;另一组用于 GPU 路径,来自 /system/lib/vndk-sp
  • /system/lib 中的 RS 内部库作为平台的一部分构建,并在 system.img 升级时更新。但是,/system/lib/vndk-sp 中的库是为供应商构建的,并且在 system.img 升级时不会更新(虽然它们可以为安全修复程序更新,但其 ABI 保持不变)。
  • 供应商代码(RS HAL、RS 驱动程序和 bcc plugin)链接到位于 /system/lib/vndk-sp 的 RenderScript 内部库。它们无法链接到 /system/lib 中的库,因为该目录中的库是为平台构建的,因此可能与供应商代码不兼容(即,可能会删除符号)。这样做会使仅框架 OTA 成为不可能。

设计

以下部分详细介绍了 Android 8.0 及更高版本中的 RenderScript 设计。

供应商可用的 RenderScript 库

本部分列出了供应商代码可用的 RenderScript 库(称为同进程 HAL 或 VNDK-SP 的供应商 NDK)以及可以链接到的库。它还详细介绍了与 RenderScript 无关但同样提供给供应商代码的其他库。

虽然以下库列表在不同的 Android 版本之间可能有所不同,但对于特定的 Android 版本来说是不可变的;如需获取可用库的最新列表,请参阅 /system/etc/ld.config.txt

RenderScript 库 非 RenderScript 库
  • android.hardware.graphics.renderscript@1.0.so
  • libRS_internal.so
  • libRSCpuRef.so
  • libblas.so
  • libbcinfo.so
  • libcompiler_rt.so
  • libRSDriver.so
  • libc.so
  • libm.so
  • libdl.so
  • libstdc++.so
  • liblog.so
  • libnativewindow.so
  • libsync.so
  • libvndksupport.so
  • libbase.so
  • libc++.so
  • libcutils.so
  • libutils.so
  • libhardware.so
  • libhidlbase.so
  • libhidltransport.so
  • libhwbinder.so
  • liblzma.so
  • libz.so
  • libEGL.so
  • libGLESv1_CM.so
  • libGLESv2.so

链接器命名空间配置

运行时使用链接器命名空间强制执行阻止 VNDK-SP 中未列出的库被供应商代码使用的链接限制。(如需了解详情,请参阅 VNDK 设计演示文稿。)

在运行 Android 8.0 及更高版本的设备上,所有同进程 HAL (SP-HAL)(RenderScript 除外)都在链接器命名空间 sphal 内加载。RenderScript 加载到 RenderScript 特定的命名空间 rs 中,该位置允许对 RenderScript 库进行稍微宽松的强制执行。由于 RS 实现需要加载编译后的位代码,因此 /data/*/*.so 会添加到 rs 命名空间的路径中(不允许其他 SP-HAL 从数据分区加载库)。

此外,rs 命名空间允许的库比其他命名空间提供的库更多。libmediandk.solibft2.sors 命名空间公开,因为 libRS_internal.so 对这些库具有内部依赖性。

图 2. 链接器的命名空间配置。

加载驱动程序

CPU 回退路径

根据创建 RS 上下文时是否存在 RS_CONTEXT_LOW_LATENCY 位,将选择 CPU 或 GPU 路径。当选择 CPU 路径时,libRS_internal.so(RS 框架的主要实现)将直接从提供平台版本 RS 库的默认链接器命名空间中 dlopen

当采用 CPU 回退路径时,完全不会使用供应商提供的 RS HAL 实现,并且将使用空 mVendorDriverName 创建 RsContext 对象。libRSDriver.so 默认情况下会 dlopen,并且驱动程序库会从 default 命名空间加载,因为调用方 (libRS_internal.so) 也加载到 default 命名空间中。

图 3. CPU 回退路径。

GPU 路径

对于 GPU 路径,libRS_internal.so 的加载方式有所不同。首先,libRS.so 使用 android.hardware.renderscript@1.0.so(及其底层的 libhidltransport.so)将 android.hardware.renderscript@1.0-impl.so(RS HAL 的供应商实现)加载到名为 sphal 的不同链接器命名空间中。然后,RS HAL 在另一个名为 rs 的链接器命名空间中 dlopen libRS_internal.so

供应商可以通过设置构建时标志 OVERRIDE_RS_DRIVER 来提供自己的 RS 驱动程序,该标志嵌入到 RS HAL 实现 (hardware/interfaces/renderscript/1.0/default/Context.cpp) 中。然后,对于 GPU 路径的 RS 上下文,将 dlopen 此驱动程序名称。

RsContext 对象的创建委托给 RS HAL 实现。HAL 使用驱动程序的名称作为参数,回调到使用 rsContextCreateVendor() 函数的 RS 框架。然后,当 RsContext 初始化时,RS 框架会加载指定的驱动程序。在这种情况下,驱动程序库会加载到 rs 命名空间中,因为 RsContext 对象是在 rs 命名空间内创建的,并且 /vendor/lib 位于该命名空间的搜索路径中。

图 4. GPU 回退路径。

当从 default 命名空间转换到 sphal 命名空间时,libhidltransport.so 使用 android_load_sphal_library() 函数显式地命令动态链接器从 sphal 命名空间加载 -impl.so 库。

当从 sphal 命名空间转换到 rs 命名空间时,加载是通过 /system/etc/ld.config.txt 中的以下行间接完成的

namespace.sphal.link.rs.shared_libs = libRS_internal.so

此行指定当无法从 sphal 命名空间(始终如此,因为 sphal 命名空间不搜索 libRS_internal.so 所在的 /system/lib/vndk-sp)找到/加载 lib 时,动态链接器应从 rs 命名空间加载 libRS_internal.so。通过此配置,对 libRS_internal.so 的简单 dlopen() 调用足以完成命名空间转换。

加载 bcc 插件

bcc plugin 是供应商提供的库,加载到 bcc 编译器中。由于 bcc/system/bin 目录中的系统进程,因此 bcc plugin 库可以被视为 SP-HAL(即,可以直接加载到系统进程中而无需绑定的供应商 HAL)。作为 SP-HAL,bcc-plugin

  • 无法链接到仅框架库,例如 libLLVM.so
  • 只能链接到供应商可用的 VNDK-SP 库。

此限制通过使用 android_sphal_load_library() 函数将 bcc plugin 加载到 sphal 命名空间来强制执行。在以前版本的 Android 中,插件名称是使用 -load 选项指定的,并且 lib 是由 libLLVM.so 使用简单的 dlopen() 加载的。在 Android 8.0 及更高版本中,这在 -plugin 选项中指定,并且 lib 由 bcc 本身直接加载。此选项启用了一个非 Android 特定的开源 LLVM 项目路径。

图 5. 加载 bcc 插件,Android 7.x 及更低版本。



图 6. 加载 bcc 插件,Android 8.0 及更高版本。

ld.mc 的搜索路径

执行 ld.mc 时,一些 RS 运行时库作为输入提供给链接器。来自应用的 RS 位代码与运行时库链接,并且当转换后的位代码加载到应用进程中时,运行时库再次从转换后的位代码动态链接。

运行时库包括

  • libcompiler_rt.so
  • libm.so
  • libc.so
  • RS 驱动程序(libRSDriver.soOVERRIDE_RS_DRIVER

将编译后的位代码加载到应用进程时,请提供与 ld.mc 使用的库完全相同的库。否则,编译后的位代码可能找不到在链接时可用的符号。

为此,当执行 ld.mc 时,RS 框架对运行时库使用不同的搜索路径,具体取决于 RS 框架本身是从 /system/lib 加载还是从 /system/lib/vndk-sp 加载。这可以通过读取 RS 框架库的任意符号的地址并使用 dladdr() 获取映射到该地址的文件路径来确定。

SELinux 策略

由于 Android 8.0 及更高版本中的 SELinux 策略更改,在 vendor 分区中标记其他文件时,您必须遵循特定规则(通过 neverallows 强制执行)

  • vendor_file 必须是 vendor 分区中所有文件的默认标签。平台策略要求这样做才能访问直通 HAL 实现。
  • 通过供应商 SEPolicy 在 vendor 分区中添加的所有新 exec_types 都必须具有 vendor_file_type 属性。这是通过 neverallows 强制执行的。
  • 为避免与未来的平台/框架更新发生冲突,请避免标记 vendor 分区中除 exec_types 之外的文件。
  • AOSP 标识的相同进程 HAL 的所有库依赖项都必须标记为 same_process_hal_file

有关 SELinux 策略的详细信息,请参阅 Android 中的安全增强型 Linux

位代码的 ABI 兼容性

如果没有添加新的 API,这意味着没有 HAL 版本提升,则 RS 框架将继续使用现有的 GPU (HAL 1.0) 驱动程序。

对于不影响位代码的次要 HAL 更改 (HAL 1.1),框架应针对这些新添加的 API 回退到 CPU,并在其他地方继续使用 GPU (HAL 1.0) 驱动程序。

对于影响位代码编译/链接的重大 HAL 更改 (HAL 2.0),RS 框架应选择不加载供应商提供的 GPU 驱动程序,而是使用 CPU 或 Vulkan 路径进行加速。

使用 RenderScript 位代码分为三个阶段

阶段 详细信息
编译
  • bcc 的输入位代码 (.bc) 必须是 LLVM 3.2 位代码格式,并且 bcc 必须向后兼容现有(旧版)应用。
  • 但是,.bc 中的元数据可能会更改(可能会有新的运行时函数,例如,Allocation setter ∓ getter、数学函数等)。部分运行时函数存在于 libclcore.bc 中,部分存在于 LibRSDriver 或供应商等效项中。
  • 新的运行时函数或破坏元数据的更改需要增加位代码 API 级别。由于供应商驱动程序将无法使用它,因此 HAL 版本也必须增加。
  • 供应商可能有自己的编译器,但 bcc 的结论/要求也适用于这些编译器。
链接
  • 编译后的 .o 将与供应商驱动程序(例如,libRSDriver_foo.so)和 libcompiler_rt.so 链接。CPU 路径将与 libRSDriver.so 链接。
  • 如果 .o 需要来自 libRSDriver_foo 的新运行时 API,则必须更新供应商驱动程序以支持它。
  • 某些供应商可能拥有自己的链接器,但 ld.mc 的参数也适用于它们。
加载
  • libRSCpuRef 加载共享对象。如果此接口有更改,则需要 HAL 版本提升。
  • 供应商要么依赖 libRSCpuRef 加载共享对象,要么实现自己的共享对象。

除了 HAL 之外,运行时 API 和导出的符号也是接口。自 Android 7.0 (API 24) 以来,这两个接口均未更改,并且在 Android 8.0 及更高版本中没有立即更改的计划。但是,如果接口确实发生更改,则 HAL 版本也会增加。

供应商实现

Android 8.0 及更高版本需要对 GPU 驱动程序进行一些更改,才能使 GPU 驱动程序正常工作。

驱动程序模块

  • 驱动程序模块不得依赖于 列表中未列出的任何系统库。
  • 驱动程序必须提供自己的 android.hardware.renderscript@1.0-impl_{NAME},或声明默认实现 android.hardware.renderscript@1.0-impl 作为其依赖项。
  • CPU 实现 libRSDriver.so 是如何删除非 VNDK-SP 依赖项的一个很好的示例。

位代码编译器

您可以通过两种方式为供应商驱动程序编译 RenderScript 位代码

  1. /vendor/bin/ 中调用供应商特定的 RenderScript 编译器(GPU 编译的首选方法)。与其他驱动程序模块类似,供应商编译器二进制文件不能依赖于 RenderScript 供应商可用库列表中未列出的任何系统库。
  2. 调用系统 bcc:/system/bin/bcc,带有供应商提供的 bcc plugin;此插件不能依赖于 RenderScript 供应商可用库列表中未列出的任何系统库。

如果供应商 bcc plugin 需要干预 CPU 编译,并且其对 libLLVM.so 的依赖项无法轻易删除,则供应商应将 bcc(以及所有非 LL-NDK 依赖项,包括 libLLVM.solibbcc.so)复制到 /vendor 分区。

此外,供应商需要进行以下更改

图 7. 对供应商驱动程序的更改。

  1. libclcore.bc 复制到 /vendor 分区。这确保 libclcore.bclibLLVM.solibbcc.so 同步。
  2. 通过从 RS HAL 实现设置 RsdCpuScriptImpl::BCC_EXE_PATH 来更改 bcc 可执行文件的路径。

SELinux 策略

SELinux 策略会影响驱动程序和编译器可执行文件。所有驱动程序模块都必须在设备的 file_contexts 中标记为 same_process_hal_file。例如

/vendor/lib(64)?/libRSDriver_EXAMPLE\.so     u:object_r:same_process_hal_file:s0

编译器可执行文件必须能够由应用进程调用,供应商的 bcc 副本 (/vendor/bin/bcc) 也是如此。例如

device/vendor_foo/device_bar/sepolicy/file_contexts:
/vendor/bin/bcc                    u:object_r:same_process_hal_file:s0

旧版设备

旧版设备是满足以下条件的设备

  1. PRODUCT_SHIPPING_API_LEVEL 低于 26。
  2. 未定义 PRODUCT_FULL_TREBLE_OVERRIDE

对于旧版设备,升级到 Android 8.0 及更高版本时,不会强制执行这些限制,这意味着驱动程序可以继续链接到 /system/lib[64] 中的库。但是,由于与 OVERRIDE_RS_DRIVER 相关的架构更改,必须将 android.hardware.renderscript@1.0-impl 安装到 /vendor 分区;否则,RenderScript 运行时将强制回退到 CPU 路径。

有关 Renderscript 弃用的动机的信息,请参阅 Android Developers Blog:Android GPU 计算的未来发展方向。此弃用的资源信息包括以下内容