AndroidQ 打通應用層到HAL層---(HAL模塊實現)

從這篇文章開始準備研究應用層到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庫
hello.default.so

我們將這個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層的整個開發框架

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