Android HIDL HAL 接口定義語言詳解(1)

1. HIDL 概述

在 Andoird 8.0 版本框架代碼中,加入了 HIDL(HAL 接口定義語言),HIDL 的出現是爲了將用戶層和 HAL 層分割開,它指定了 HAL 和用戶之間的接口,讓用戶能夠替換 Android 框架,而無需重新編譯 HAL,以便讓廠商能夠以更低的成本、更快速地將設備更新到新版 Android 版本中。

通俗的來說,HIDL 設計了一套通過的框架接口,將 HAL 層實現與 Android 操作系統框架分離開來,設備廠商只需要構建一次 HAL,並將其放置在 /vendor 分區中,便能適應大部分 Android 操作系統框架版本的升級。

image

圖:HIDL 設計下的升級方式

在 HIDL 設計理念中,HAL 模塊以一個獨立的 Service 運行,用戶通過 Binder IPC 與 HAL 模塊進行通信。因此 在 Android 8.0 以上的版本中查看用戶進程會發現很多 HAL 模塊 Service 運行,如下:

consule:/ $ ps -A | grep android.hardware
system         219     1    9728   4852 0                   0 S [email protected]
audioserver    235     1   21268   8916 0                   0 S [email protected]
bluetooth      236     1    7716   3408 0                   0 S [email protected]
cameraserver   237     1   28412  11948 0                   0 S [email protected]
media          238     1   10232   4312 0                   0 S [email protected]
system         239     1    9976   3648 0                   0 S [email protected]

這些 Service 提供了設備 HAL 模塊具體接口實現(由 HIDL 主導設計),這些接口獨立於 Android 平臺與設備廠商 HAL 實現。下面會主要以 HIDL 設計下 Android 9.0 composer HAL 實現來插入分析。

2. HIDL 設計分析

HIDL 是一種接口定義語言,描述了 HAL 和它的用戶之間的接口,因此首先需要設計一套通用接口實現。HIDL 爲每個 HAL 模塊設計了不同接口定義 hal 文件,以 .hal 結尾,在 hidl-gen 工具的幫助下即可自動編譯生成對應接口 C++ 實現或者 Java 實現。

下面先來理清幾個概念。

2.1 軟件包

Google 爲每個 HAL 模塊設計一個接口軟件包,大部分 HIDL 接口軟件包位於 hardware/interfaces 下,hardware/interfaces 頂層會直接映射到 android.hardware 軟件包命名空間,軟件包名稱可以具有子級,表示接口軟件包的版本,如 HAL Service:[email protected] 的接口軟件包可以在 hardware/interfaces/graphics/composer/2.1 目錄下找到。

下表列出了 Android 所有軟件包前綴和位置:

軟件包前綴 位置
android.hardware.* hardware/interfaces/*
android.frameworks.* frameworks/hardware/interfaces/*
android.system.* system/hardware/interfaces/*
android.hidl.* system/libhidl/transport/*

軟件包結構

.hal 文件

軟件包是 HIDL 設計的關鍵所在,每個接口軟件包都包含一個 .hal 文件,.hal 文件包含一個指定文件所屬的軟件包和版本的 package 語句。如路徑 hardware/interfaces/graphics/composer/2.1/IComposer.hal 聲明。

package [email protected];

interface IComposer {
...
}

.hal 文件中定義了 HAL 模塊向用戶提供的訪問接口及數據類型

interface IComposer {
    struct MyStruct {/*...*/};
    ...
    getCapabilities() generates (vec<Capability> capabilities);
    dumpDebugInfo() generates (string debugInfo);
    ....
}

不含顯式 extends 聲明的接口會從 [email protected]::IBase 隱式擴展,其他語法不做分析,可以訪問 Google 官網 做深入瞭解。

hidl-gen 編譯器會將 .hal 文件編譯成一組 .h 和 .cpp 文件,這些自動生成的文件用於編譯客戶端/服務端實現鏈接到的共享庫,用於編譯此共享庫的 Android.bp 文件由 hardware/interfaces/update-makefiles.sh 腳本自動生成。每次將新軟件包添加到 hardware/interfaces 或在現有軟件包中添加/移除 .hal 文件時,都必須重新運行該腳本,以確保生成的共享庫是最新的。

hidl-gen 工具

在 HIDL 架構中,系統定義的所有的 .hal 接口,都是通過 hidl-gen 工具在編譯時轉換成對應的代碼。比如:

hal 文件:hardware/interfaces/graphics/composer/2.1/IComposer.hal


1. 生成文件路徑:
out/soong/.intermediates/hardware/interfaces/graphics/composer/2.1
2. 頭文件自動生成在:
[email protected]_genc++_headers
3. C++文件自動生成在:
[email protected]_genc++

hidl-gen 源碼路徑:system/tools/hidl,是在 ubuntu 上可執行的二進制文件,這裏不對工具源碼做分析。

hidl-gen 工具以 .hal 文件爲輸入,自動生成的文件主要以下幾個,這些文件會鏈接到與軟件包同名的單個共享庫(例如 [email protected])。

image

圖:由編譯器生成的文件

其中,

  • IFoo.h - 描述 C++ 類中的純 IFoo 接口;它包含 IFoo.hal 文件中的 IFoo 接口中所定義的方法和類型,類的命名空間包含軟件包名稱和版本號,例如 ::android::hardware::samples::IFoo::V1_0。客戶端和服務器都包含此標頭:客戶端用它來調用方法,服務器用它來實現這些方法。
  • IHwFoo.h - 頭文件,其中包含用於對接口中使用的數據類型進行序列化的函數的聲明。開發者不得直接包含其標頭(它不包含任何類)。
  • BpFoo.h - 從 IFoo 繼承的類,可描述接口的 HwBinder 代理(客戶端)實現。開發者不得直接引用此類。
  • BnFoo.h - 保存對 IFoo 實現的引用的類,可描述接口的 HwBinder 服務器端實現。開發者不得直接引用此類。
  • FooAll.cpp - 包含 HwBinder 客戶端和 HwBinder 服務器端的實現的類。當客戶端調用接口方法時,代理會自動從客戶端封送參數,並將事務發送到綁定內核驅動程序,該內核驅動程序會將事務傳送到另一端的服務器端實現。

2.2 共享庫與 Service

軟件包中編譯,如 在hardware/interfaces/graphics/composer 目錄下 mm 編譯將生成:

  1. [email protected] .so
  2. [email protected]
  3. [email protected]
  4. [email protected]

下面會簡單介紹一下這些庫及文件的用途,以及一個 HIDL 架構下的 HAL 模塊是怎麼運轉起來的,在下一章的實例分析中會具體分析它的代碼實現。

首先,[email protected] 是一個服務,是一個 Hal 模塊可執行程序,而 [email protected] 正是它的啓動配置腳本:

service vendor.hwcomposer-2-1 /vendor/bin/hw/[email protected]                                     
    class hal animation
    user system
    group graphics drmrpc
    capabilities SYS_NICE
    writepid /dev/cpuset/system-background/tasks

也就是說,Android HIDL 架構下,所有 HAL 模塊實現都以服務的形式運行在獨立的進程空間:

$ ps -A 
system         245     1   29084   8000 0                   0 S [email protected]
...

而 HAL 服務進程會鏈接 [email protected]

cc_binary {
    name: "[email protected]",
    defaults: ["hidl_defaults"],
    vendor: true,
    relative_install_path: "hw",
    srcs: ["service.cpp"],
    init_rc: ["[email protected]"],
    shared_libs: [
        "[email protected]", // 鏈接 HIDL 生成主要庫
        "libbinder",
        "libhidlbase",
        "libhidltransport",
        "liblog",
        "libsync",
        "libutils",
    ],
} 

首先,[email protected] 服務的作用就是向 hwservicemanager 註冊 HAL,以便客戶端調用,因此需要開機啓動。

[email protected] 中包含必要的 binder IPC 通信機制,包含 HAL 對象如 IComposer 對象的客戶端和服務器端通信實現及接口調用。而 [email protected] 又會鏈接 [email protected],這個動態庫具體實現了接口邏輯,如在 passthrought 模式下,鏈接舊版 HAL(hw_moudle_get)實現邏輯。

在最後一節會通過 composer HAL 來具體分析。

2.3 HAL 兼容類型

Google 的 HIDL 設計目的是讓 HAL 與 用戶調用在不同的進程中,HAL 被寫成 binder service,而用戶接口如 frameworks 作爲 binder client 通過 IPC 機制實現跨進程接口調用。

但是理想很豐滿,現實卻很殘酷,很多廠商還停留在 Android 老版本,爲了給廠商改變時間,同時保持 Android 向前兼容性,Google 另外設計了 Passthrough 類型的 HAL 框架。請看,

image

圖:HAL 的發展歷程

  1. Legacy Hal:Android 8.0 之前版本的 HAL 都是編譯成 so,然後動態鏈接到各個 frameworks service 中去。
  2. Passthrough Hal:該模式是爲了兼容舊版的 HAL,舊版 HAL 實現仍以動態庫的方式提供,只是 binder service 鏈接了動態庫 HAL 實現。
  3. Binderized HAL:HAL 與 用戶調用在不同的進程中,HAL 被寫成 binder service,而用戶接口如 frameworks 作爲 binder client 通過 IPC 機制實現跨進程接口調用。這個是 Google 的最終設計目標。

2.4 通用客戶端與服務端實現

創建 HAL 客戶端

通過 HAL Service 的註冊,hwservicemanager 中已經保存了 HAL 模塊對象(如 IComposer),因此我們只需如下操作客戶端。

首先將 HAL 庫添加到 makefile 中:

Make:LOCAL_SHARED_LIBRARIES += [email protected]
Soong:shared_libs: [ …, [email protected] ]

接下來,添加 HAL 頭文件:

#include <android/hardware/graphics/composer/2.1/IComposer.h>
…
// in code:
sp<IFoo> client = IComposer::getService();
client->doThing();

創建 HAL 服務器

要創建 HAL 實現,必須具有表示 HAL 的 .hal 文件並已在 hidl-gen 上使用 -Lmakefile 或 -Landroidbp 爲 HAL 生成 makefile(./hardware/interfaces/update-makefiles.sh 完成)。

爲了讓 HAL 在 Passthrough 模式下工作(兼容舊版 HAL),必須具備 HIDL_FETCH_IModuleName 函數(位於 /(system|vendor|…)/lib(64)?/hw/android.hardware.graphics/[email protected] 下)。

接下來,完成服務器端代碼並設置守護進程。守護進程代碼(支持 Passthrough 模式)示例:

#include <hidl/LegacySupport.h>

int main(int /* argc */, char* /* argv */ []) {
    return defaultPassthroughServiceImplementation<INfc>("nfc");
}

defaultPassthroughServiceImplementation 將對提供的 -impl 庫執行 dlopen() 操作,並將其作爲綁定式服務提供。守護進程代碼(對於純綁定式服務)示例:

int main(int /* argc */, char* /* argv */ []) {
    // This function must be called before you join to ensure the proper
    // number of threads are created. The threadpool will never exceed
    // size one because of this call.
    ::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);

    sp nfc = new Nfc();
    const status_t status = nfc->registerAsService();
    if (status != ::android::OK) {
        return 1; // or handle error
    }

    // Adds this thread to the threadpool, resulting in one total
    // thread in the threadpool. We could also do other things, but
    // would have to specify 'false' to willJoin in configureRpcThreadpool.
    ::android::hardware::joinRpcThreadpool();
    return 1; // joinRpcThreadpool should never return
}

此守護進程通常存在於 $PACKAGE + “-service-suffix”(例如 [email protected])中,但也可以位於任何位置。HAL 的特定類的 sepolicy 是屬性 hal_(例如 hal_composer))。您必須將此屬性應用到運行特定 HAL 的守護進程(如果同一進程提供多個 HAL,則可以將多個屬性應用到該進程)。

下一部分將分析 composer HAL 的啓動註冊過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章