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。

設計

下列各節詳細說明 Android 8.0 以上版本的 RenderScript 設計。

供應商可用的 RenderScript 程式庫

本節列出供應商程式碼可用的 RenderScript 程式庫 (稱為「適用於相同程序 HAL 的供應商 NDK」或「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 以上版本的裝置上,所有同程序 HAL (SP-HAL) (RenderScript 除外) 都會載入連結器命名空間 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 程式庫平台版本的預設連結器命名空間,dlopen libRS_internal.so (RS 架構的主要實作項目)。

採用 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 接著會將 dlopens libRS_internal.so 放入名為 rs 的另一個連結器命名空間。

供應商可以設定建構時間標記 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 位於命名空間的搜尋路徑中。

圖 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

這行會指定動態連結器應從 rs 命名空間載入 libRS_internal.so,前提是無法從 sphal 命名空間找到/載入程式庫 (一律如此,因為 sphal 命名空間不會搜尋 /system/lib/vndk-sp,而 libRS_internal.so 就位於 /system/lib/vndk-sp 中)。完成這項設定後,只要對 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 選項指定,而程式庫是透過 libLLVM.so 簡單的 dlopen() 載入。在 Android 8.0 以上版本中,這項設定是在 -plugin 選項中指定,且程式庫是由 bcc 本身直接載入。這個選項可啟用開放原始碼 LLVM 專案的非 Android 特定路徑。

圖 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 使用的相同程式庫。否則,編譯的位元碼可能找不到連結時可用的符號。

為此,RS 架構會在執行 ld.mc 時,根據 RS 架構本身是從 /system/lib/system/lib/vndk-sp 載入,使用不同的執行階段程式庫搜尋路徑。 方法是讀取 RS 架構程式庫任意符號的位址,並使用 dladdr() 取得對應至該位址的檔案路徑。

SELinux 政策

由於 Android 8.0 以上版本中的 SELinux 政策有所變更,您必須遵循特定規則 (透過 neverallows 強制執行),才能在 vendor 分區中標記其他檔案:

  • 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 中的中繼資料可能會變更 (例如,可能會有新的執行階段函式,分配設定項和擷取項、數學函式等。部分執行階段函式位於 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 (和所有非 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 開發人員網誌:Android GPU Compute Going Forward。這項淘汰作業的資源資訊包括: