hidl實現hal

xiaowang_lj發表於2024-05-22

一、前言

正如 Android HIDL 概述 一文中簡單的對 HIDL 的演進和新架構下 Framework 與 Hal 層之間的通訊做了介紹。但是筆者的目的是想完整的實現從上層 APP 到 hal 之間通訊過程,由此可以更加深刻的理解這種機制。

二、Binderized Mode (繫結式)簡介

從上文介紹,我們知道 繫結模式 是 google 為了向前相容而定義的一種型別,且 Android 8.0 及後續版本的裝置都必須只支援這種模式。這種模式下 Framework 與 Hal 分別位於不同的程序中,其實從具體實現來講這種模式也更應該被稱為 Binder 化的直通式。本文將透過這種方式實現一個 HIDL 服務。

三、環境/工具準備

  • Ubuntu 20.04 TLS
  • Android 原始碼:Android 9.0,編譯燒錄詳見 Android原始碼編譯燒錄
  • hidl-gen 工具:Android 系統自帶,需要配置一下環境變數

四、HIDL 實現

本文目的是實現一個具有 加減乘除 運算的 HIDL 服務,命名為 銀河一號(GalaxyOne)。HIDL用起來非常簡單,在系統原始碼中的 hardware/interfaces 目錄下有很多的 HIDL,我們仿照其他 HIDL 來建立自己的目錄:hardware/interfaces/galaxy_one/1.0

4.1 建立 IGalaxyOne.hal 檔案

hardware/interfaces/galaxy_one/1.0/IGalaxyOne.hal

這裡定義了四種基本的運算:加、減、乘、除,這是上層呼叫 HAL 的入口,內容如下:

package android.hardware.galaxy_one@1.0;

interface IGalaxyOne{

    //加法
    add(uint32_t a,uint32_t b) generates (uint32_t result);
    //減法
    sub(uint32_t a,uint32_t b) generates (uint32_t result);
    //乘法
    mul(uint32_t a,uint32_t b) generates (uint32_t result);
    //除法
    div(uint32_t a,uint32_t b) generates (uint32_t result);
    
};

4.2 hidl-gen 生成 HIDL 框架

在使用 hidl-gen 之前需要先做兩件事:
1、hidl-gen 由 Android 提供,使用之前需要先配置一下系統路徑,如我這裡所做的:

# vim ~/.bashrc
export PATH=/home/zsk/AOSP/out/soong/host/linux-x86/bin:$PATH

2、Ubuntu 新的終端視窗必須先設定一些 Android 環境變數:

source build/envsetup.sh
lunch aosp_sailfish-userdebug  // lunch mode 根據需求修改
make hidl-gen

配置完成之後在 原始碼根目錄 下執行如下命令:

  PACKAGE=android.hardware.galaxy_one@1.0
  LOC=hardware/interfaces/galaxy_one/1.0/default/
  hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
  hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

命令執行成功之後會發現在 hardware/interfaces/galaxy_one/1.0 目錄下多了一個 default 目錄,進入之後發現有如下檔案:

之後執行 update-makefiles.sh 指令碼來為 HIDL 生成對應的 Android.bp 檔案,此指令碼位於 hardware/interfaces 目錄下,同樣可在原始碼根目錄下執行:

./hardware/interfaces/update-makefiles.sh

接下來我們需要新增兩個空檔案:

touch hardware/interfaces/galaxy_one/1.0/default/android.hardware.galaxy_one@1.0-service.rc
touch hardware/interfaces/galaxy_one/1.0/default/service.cpp

完成之後,整個工程結構如下所示:

4.3 呼叫流程

上述過程已經將 HIDL 服務所需要的全本檔案配置完成,雖然其中很多檔案是空的,或者沒有具體實現,我們現在先放在一邊,先來對整體的呼叫流程及各個檔案的功效略作說明。

Binder 化直通式
  • Application:指上層應用
  • JNI:指 framework 層,getService 獲取 hal 層 service
  • android.hardware.galaxy_one@1.0.so:由 IGalaxyOne.hal 生成的介面庫,由 hardware/interfaces/galaxy_one/1.0/Android.bp 透過 IGalaxyOne.hal 生成,這樣只要這個介面庫不變,那麼 framework 的更新和 hal 層就隔絕開了
  • android.hardware.galaxy_one@1.0-service.rc:裝置開機時透過 rc 檔案啟動此服務
  • galaxy_hal_service:service的名,可透過國 start galaxy_hal_service 啟動服務
  • android.hardware.galaxy_one@1.0-impl.so:實現庫,上層應用的最終呼叫

關於 Application、JNI 這兩層內容會在稍後用兩個篇幅去分析,此處暫不理會。現在我們就著這個呼叫過程將需要的內容補充完成。

4.3.1 介面庫生成

android.hardware.galaxy_one@1.0.so,由 hardware/interfaces/galaxy_one/1.0/Android.bp 透過 IGalaxyOne.hal 生成,Android.bp 檔案是在上面一些列命令執行之後生成,而介面庫是當我們最終執行編譯模組時生成,可以說這個過程不需要我們手動參與,Android.bp 內容如下:

// This file is autogenerated by hidl-gen -Landroidbp.

hidl_interface {
    name: "android.hardware.galaxy_one@1.0",   //此處設定介面庫的名字
    root: "android.hardware",
    vndk: {
        enabled: true,
    },
    srcs: [
        "IGalaxyOne.hal",
    ],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    gen_java: true,
}

4.3.2 實現庫生成

android.hardware.galaxy_one@1.0-impl.so,由 hardware/interfaces/galaxy_one/1.0/default/Android.bp 透過 GalaxyOne.cpp 生成,注意這個 Android.bp 檔案是位於 default 目錄下,同樣的在最後模組編譯時生成,原始內容如下:

cc_library_shared {
    name: "android.hardware.galaxy_one@1.0-impl",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "GalaxyOne.cpp",
    ],
    shared_libs: [    //這裡可以新增我們需要的庫
        "liblog",     
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "libutils",
        "android.hardware.galaxy_one@1.0",
    ],
}
4.3.3 GalaxyOne.cpp 實現

4.3.2 中,實現庫是由 GalaxyOne.cpp 編譯而成,現在我們來將此檔案補充完成:

GalaxyOne.h:
Binder化直通式,同樣需要將 HIDL_FETCH_XXX 開啟,至於原因我們在後面會提及

#ifndef ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
#define ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H

#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <log/log.h>

namespace android {
namespace hardware {
namespace galaxy_one {
namespace V1_0 {
namespace implementation {

using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;

struct GalaxyOne : public IGalaxyOne {
    // Methods from ::android::hardware::galaxy_one::V1_0::IGalaxyOne follow.
    Return<uint32_t> add(uint32_t a, uint32_t b) override;
    Return<uint32_t> sub(uint32_t a, uint32_t b) override;
    Return<uint32_t> mul(uint32_t a, uint32_t b) override;
    Return<uint32_t> div(uint32_t a, uint32_t b) override;

    // Methods from ::android::hidl::base::V1_0::IBase follow.

};

// FIXME: most likely delete, this is only for passthrough implementations
 extern "C" IGalaxyOne* HIDL_FETCH_IGalaxyOne(const char* name);

}  // namespace implementation
}  // namespace V1_0
}  // namespace galaxy_one
}  // namespace hardware
}  // namespace android

#endif  // ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H

GalaxyOne.cpp:

#include "GalaxyOne.h"

namespace android {
namespace hardware {
namespace galaxy_one {
namespace V1_0 {
namespace implementation {

// Methods from ::android::hardware::galaxy_one::V1_0::IGalaxyOne follow.
Return<uint32_t> GalaxyOne::add(uint32_t a, uint32_t b) {
    uint32_t result = a + b;
    ALOGE("GalaxyOne::add  a = %d,b = %d,result = %d",a,b,result);
    return result;
}

Return<uint32_t> GalaxyOne::sub(uint32_t a, uint32_t b) {
    uint32_t result = a - b;
    ALOGE("GalaxyOne::sub  a = %d,b = %d,result = %d",a,b,result);
    return result;
}

Return<uint32_t> GalaxyOne::mul(uint32_t a, uint32_t b) {
    uint32_t result = a * b;
    ALOGE("GalaxyOne::mul  a = %d,b = %d,result = %d",a,b,result);
    return result;
}

Return<uint32_t> GalaxyOne::div(uint32_t a, uint32_t b) {
    uint32_t result = a / b;
    ALOGE("GalaxyOne::div  a = %d,b = %d,result = %d",a,b,result);
    return result;
}

// Methods from ::android::hidl::base::V1_0::IBase follow.
IGalaxyOne* HIDL_FETCH_IGalaxyOne(const char* /* name */) {
    ALOG("galaxy_one service init success....");
    return new GalaxyOne();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace galaxy_one
}  // namespace hardware
}  // namespace android

4.3.3 模組編譯

現在除了需要的 rc 檔案沒有補充、galaxy-hal-service 服務沒有生成外其餘均已配置好了,現在進行編譯生成對應的庫。進入根目錄下執行如下命令:(注意是在剛剛執行過的 source build/envsetup.shlunch 的視窗下編譯,若是新視窗則需要重新執行這兩條命令)

mmm  hardware/interfaces/galaxy_one/1.0

此時應該可以在 out/tartget/product/XXX/vendor/lib64/hwout/tartget/product/XXX/system/lib64/hw 目錄下找到 android.hardware.galaxy_one@1.0.soandroid.hardware.galaxy_one@1.0-impl.so 兩個動態庫

4.3.4 serice 生成

上面過程將需要的動態庫生成完畢,接下來我們需要生成對應的 service 可執行檔案,這個過程一共分為三步:

1、在 /default 下的 Android.bp 檔案中新增如下內容

cc_binary {
    name: "android.hardware.galaxy_one@1.0-service",
    defaults: ["hidl_defaults"],
    relative_install_path: "hw",
    vendor: true,
    srcs: [
        "service.cpp"
    ],
    init_rc: ["android.hardware.galaxy_one@1.0-service.rc"],
    shared_libs: [
        "liblog",
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "libutils",
        "android.hardware.galaxy_one@1.0",
    ],
}

2、補充 service.cpp 內容

內容很簡單,defaultPassthroughServiceImplementation 幫我們自動註冊服務:

#define LOG_TAG "android.hardware.galaxy_one@1.0-service"
 
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/LegacySupport.h>
#include "GalaxyOne.h"
 
// Generated HIDL files
using android::hardware::galaxy_one::V1_0::IGalaxyOne;
using android::hardware::galaxy_one::V1_0::implementation::GalaxyOne;
 
using android::hardware::defaultPassthroughServiceImplementation;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
 
int main() {
    return defaultPassthroughServiceImplementation<IGalaxyOne>();
} 

3、補充 rc 檔案

注意這裡的 galaxy-hal-service 相當於這個服務的別名,系統就是根據這個檔案在啟動的同時也將這個 service 啟動,因此在下面我們手動啟動測試時沒有什麼作用,不過這裡先補充完整。

service galaxy-hal-service /vendor/bin/hw/android.hardware.galaxy_one@1.0-service
    class hal
    user system
    group system

同樣執行 mmm hardware/interfaces/galaxy_one/1.0 命令,完成之後就會得到如下二進位制可執行檔案:

out/target/product/sailfish/vendor/bin/hw/android.hardware.galaxy_one@1.0-service

4.4 client 端

經過一系列過程之後,我們得到了三個產物
1、android.hardware.galaxy_one@1.0.so
2、android.hardware.galaxy_one@1.0-impl.so
3、android.hardware.galaxy_one@1.0-service

現在需要模擬一個客戶端來測試呼叫,因此在 default 目錄下新建 test 目錄,並新建 client.cpp、Android.bp 檔案,具體結構如下:

client.cpp 內容如下:

#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/Status.h>
#include <log/log.h>

using android::sp;
using android::hardware::galaxy_one::V1_0::IGalaxyOne;
using android::hardware::Return;

int main(){
    android::sp<IGalaxyOne> service = IGalaxyOne::getService();
    if (service == nullptr) {
        ALOGD("faile to get galaxy_one service......");
        return -1;
    }
    ALOGE("success to get galaxy_one service.....");

    uint32_t addResult = service->add(3,4);
    ALOGE("galaxy_one service add: result = %d",(int)addResult);

    uint32_t subResult = service->sub(8,3);
    ALOGE("galaxy_one service sub: result = %d",(int)subResult);

    uint32_t mulResult = service->mul(3,8);
    ALOGE("galaxy_one service mul: result = %d",(int)mulResult);

    uint32_t divResult = service->div(8,2);
    ALOGE("galaxy_one service div: result = %d",(int)divResult);

    return 0;
}

Android.bp 內容如下:

cc_binary {
    name: "galaxy_test",    //表示生成的 client 名稱
    srcs: [
        "client.cpp"
    ],
    shared_libs: [
        "liblog",
        "android.hardware.galaxy_one@1.0",
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "libutils",
    ],
}

mmm hardware/interfaces/galaxy_one/1.0 命令編譯之後,可以在 out/target/product/XXX/system/bin 目錄下找到 galaxy_test

五、驗證服務

5.1 push 裝置

現在我們一共得到 4 個產物,使用 adb 命令將其 push 到手機對應目錄下:

1、android.hardware.galaxy_one@1.0-impl.so ===> /vendor/lib64/hw
2、android.hardware.galaxy_one@1.0.so ===> vendor/lib64
3、android.hardware.galaxy_one@1.0-service ===> /vendor/bin/hw
4、galaxy_test ===> /system/bin

5、rc ===》 /vendor/etc/init

6、xml ===》/vendor/etc/vintf/manifest/

5.2 修改裝置 manifest.xml

HIDL 想要被 framework 獲取使用還需要在 manifest.xml 中註冊,此在手機 vendor/etc/vintf 下,我們將這個檔案 pull 出來新增如下程式碼之後再 push 原來位置:

    <hal format="hidl">
        <name>android.hardware.galaxy_one</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IGalaxyOne</name>
            <instance>default</instance>
        </interface>
        <fqname>@1.0::IGalaxyOne/default</fqname>
    </hal>

5.3 執行 service

這裡我們手動啟動,用於測試

./vendor/bin/hw/android.hardware.galaxy_one@1.0-service

5.4 執行 client

./system/bin/galaxy_test

執行成功之後可檢視日誌,有如下內容則表示服務建立成功:

apk呼叫

在out下查詢jar

apk呼叫:

IGalaxyOne service = null;
        try {
            service = IGalaxyOne.getService(false);
            Log.e("longjiang",service.add(3,5)+"");
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }

相關文章