Renderscript

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

Android O 设备使用以下 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 插件)与位于 /system/lib/vndk-sp 的 RenderScript 内部库相关联。它们无法与 /system/lib 中的库相关联,因为该目录中的库是面向平台构建的,可能与供应商代码不兼容(即,符号可能会被移除)。如此一来可能会导致仅针对框架的 OTA 无法实现。

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

设计

以下部分详细介绍了 Android O 中的 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 中未包含的库(有关详情,请参阅 Android O 中的 VNDK 设计演示文稿)。

在运行 Android O 的设备上,除 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 插件加载到 sphal 命名空间来强制实施的。在之前的 Android 版本中,插件名称是使用 -load 选项指定的,而库是由 libLLVM.so 使用简单的 dlopen() 加载的。在 Android O 中,该名称在 -plugin 选项中指定,而库则直接由 bcc 本身加载。此选项可使开放源代码 LLVM 项目支持非 Android 专用路径。

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


图 7. 加载 bcc 插件 - Android O。

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 O 中的 SELinux 政策发生了变化,您在 vendor 分区中标记额外的文件时必须遵循特定规则(通过 neverallows 强制实施):

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

位码的 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 O 及更高版本中对其做出更改的计划。但是,如果接口发生更改,HAL 版本也会进行升级。

供应商实现

Android O 需要对 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 插件调用系统 bcc:/system/bin/bccbcc plugin 不能依赖面向供应商的 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 O 时,不会强制执行本文档中详细说明的限制,这意味着驱动程序可以继续与 /system/lib[64] 中的库相关联。不过,由于与 OVERRIDE_RS_DRIVER 相关的架构变更,您必须将 android.hardware.renderscript@1.0-impl 安装到 /vendor 分区;如果无法做到这一点,RenderScript 运行时会被强制回退到 CPU 路径。