Renderscript

RenderScript 是用于在 Android 上以高性能运行计算密集型任务的框架。RenderScript 专为数据并行计算而设计,不过串行工作负载也可以从中受益。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 无法实现。

有关详情,请参阅 developer.android.com 上的 Renderscript

设计

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

供应商可使用的 RenderScript 库

本部分列出了向供应商代码开放且可与之关联的 RenderScript 库(称为供应商 NDK,适用于 Same-Process HAL 或 VNDK-SP)。此外,本部分还详细介绍了虽然与 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 及更高版本的设备上,除 RenderScript 之外的所有 Same-Process HAL (SP-HA) 都会在链接器命名空间 sphal 中加载。RenderScript 将被加载到 RenderScript 专用的命名空间 rs 中,该位置对 RenderScript 库的限制稍微宽松些。由于 RS 实现需要加载编译后的位码,因此系统会将 /data/*/*.so 添加到 rs 命名空间的路径中(不允许其他 SP-HAL 从该数据分区加载库)。

此外,rs 命名空间所允许的库要比其他命名空间提供的库多。libmediandk.solibft2.so 将可用于 rs 命名空间,因为 libRS_internal.so 有一个对这些库的内部依赖项。

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

加载驱动程序

CPU 备用路径

根据在创建 RS 上下文时是否存在 RS_CONTEXT_LOW_LATENCY 位,可以选择 CPU 或 GPU 路径。选择 CPU 路径时,系统会直接从默认链接器命名空间(提供了 RS 库的平台版本)对 libRS_internal.so(RS 框架的主要实现)执行 dlopen 处理。

采用 CPU 备用路径时,系统根本不会使用来自供应商的 RS HAL 实现,而是通过空的 mVendorDriverName 创建一个 RsContext 对象。系统会对 libRSDriver.so 执行 dlopen 处理(默认情况下),且驱动程序库会从 default 名称空间加载,因为调用程序 (libRS_internal.so) 也会在 default 命名空间中加载。

图 4. 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 的链接器命名空间中对 libRS_internal.so 执行 dlopen 处理。

供应商可以通过设置编译时标记 OVERRIDE_RS_DRIVER 来提供自己的 RS 驱动程序,该标记嵌入在 RS HAL 实现 (hardware/interfaces/renderscript/1.0/default/Context.cpp) 中。然后,系统会在 GPU 路径的 RS 上下文中对该驱动程序名称执行 dlopen 处理。

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

图 5. 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),动态链接器应该从 rs 命名空间加载 libRS_internal.so。借助此配置,对 libRS_internal.so 进行简单的 dlopen() 调用就足以实现命名空间转换。

加载 bcc 插件

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

  • 无法与框架专用库(如 libLLVM.so)相关联。
  • 只能与面向供应商的 VNDK-SP 库相关联。

此限制是通过使用 android_sphal_load_library() 函数将 bcc plugin 加载到 sphal 命名空间来强制实施的。在之前的 Android 版本中,插件名称是使用 -load 选项指定的,而库是由 libLLVM.so 使用简单的 dlopen() 加载的。在 Android 8.0 及更高版本中,该名称在 -plugin 选项中指定,而库则直接由 bcc 本身加载。此选项可使开放源代码 LLVM 项目支持非 Android 专用路径。

图 6. 加载 bcc 插件 - Android 7.x 及更低版本


图 7. 加载 bcc 插件 - Android 8.0 及更高版本

ld.mc 的搜索路径

在执行 ld.mc 时,系统会将某些 RS 运行时库作为输入提供给链接器。来自应用的 RS 位码会与运行时库相关联,当转换后的位码被加载到某个应用进程中时,会再次与运行时库动态关联。

运行时库包括:

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

在将编译后的位码加载到应用进程中时,请提供与 ld.mc 所使用的完全相同的库。否则,编译后的位码可能无法找到它被关联时可供使用的那个符号。

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

SELinux 政策

由于 Android 8.0 及更高版本中的 SELinux 政策发生了变化,您在 neverallows 分区中标记额外的文件时必须遵循特定规则(通过 vendor 强制实施):

  • vendor_file 必须是 vendor 分区中所有文件的默认标签。平台政策要求使用此标签来访问直通式 HAL 实现。
  • 通过供应商 SEPolicy 在 vendor 分区中添加的所有新 exec_types 均必须具有 vendor_file_type 属性。这一规则将通过 neverallows 强制实施。
  • 为了避免与将来的平台/框架更新发生冲突,请避免在 vendor 分区中标记除 exec_types 之外的文件。
  • AOSP 标识的 Same-Process HAL 的所有库依赖项均必须标记为 same_process_hal_file

要详细了解 SELinux 政策,请参阅 Android 中的安全增强型 Linux

位码的 ABI 兼容性

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

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

对于会影响位码编译/关联的 HAL 大更改 (HAL 2.0),RS 框架应选择不加载供应商提供的 GPU 驱动程序,而是使用 CPU 或 Vulkan 路径以实现加速。

RenderScript 位码的使用发生在以下三个阶段:

阶段 详细信息
编译
  • bcc 的输入位码 (.bc) 的格式必须是 LLVM 3.2,且 bcc 必须向后兼容现有的(旧版)应用。
  • 不过,.bc 中的元数据可能会发生变化(可能会有新的运行时函数,例如分配设置器和获取器、数学函数等)。部分运行时函数位于 libclcore.bc 中,部分位于 LibRSDriver 或供应商同类驱动程序中。
  • 对于新运行时函数或重大元数据更改,必须递增位码 API 级别。HAL 版本也必须递增,否则供应商驱动程序将无法使用它。
  • 供应商可能有自己的编译器,不过针对 bcc 的总结/要求也适用于这些编译器。
链接
  • 编译后的 .o 将与供应商驱动程序相关联,例如 libRSDriver_foo.solibcompiler_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 plugin 调用系统 bcc:/system/bin/bcc;此插件不能依赖面向供应商的 RenderScript 库列表中未包含的任何系统库。

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

此外,供应商还需要做出以下更改:

图 8. 供应商驱动程序更改
  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.te:
type renderscript_exec, exec_type, file_type;

device/vendor_foo/device_bar/sepolicy/app.te:
allow appdomain renderscript_exec:file { read open getattr execute execute_no_trans };

device/vendor_foo/device_bar/sepolicy/file_contexts:
/vendor/bin/bcc                    u:object_r:renderscript_exec: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 路径。