Android HAL機制的深入理解及在Linux上移植和執行的一個好玩的HAL小例子

2023-04-08 15:01:07

PS:要轉載請註明出處,本人版權所有。

PS: 這個只是基於《我自己》的理解,

如果和你的原則及想法相沖突,請諒解,勿噴。

環境說明

  Ubuntu 18.04.x

前言


  近一年來,雖然還是做的是AIOT相關的事情,但是某些事情卻發生了一些變化。隨著個人的閱歷提升,現在的AI在邊緣端部署已經不侷限於傳統的linux這樣的形態,這一年來,我已經逐漸接觸到android的邊緣端盒子這樣的概念了。

  對於Android來說,我之前有所瞭解,但是停留的非常表面,只知道其是一個Linux核心+Android Runtime+app的這樣的形態。但是如果我們將Android Runtime和App看作普通的Linux app,那麼我們會發現Android和傳統Linux的差別沒有那麼大,我們甚至可以將Android當成一個Linux發行版來使用。但是實際在使用過程中,最大的差異在於Android引入了許多的Android特有的內容,例如binder,log,adb等等,其次和linux下面程式設計的最大區別還是在於他們的基礎c庫不一樣,一個是bonic c,一個的glibc,這一點可以說是貫穿我在使用Android的整個過程中。

  在使用Android的過程中,我們會聽見一個HAL的詞,整個HAL可以說是Android能夠成功商業化的一個重要因素,因為其可以保護各個廠商的利益,然後反過來,正是由於各個廠家的支援,導致了Android的生態是非常豐富的。那麼我們來看看這個HAL到底是幹嘛的。

  由於網上有許多介紹HAL的文章了,本文不會重複一些基礎的內容,因此本文後續的閱讀需要讀者至少對Linux和Android HAL有一個基礎的瞭解後,才建議閱讀本文。





HAL 深入分析


  首先Android HAL分為大概分為兩個版本,一個新的和舊的,本文重點分析新版HAL原理。其中兩種版本架構大概簡介如下:

  1. 舊的HAL架構(libhardware_legacy.so)每個app都會載入模組,有重入問題,由於每個app直接載入對應的so,也導致app和模組介面耦合較大,非常不方便維護。
  2. 新的HAL架構module/stub,app存取對應硬體對應的服務,然後對應硬體服務通過抽象api,module id,裝置id,代理後存取硬體。

   上面對新的架構說的還是有些表面了,下面我們深入分析其中的重要的幾個結構(在hardware.h中),然後最後通過一個實際例子來深入理解它。



struct hw_module_t

  它的實際原始碼定義如下:

/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of this data structure must begin with hw_module_t
 * followed by module specific information.
 */
typedef struct hw_module_t {
    /** tag must be initialized to HARDWARE_MODULE_TAG */
    uint32_t tag;

    /**
     * The API version of the implemented module. The module owner is
     * responsible for updating the version when a module interface has
     * changed.
     *
     * The derived modules such as gralloc and audio own and manage this field.
     * The module user must interpret the version field to decide whether or
     * not to inter-operate with the supplied module implementation.
     * For example, SurfaceFlinger is responsible for making sure that
     * it knows how to manage different versions of the gralloc-module API,
     * and AudioFlinger must know how to do the same for audio-module API.
     *
     * The module API version should include a major and a minor component.
     * For example, version 1.0 could be represented as 0x0100. This format
     * implies that versions 0x0100-0x01ff are all API-compatible.
     *
     * In the future, libhardware will expose a hw_get_module_version()
     * (or equivalent) function that will take minimum/maximum supported
     * versions as arguments and would be able to reject modules with
     * versions outside of the supplied range.
     */
    uint16_t module_api_version;
#define version_major module_api_version
    /**
     * version_major/version_minor defines are supplied here for temporary
     * source code compatibility. They will be removed in the next version.
     * ALL clients must convert to the new version format.
     */

    /**
     * The API version of the HAL module interface. This is meant to
     * version the hw_module_t, hw_module_methods_t, and hw_device_t
     * structures and definitions.
     *
     * The HAL interface owns this field. Module users/implementations
     * must NOT rely on this value for version information.
     *
     * Presently, 0 is the only valid value.
     */
    uint16_t hal_api_version;
#define version_minor hal_api_version

    /** Identifier of module */
    const char *id;

    /** Name of this module */
    const char *name;

    /** Author/owner/implementor of the module */
    const char *author;

    /** Modules methods */
    struct hw_module_methods_t* methods;

    /** module's dso */
    void* dso;

#ifdef __LP64__
    uint64_t reserved[32-7];
#else
    /** padding to 128 bytes, reserved for future use */
    uint32_t reserved[32-7];
#endif

} hw_module_t;

  一個hw_module_t代表一個硬體模組,但是一個硬體模組可能包含了很多的硬體裝置,所以我們要操作一個實際的硬體裝置,按照這套框架,第一件事獲取模組,第二件事就是開啟裝置,第三操作裝置,第四關閉裝置。

   其實這裡的註釋說的很清楚,使用它,有兩個注意事項,一是必須要在實際模組中定義一個HAL_MODULE_INFO_SYM的結構體變數,且此結構體必須是struct hw_module_t 作為第一個成員變數。這裡的根本原因是因為c的結構體記憶體佈局和暴露這個結構體的名字,後面會詳細說這個事情。



struct hw_module_methods_t

  它的實際原始碼定義如下:

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);

} hw_module_methods_t;

  此結構體沒啥可說的,就是通過實際的模組,然後傳入一個硬體裝置的id,然後開啟實際的硬體裝置。因此在每個實際的hw_module_t中,都包含了一個hw_module_methods_t,然後有開啟裝置的操作。這裡也體現出來了一個模組可以有多個裝置的這種概念。



struct hw_device_t

  它的實際原始碼定義如下:

/**
 * Every device data structure must begin with hw_device_t
 * followed by module specific public methods and attributes.
 */
typedef struct hw_device_t {
    /** tag must be initialized to HARDWARE_DEVICE_TAG */
    uint32_t tag;

    /**
     * Version of the module-specific device API. This value is used by
     * the derived-module user to manage different device implementations.
     *
     * The module user is responsible for checking the module_api_version
     * and device version fields to ensure that the user is capable of
     * communicating with the specific module implementation.
     *
     * One module can support multiple devices with different versions. This
     * can be useful when a device interface changes in an incompatible way
     * but it is still necessary to support older implementations at the same
     * time. One such example is the Camera 2.0 API.
     *
     * This field is interpreted by the module user and is ignored by the
     * HAL interface itself.
     */
    uint32_t version;

    /** reference to the module this device belongs to */
    struct hw_module_t* module;

    /** padding reserved for future use */
#ifdef __LP64__
    uint64_t reserved[12];
#else
    uint32_t reserved[12];
#endif

    /** Close this device */
    int (*close)(struct hw_device_t* device);

} hw_device_t;

  此結構體就是上文我們說的實際開啟的裝置結構體,一般情況我們會將此結構體暴露到對應hal的標頭檔案中,因為這個包含了實際操作硬體的一些介面資訊。注意這個hw_device_t包含了一個close介面,是每個裝置的關閉介面。

  注意,我們這個時候沒有去說hardware.c所做的事情,也就是如下兩個介面到底做了什麼,這個問題的解答,我們留到下一小節例子中去深入認識他。

/**
 * Get the module info associated with a module by id.
 *
 * @return: 0 == success, <0 == error and *module == NULL
 */
int hw_get_module(const char *id, const struct hw_module_t **module);

/**
 * Get the module info associated with a module instance by class 'class_id'
 * and instance 'inst'.
 *
 * Some modules types necessitate multiple instances. For example audio supports
 * multiple concurrent interfaces and thus 'audio' is the module class
 * and 'primary' or 'a2dp' are module interfaces. This implies that the files
 * providing these modules would be named audio.primary.<variant>.so and
 * audio.a2dp.<variant>.so
 *
 * @return: 0 == success, <0 == error and *module == NULL
 */
int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t **module);




一個MY_HW的硬體模組的HAL例子


   如上,我們已經介紹了hal裡面的重要的3個結構體,但是如果就到此的話,其實我們對hal還是一知半解,這個時候,我們可以嘗試自己虛擬一個硬體出來,然後設計HAL介面。這樣可以實際體會HAL的工作原理。

   首先,我們定義我們的模組叫做MY_HW。



下面是自定義的hal模組原始碼

  my_hw_hal.h 原始碼

#ifndef __MY_HW_HAL_H__
#define __MY_HW_HAL_H__


#include "hardware.h"

#define MY_HW_MODULE_ID "MY_HW"


struct my_hw_device_t{

    struct hw_device_t base;

    int (*set_my_hw_op)(struct my_hw_device_t * dev, int op_type);
};

#endif //__MY_HW_HAL_H__

  my_hw_hal.c 原始碼

#include "my_hw_hal.h"

#include <stdio.h>
#include <string.h>
int my_hw_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
int set_my_hw_op_device_0 (struct my_hw_device_t * dev, int op_type);
int my_hw_close_device_0(struct hw_device_t* device);

//For hw_moudle_methods_t
static struct hw_module_methods_t my_hw_methods = {

    .open = my_hw_open,
};



//For hw_module_t
struct my_hw_module_t {

    struct hw_module_t base;
};




__attribute__((visibility("default")))  struct  my_hw_module_t  HAL_MODULE_INFO_SYM = {

    .base = {

        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = 0,
        .hal_api_version = 0,
        .id = MY_HW_MODULE_ID,
        .name = "MY HW MODULE",
        .author = "Sky",
        .methods = &my_hw_methods
    }
};

//For hw_device_t
static struct my_hw_device_t my_hw_device_0 = {
    .base = {
        .tag = HARDWARE_DEVICE_TAG,
        .version = 0,
        .module = (hw_module_t*)&HAL_MODULE_INFO_SYM,
        .close = my_hw_close_device_0
    },
    .set_my_hw_op = set_my_hw_op_device_0
};



//For hw_device_t
int set_my_hw_op_device_0 (struct my_hw_device_t * dev, int op_type)
{
    printf("set_my_hw_op_device_0() op_type = %d\n", op_type);
    return 0;
}

int my_hw_close_device_0(struct hw_device_t* device)
{
    printf("my_hw_close_device_0()\n");
    return 0;
}




//For hw_moudle_methods_t
int my_hw_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device)
{
    printf("my_hw_open() device id %s\n", id);
    if (strcmp(id, "0") != 0){
        printf("my_hw_open() failed\n");
        return -1;
    }
    *device = (struct hw_device_t*)&my_hw_device_0;

    return 0;
}

  從這裡我們可以看出,新模組的實現就是對3個結構體的繼承和實現。同時對一些成員變數進行賦值。



MY_HW模組原始碼的分析
__attribute__((visibility("default")))  struct  my_hw_module_t  HAL_MODULE_INFO_SYM = {

    .base = {

        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = 0,
        .hal_api_version = 0,
        .id = MY_HW_MODULE_ID,
        .name = "MY HW MODULE",
        .author = "Sky",
        .methods = &my_hw_methods
    }
};

   my_hw_module_t 的定義,就是定義一個HAL_MODULE_INFO_SYM(它是一個宏定義)變數,注意此宏定義會被替換為一個HMI的名字,此名字是所有HAL模組必須暴露的一個符號。且必須叫做這個名字,因為這是libhardware.so中讀取它的約定。

  此外,hw_module_t必須在我定義的變數的開始位置,這樣方便型別轉換。

//For hw_moudle_methods_t
static struct hw_module_methods_t my_hw_methods = {

    .open = my_hw_open,
};

   hw_module_methods_t 的定義,實現一個真正的裝置開啟介面。

//For hw_device_t
static struct my_hw_device_t my_hw_device_0 = {
    .base = {
        .tag = HARDWARE_DEVICE_TAG,
        .version = 0,
        .module = (hw_module_t*)&HAL_MODULE_INFO_SYM,
        .close = my_hw_close_device_0
    },
    .set_my_hw_op = set_my_hw_op_device_0
};

   my_hw_device_t的定義,此裝置就是我們這個模組定義的一個裝置,此裝置通過my_hw_module_t中的open介面開啟,然後提供相關的介面給HAL相關的程式使用。



綜合分析

  這裡我們簡單設計一個服務程式來呼叫我們封裝的hal模組,其流程就是呼叫hw_get_module獲取實際module地址,然後通過module開啟對應裝置,然後操作裝置,最後關閉裝置。

#include "my_hw_hal.h"

int main(int argc, char * argv[])
{
    hw_module_t * hwmodule = nullptr;
    my_hw_device_t * my_hw_device = nullptr;

    int _ret = hw_get_module(MY_HW_MODULE_ID, (const hw_module_t**)&hwmodule);

    #define MY_HW_DEVICE_ID "0"
    _ret = hwmodule->methods->open(hwmodule, MY_HW_DEVICE_ID, (hw_device_t**)&my_hw_device);

    #define MY_HW_DEVICE_ID_0_OP_TYPE_0 0
    my_hw_device->set_my_hw_op(my_hw_device, MY_HW_DEVICE_ID_0_OP_TYPE_0);

    my_hw_device->base.close((hw_device_t*)my_hw_device);
    return 0;
}

  然後通過如下編譯指令碼生成兩個so和一個應用程式。

#!/bin/bash


# for hardware so
gcc -fPIC -c hardware.c -I . -fvisibility=hidden
gcc -shared hardware.o -o libhardware.so -ldl -fvisibility=hidden
strip libhardware.so



# for MY_HW.sky-sdk.so
gcc -fPIC -c my_hw_hal.c -I . -fvisibility=hidden
gcc -shared my_hw_hal.o -o MY_HW.sky-sdk.so -fvisibility=hidden
strip MY_HW.sky-sdk.so


# for my hw service 
g++ my_hw_service.cpp -o my_hw_service -L . -l hardware -I .  -fvisibility=hidden

  我們先來看看我們應用程式執行的結果如下:

rep_img
  我們可以看到,第一步通過hw_get_module獲取到一個模組資訊,這裡其實在hardware.c裡面定義的很清楚,直接通過dlopen/dlsym 一個HMI的符號得到了我們定義的my_hw_module_t的變數地址,由於c的記憶體佈局的原因,本來這個地址存放的是my_hw_module_t變數,但是可以直接強轉為hw_module_t變數。簡單來說,這就是一種c裡面實現類似c++繼承的方法,由於記憶體佈局是連續的,根據hw_module_t的大小,可以直接從my_hw_module_t前面部分轉換為hw_module_t。這也是hw_module_t必須放在my_hw_module_t中開始的原因。

  我們也可知道,在hardware.c中,hw_get_module是根據id來在特定目錄中去搜尋相關的模組so,然後通過dlopen開啟它並進行後續的操作。如我修改的hardware.c部分節選:

rep_img

  同理,到了這裡,我們不用猜測,一定在MY_HW.sky-sdk.so暴露了一個HMI的符號。如圖:

rep_img

  注意hardware.c和hardware.h直接從android原始碼中拿出來,簡單做修改即可在linux裡面編譯。這裡我們簡單看看libhardware.so的符號暴露資訊:

rep_img

  這裡其實就暴露了上面提到的兩個介面,hw_get_module和hw_get_module_by_class。





後記


  總的來說:

  • hw_module_methods_t 可以用來標識模組的公用方法,當前具備了一個open方法,注意一個module對應多個裝置功能。
  • hw_get_module() 主要是使用傳入的id,然後通過id和一些屬性通過dlopen載入so。注意'HMI'這個符號,這個符號是存放的hw_module_t作為基礎類別的地址。通過此地址可以開啟這個模組中的特有裝置,並提供特定操作。
  • hw_module_t 可以用來標識一個模組,首先通過hw_get_module()獲取當前hw_module_t,然後通過當前hw_module_t的hw_module_methods_t中的open方法開啟裝置。
  • hw_device_t 可以用來標識模組下的一個裝置,其中的close方法用來關閉本裝置。

  注意三個結構體之間的關係:hw_get_module()獲取hw_module_t,hw_module_t通過hw_module_methods_t獲取hw_device_t,hw_device_t中攜帶了當前裝置的各種操作方法,其實HAL的另一個重要部分是在hw_device_t中定義當前裝置的通用介面。

  其實HAL的整個原理並不複雜,在Linux核心原始碼中,你會看到大量的類似的操作。歸根到底,其實HAL的這種封裝,就是一種應用技巧。

參考文獻




打賞、訂閱、收藏、丟香蕉、硬幣,請關注公眾號(攻城獅的搬磚之路)
qrc_img

PS: 請尊重原創,不喜勿噴。

PS: 要轉載請註明出處,本人版權所有。

PS: 有問題請留言,看到後我會第一時間回覆。