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有一個基礎的瞭解後,才建議閱讀本文。
首先Android HAL分為大概分為兩個版本,一個新的和舊的,本文重點分析新版HAL原理。其中兩種版本架構大概簡介如下:
上面對新的架構說的還是有些表面了,下面我們深入分析其中的重要的幾個結構(在hardware.h中),然後最後通過一個實際例子來深入理解它。
它的實際原始碼定義如下:
/**
* 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的結構體記憶體佈局和暴露這個結構體的名字,後面會詳細說這個事情。
它的實際原始碼定義如下:
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,然後有開啟裝置的操作。這裡也體現出來了一個模組可以有多個裝置的這種概念。
它的實際原始碼定義如下:
/**
* 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);
如上,我們已經介紹了hal裡面的重要的3個結構體,但是如果就到此的話,其實我們對hal還是一知半解,這個時候,我們可以嘗試自己虛擬一個硬體出來,然後設計HAL介面。這樣可以實際體會HAL的工作原理。
首先,我們定義我們的模組叫做MY_HW。
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個結構體的繼承和實現。同時對一些成員變數進行賦值。
__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
我們先來看看我們應用程式執行的結果如下:
我們也可知道,在hardware.c中,hw_get_module是根據id來在特定目錄中去搜尋相關的模組so,然後通過dlopen開啟它並進行後續的操作。如我修改的hardware.c部分節選:
同理,到了這裡,我們不用猜測,一定在MY_HW.sky-sdk.so暴露了一個HMI的符號。如圖:
注意hardware.c和hardware.h直接從android原始碼中拿出來,簡單做修改即可在linux裡面編譯。這裡我們簡單看看libhardware.so的符號暴露資訊:
這裡其實就暴露了上面提到的兩個介面,hw_get_module和hw_get_module_by_class。
總的來說:
注意三個結構體之間的關係: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的這種封裝,就是一種應用技巧。
PS: 請尊重原創,不喜勿噴。
PS: 要轉載請註明出處,本人版權所有。
PS: 有問題請留言,看到後我會第一時間回覆。