Android HIDL 學習

原文鏈接:https://source.android.com/devices/architecture/hidl

鏈接:https://blog.csdn.net/lin20044140410/article/details/79578664

鏈接:https://blog.csdn.net/junwua/article/details/80594202

將上面幾片文章整理了下,供後續學習參考。

1、Android HIDL 官方介紹

HAL 接口定義語言(簡稱 HIDL,發音爲“hide-l”)是用於指定 HAL 和其用戶之間的接口的一種接口描述語言 (IDL)。HIDL 允許指定類型和方法調用(會彙集到接口和軟件包中)。從更廣泛的意義上來說,HIDL 是用於在可以獨立編譯的代碼庫之間進行通信的系統。

HIDL 旨在用於進程間通信 (IPC)。進程之間的通信經過 Binder 化。對於必須與進程相關聯的代碼庫,還可以使用直通模式(在 Java 中不受支持)。

HIDL 可指定數據結構和方法簽名,這些內容會整理歸類到接口(與類相似)中,而接口會彙集到軟件包中。儘管 HIDL 具有一系列不同的關鍵字,但 C++ 和 Java 程序員對 HIDL 的語法並不陌生。此外,HIDL 還使用 Java 樣式的註釋。

2、HIDL design

HIDL 的目標是,框架可以在無需重新構建 HAL 的情況下進行替換。HAL 將由供應商或 SOC 製造商構建,放置在設備的 /vendor 分區中,這樣一來,框架就可以在其自己的分區中通過 OTA 進行替換,而無需重新編譯 HAL。

HIDL 設計在以下方面之間保持了平衡:

    互操作性。在可以使用各種架構、工具鏈和編譯配置來編譯的進程之間創建可互操作的可靠接口。HIDL 接口是分版本的,發佈後不得再進行更改。
    效率。HIDL 會嘗試儘可能減少複製操作的次數。HIDL 定義的數據以 C++ 標準佈局數據結構傳遞至 C++ 代碼,無需解壓,可直接使用。此外,HIDL 還提供共享內存接口;由於 RPC 本身有點慢,因此 HIDL 支持兩種無需使用 RPC 調用的數據傳輸方法:共享內存和快速消息隊列 (FMQ)。
    直觀。通過僅針對 RPC 使用 in 參數,HIDL 避開了內存所有權這一棘手問題(請參閱 Android 接口定義語言 (AIDL));無法從方法高效返回的值將通過回調函數返回。無論是將數據傳遞到 HIDL 中以進行傳輸,還是從 HIDL 接收數據,都不會改變數據的所有權,也就是說,數據所有權始終屬於調用函數。數據僅需要在函數被調用期間保留,可在被調用的函數返回數據後立即清除。

2.1 使用直通模式

要將運行早期版本的 Android 的設備更新爲使用 Android O,您可以將慣用的(和舊版)HAL 封裝在一個新 HIDL 接口中,該接口將在綁定式模式和同進程(直通)模式提供 HAL。這種封裝對於 HAL 和 Android 框架來說都是透明的。

直通模式僅適用於 C++ 客戶端和實現。運行早期版本的 Android 的設備沒有用 Java 編寫的 HAL,因此 Java HAL 自然而然經過 Binder 化。

直通式標頭文件

編譯 .hal 文件時,除了用於 Binder 通信的標頭之外,hidl-gen 還會生成一個額外的直通標頭文件 BsFoo.h;此標頭定義了會被執行 dlopen 操作的函數。由於直通式 HAL 在它們被調用的同一進程中運行,因此在大多數情況下,直通方法由直接函數調用(同一線程)來調用。oneway 方法在各自的線程中運行,因爲它們不需要等待 HAL 來處理它們(這意味着,在直通模式下使用 oneway 方法的所有 HAL 對於線程必須是安全的)。

如果有一個 IFoo.hal,BsFoo.h 會封裝 HIDL 生成的方法,以提供額外的功能(例如使 oneway 事務在其他線程中運行)。該文件類似於 BpFoo.h,不過,所需函數是直接調用的,並未使用 Binder 傳遞調用 IPC。未來,HAL 的實現可能提供多種實現結果,例如 FooFast HAL 和 FooAccurate HAL。在這種情況下,系統會針對每個額外的實現結果創建一個文件(例如 PTFooFast.cpp 和 PTFooAccurate.cpp)。


Binder 化直通式 HAL

您可以將支持直通模式的 HAL 實現 Binder 化。如果有一個 HAL 接口 [email protected]::IFoo,系統會創建兩個軟件包:

    [email protected]::IFoo-impl。包含 HAL 的實現,並暴露函數 IFoo* HIDL_FETCH_IFoo(const char* name)。在舊版設備上,此軟件包經過 dlopen 處理,且實現使用 HIDL_FETCH_IFoo 進行了實例化。您可以使用 hidl-gen 和 -Lc++-impl 以及 -Landroidbp-impl 來生成基礎代碼。
    [email protected]::IFoo-service。打開直通式 HAL,並將其自身註冊爲 Binder 化服務,從而使同一 HAL 實現能夠同時以直通模式和 Binder 化模式使用。

如果有一個 IFoo,您可以調用 sp<IFoo> IFoo::getService(string name, bool getStub),以獲取對 IFoo 實例的訪問權限。如果 getStub 爲 True,則 getService 會嘗試僅在直通模式下打開 HAL。如果 getStub 爲 False,則 getService 會嘗試找到 Binder 化服務;如果未找到,則它會嘗試找到直通式服務。除了在 defaultPassthroughServiceImplementation 中,其餘情況一律不得使用 getStub 參數。(搭載 Android O 的設備是完全 Binder 化的設備,因此不得在直通模式下打開服務。)

3、HIDL 語法

在HIDL裏,與AIDL比較類似,底層也是基於binder機制。但是也有稍微不一樣的地方。爲了支持HIDL,Android 對BInder做了一定程度的修改。

這裏寫圖片描述

HIDL的基本語法:

1)定義接口 文件IsimpleTest.hal:

interface ISimpleTest {
    //定義成員
    enum SomeBaseEnum : uint8_t {
        bar = 66
    };

    struct Goober {
        int32_t q;
        string name;
        string address;
    };
    //定義成員函數
    getCookie() generates (int32_t cookie);

    customVecInt() generates (vec<int32_t> chain);

    customVecStr() generates (vec<string> chain);

    mystr() generates (string str);

    myhandle() generates (handle str);
};

 

在這裏,我們定義一個新的HIDL接口,取名叫做 ISimpleTest, 從語法上看有點像JAVA的語法。interface是關鍵字,代表要創建一個HIDL的接口。我們把上述接口保存成 IsimpleTest.hal文件存放在hardware/interfaces/tests/foo/1.0/ISimpleTest.hal,其實我們完全可以新建一個新目錄,使用一個新的package名,而不使用android.hardware.tests.foo,

2)HIDL編譯

在根目錄執行./hardware/interfaces/update-makefiles.sh,我們能夠看到會把Android 在hardware/interfaces下的所有package都會更新一遍,我們看下hardware/interfaces/tests/foo/1.0/Android.bp,在Android O當中,貌似使用了 Android.bp來替代Android.mk來作爲編譯管理工具。至於Android.bp的東西可以後續在研究,這裏我們只關注於HIDL。

filegroup {
    name: "[email protected]_hal",
     srcs: [
         "types.hal",
         "IFoo.hal",
         "IFooCallback.hal",
         "IMyTypes.hal",
        "ISimple.hal",
         "ISimpleTest.hal",
         "ITheirTypes.hal",
     ],
}

而在生成的C++文件:

genrule {
    name: "[email protected]_genc++",
    tools: ["hidl-gen"],
    cmd: "$(location hidl-gen) -o $(genDir) -Lc++-sources -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport [email protected]",
    
    srcs: [
        ":[email protected]_hal",
    ],

    out: [
        "android/hardware/tests/foo/1.0/types.cpp",

        "android/hardware/tests/foo/1.0/FooAll.cpp",

        "android/hardware/tests/foo/1.0/FooCallbackAll.cpp",

        "android/hardware/tests/foo/1.0/MyTypesAll.cpp",

        "android/hardware/tests/foo/1.0/SimpleAll.cpp",

        "android/hardware/tests/foo/1.0/SimpleTestAll.cpp",

        "android/hardware/tests/foo/1.0/TheirTypesAll.cpp",
    ],
}

我們可以看到這段邏輯是利用 hidl-gen工具來生成.cpp文件。命令是: cmd: “(genDir) -Lc++-sources -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport [email protected]”, 

.hal源碼是:

srcs: [
    ":[email protected]_hal",
],

而這部分就是上面所定義的各種.hal文件。最終輸出就是各種.cpp文件。我們比較關注的就是 SimpleTestAll.cpp文件。

同時會生成以下一些頭文件:

genrule {
    name: "[email protected]_genc++_headers",
    tools: ["hidl-gen"],
    cmd: "$(location hidl-gen) -o $(genDir) -Lc++-headers -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.tests.foo@1 .0",
    
    srcs: [
        ":[email protected]_hal",
    ],
    out: [
        ……………………….
        "android/hardware/tests/foo/1.0/ISimpleTest.h",
        "android/hardware/tests/foo/1.0/IHwSimpleTest.h",
        "android/hardware/tests/foo/1.0/BnHwSimpleTest.h",
        "android/hardware/tests/foo/1.0/BpHwSimpleTest.h",
        "android/hardware/tests/foo/1.0/BsSimpleTest.h",
        ………………………...
    ],
}

 

從生成的頭文件裏看,我們看到有 ISimpleTest.h, BnHwSimpleTest.h, BpHwSimpleTest.h,Bnxxxxx與Bpxxxxx這兩個東西我們是不是看起來很眼熟?在Binder裏, Ixxxxx.h定義了client與service統一的通用接口,而Bnxxxxx.h 派生自 Ixxxxx.h,做爲service端實現的頭文件,Bpxxxxx.h同樣派生自 Ixxxxx.h做爲client端的頭文件。這樣調用Bpxxxxx.h定義的接口,就自動利用binder機制跨進程由service端實現了Bnxxxxx.h定義函數。

由編譯器生成的文件。

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

這些文件的結構類似於由 aidl-cpp 生成的文件(有關詳細信息,請參見 HIDL 概覽中的“直通模式”)。獨立於 HIDL 使用的 RPC 機制的唯一一個自動生成的文件是 IFoo.h,其他所有文件都與 HIDL 使用的 HwBinder RPC 機制相關聯。因此,客戶端和服務器實現不得直接引用除 IFoo 之外的任何內容。爲了滿足這項要求,請只包含 IFoo.h 並鏈接到生成的共享庫。

注意:HwBinder 只是一種可能的傳輸機制,未來可能會添加新的傳輸機制。

.hal最終編譯出來的結果是:

cc_library_shared {
    name: "[email protected]",
    defaults: ["hidl-module-defaults"],
    generated_sources: ["[email protected]_genc++"],
    generated_headers: ["[email protected]_genc++_headers"],
    export_generated_headers: ["[email protected]_genc++_headers"],
    vendor_available: true,
    
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "liblog",
        "libutils",
        "libcutils",
        "[email protected]",
    ],
    
    export_shared_lib_headers: [
    "libhidlbase",
    "libhidltransport",
    "libhwbinder",
    "libutils",
    "[email protected]",
    ],
}

從上面看得很清楚,.hal文件被編譯後會生成一個動態庫文件 [email protected]

3)HIDL的使用 

HIDL的使用,其實就是指怎麼在service端實現,怎麼在client端調用。其實也挺簡單,基本流程就是service端往系統裏註冊,client從系統裏拿到service 的proxy,然後調用。跟AIDL的Binder一樣一樣的。

Client端拿service的proxy: 
foo = IFoo::getService(“foo”, mode == PASSTHROUGH /* getStub */);

使用的是Ixxx裏自動生成的 getService函數,拿到之後就能使用.hal定義的接口了。

Service端往系統註冊: 
int main() { 
return defaultPassthroughServiceImplementation(); 
}

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