從這篇文章開始準備研究應用層到HAL層的一整套流程,目標是寫一個APP調用HAL的一個函數,在AOSP源碼環境下進行開發,大概流程是:
APP---->Framework service---->native----->HAL
什麼是HAL
HAL全稱Hardware Abstract Layer,硬件抽象層,它向下屏蔽了硬件的實現細節,向上提供了抽象接口,HAL是底層硬件和上層框架直接的接口,框架層通過HAL可以操作硬件設備,HAL的實現在用戶空間
爲什麼需要HAL
我們知道Android是基於Linux進行開發的,傳統的Linux對硬件的操作基本上都是在內核中,而Android把對硬件的操作分爲了兩部分,HAL和內核驅動,HAL實現在用戶空間,驅動在內核空間,這是因爲Linux內核中的代碼是需要開源的,如果把對硬件的操作放在內核這會損害硬件廠商的利益,因爲這是別人廠商的商業機密,而現在有了HAL層位於用戶空間,硬件廠商就可以將自己的核心算法之類的放在HAL層,保護了自己的利益,這樣Android系統才能得到更多廠商的支持
HAL實現的一般規則
每種硬件都對應了一個HAL模塊,要向實現自己的HAL,必須滿足HAL的相關規則,規則定義在源碼hardward目錄下,頭文件hardward.h,C文件hardward.c
hardward.h中定義了三個重要的結構體:
struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;
其實HAL的實現使用了C中結構體繼承的技巧,當然這種繼承並不是真正意義的繼承,而是一種結構體強制轉換,我們可以理解爲繼承
結構體hw_module_t代表HAL模塊,自己定義的HAL模塊必須包含一個自定義struct,且必須”繼承” hw_module_t(即第一個變量必須爲 hw_module_t),且模塊的tag必須指定爲HARDWARE_MODULE_TAG,代表這是HAL模塊的結構體
結構體hw_module_methods_t代表模塊的操作方法列表,它內部只有一個函數指針open,用來打開該模塊下的設備
結構體hw_device_t代表該模塊下的設備,自己定義的HAL模塊必須包含一個結構體,且必須“繼承” hw_device_t(即第一個變量必須爲hw_device_t)
每個自定義HAL模塊還有一個模塊名和N個設備名(標識模塊下的設備個數,一個模塊可以有多個設備),
最後這個模塊定義好之後還必須導出符號HAL_MODULE_INFO_SYM,指向這個模塊,HAL_MODULE_INFO_SYM定義在hardware.h中值爲“HMI”
接下來就手動實現一個HAL模塊,這個HAL提供一個加法函數
首先在hardware/libhardware/include/hardware目錄下創建hello.h文件
#include <sys/cdefs.h>
#include <sys/types.h>
#include <hardware/hardware.h>
//HAL模塊名
#define HELLO_HARDWARE_MODULE_ID "hello"
//HAL版本號
#define HELLO_MODULE_API_VERSION_1_0 HARDWARE_MODULE_API_VERSION(0, 1)
//設備名
#define HARDWARE_HELLO "hello"
//自定義HAL模塊結構體
typedef struct hello_module {
struct hw_module_t common;
} hello_module_t;
//自定義HAL設備結構體
typedef struct hello_device {
struct hw_device_t common;
//加法函數
int (*additionTest)(const struct hello_device *dev,int a,int b,int* total);
} hello_device_t;
//給外部調用提供打開設備的函數
static inline int _hello_open(const struct hw_module_t *module,
hello_device_t **device) {
return module->methods->open(module, HARDWARE_HELLO,
(struct hw_device_t **) device);
}
我們可以看到在hello.h中自定義了兩個結構體,分別繼承hw_module_t和hw_device_t且在第一個變量,滿足前面說的規則,另外定義在device結構體中的函數的第一個參數也必須是device結構體
接着在hardware/libhardware/modules/目錄下創建一個hello的文件夾,裏面包含一個hello.c和Android.bp,我們來看hello.c文件
#define LOG_TAG "HelloHal"
#include <malloc.h>
#include <stdint.h>
#include <string.h>
#include <log/log.h>
#include <hardware/hello.h>
#include <hardware/hardware.h>
//加法函數實現
static int additionTest(const struct hello_device *dev,int a,int b,int *total)
{
if(!dev){
return -1;
}
*total = a + b;
return 0;
}
//關閉設備函數
static int hello_close(hw_device_t *dev)
{
if (dev) {
free(dev);
return 0;
} else {
return -1;
}
}
//打開設備函數
static int hello_open(const hw_module_t* module,const char __unused *id,
hw_device_t** device)
{
if (device == NULL) {
ALOGE("NULL device on open");
return -1;
}
hello_device_t *dev = malloc(sizeof(hello_device_t));
memset(dev, 0, sizeof(hello_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = HELLO_MODULE_API_VERSION_1_0;
dev->common.module = (struct hw_module_t*) module;
dev->common.close = hello_close;
dev->additionTest = additionTest;
*device = &(dev->common);
return 0;
}
static struct hw_module_methods_t hello_module_methods = {
.open = hello_open,
};
//導出符號HAL_MODULE_INFO_SYM,指向自定義模塊
hello_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = HELLO_MODULE_API_VERSION_1_0,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = HELLO_HARDWARE_MODULE_ID,
.name = "Demo Hello HAL",
.author = "[email protected]",
.methods = &hello_module_methods,
},
};
前面我們說過,要想自定義的HAL模塊被外部使用需要導出符號HAL_MODULE_INFO_SYM,導出的這個模塊結構體最重要的其實就是tag,id和methods,tag必須定義爲HARDWARE_MODULE_TAG,id就是在hello.h中定義的模塊名,methods指向hello_module_methods地址,hello_module_methods指向結構體hw_module_methods_t,它裏面的open函數指針作用是打開模塊下的設備,它指向hello_open函數
hello_open
static int hello_open(const hw_module_t* module,const char __unused *id,
hw_device_t** device)
{
if (device == NULL) {
ALOGE("NULL device on open");
return -1;
}
hello_device_t *dev = malloc(sizeof(hello_device_t));
memset(dev, 0, sizeof(hello_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = HELLO_MODULE_API_VERSION_1_0;
dev->common.module = (struct hw_module_t*) module;
dev->common.close = hello_close;
dev->additionTest = additionTest;
*device = &(dev->common);
return 0;
}
其實hello_open函數也很簡單,就是創建一個hello_device_t結構體,這是我們自定義HAL模塊下的設備結構體,然後給這個結構體賦值,我們定義的加法函數additionTest賦值給設備結構體的函數指針,外部就可以使用,因爲這個HAL模塊導出了符號HAL_MODULE_INFO_SYM,所以我們能通過模塊id找到這個HAL模塊,有了模塊之後就可以調用它的hello_open函數打開設備,hello_open接收三個參數,module代表這個HAL模塊,id代表要打開的是這個模塊下哪一個設備,最後一個參數是一個二級指針,其實是我們自定義設備結構體指針的地址,傳個地址過來就可以給這個設備結構體賦值
這樣最簡單的一個HAL模塊就定義好了,它僅僅提供了一個加法函數,對於Android.bp如下:最終編譯成一個共享lib庫
cc_library_shared {
name: "hello.default",
relative_install_path: "hw",
proprietary: true,
srcs: ["hello.c"],
header_libs: ["libhardware_headers"],
shared_libs: ["liblog"],
}
我們在根目錄執行如下命令來編譯一下:
mmm hardware/libhardware/modules/hello/
編譯成功後生成了一個hello.default.so共享lib庫
我們將這個so庫push進手機/system/lib64路徑下,接着需要寫一個簡單測試程序來測一下
我們在HAL模塊目錄下再創建一個test目錄,test目錄下創建一個HelloTest.cpp測試文件和一個Android.bp文件,目錄結構如下:
HelloTest.cpp源碼如下:
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <log/log.h>
#define TAG "HelloHal"
int main(){
hello_device_t* hello_device = NULL;
const hello_module_t * module = NULL;
int ret = hw_get_module(HELLO_HARDWARE_MODULE_ID,(const struct hw_module_t**)&module);
if (!ret) {
ret = _hello_open((const struct hw_module_t*)module,&hello_device);
}
if (ret < 0) {
ALOGD("get hello-hal failed.......");
return -1;
}
int total = 0;
hello_device->additionTest(hello_device,3,5,&total);
ALOGD("success start hello-hal....total = %d",total);
return 0;
}
通過hardware.c提供的hw_get_module函數,傳遞模塊名獲取對應的HAL模塊,接着調用我們自定義的_hello_open函數通過獲取到的模塊打開設備,獲取到設備之後,我們調用定義在設備結構體中的加法函數
Android.bp定義如下:
cc_binary {
name: "hello_hal",
srcs: ["HelloTest.cpp"],
shared_libs: [
"liblog",
"libhardware",
],
}
這個test會被編譯成二進制可執行文件:hello_hal
我們將這個可執行文件push進手機system/bin目錄,然後執行
可以看到log已經成功打印:
到此我們最簡單的HAL就已經實現了,這個HAL並不設計底層驅動,實際開發中自己可以在設備結構體中定義函數訪問底層硬件
後續文章會接着寫從native層通過HIDL訪問HAL,以及framework層通過JNI訪問native層,以及通過應用層APK通過AIDL訪問framework層,打通應用層到HAL層的整個開發框架