天天看點

Android HAL(硬體抽象層)介紹以及調用

Android 的 HAL(Hardware Abstract Layer硬體抽象層)是Google因應廠商「希望不公開源碼」的要求下,所推出的新觀念,其架構如下圖。雖然 HAL 現在的「抽象程度」還不足,現階段實作還不是全面符合 HAL的架構規劃,不過也确實給了我們很好的思考空間。

Android HAL(硬體抽象層)介紹以及調用

圖1:Android HAL 架構規劃

這是 Patrick Brady (Google) 在2008 Google I/O 所發表的演講「Anatomy & Physiology of an Android」中,所提出的 Android HAL 架構圖。從這張架構圖我們知道,HAL 的目的是為了把 Android framework 與 Linux kernel 完整「隔開」。讓 Android 不至過度依賴 Linux kernel,有點像是「kernel independent」的意思,讓 Android framework 的開發能在不考慮驅動程式的前提下進行發展。

在 Android 原始碼裡,HAL 主要的實作儲存于以下目錄:

1. libhardware_legacy/ - 過去的實作、采取連結庫子產品的觀念進行

2. libhardware/ - 新版的實作、調整為 HAL stub 的觀念

3. ril/ - Radio Interface Layer

在 HAL 的架構實作成熟前(即圖1的規劃),我們先就目前 HAL 現況做一個簡單的分析。另外,目前 Android 的 HAL實作,仍舊散布在不同的地方,例如 Camera、WiFi 等,是以上述的目錄并不包含所有的 HAL 程式代碼。

2 HAL 的過去 

Android HAL(硬體抽象層)介紹以及調用

圖2:Android HAL / libhardware_legacy

過 去的 libhardware_legacy 作法,比較是傳統的「module」方式,也就是将 *.so 檔案當做「shared library」來使用,在runtime(JNI 部份)以 direct function call 使用 HAL module。透過直接函數呼叫的方式,來操作驅動程式。當然,應用程式也可以不需要透過 JNI 的方式進行,直接以加載 *.so 檔(dlopen)的做法呼叫*.so 裡的符号(symbol)也是一種方式。總而言之是沒有經過封裝,上層可以直接操作硬體。

3 HAL 的現況

Android HAL(硬體抽象層)介紹以及調用

圖3:Android HAL / libhardware

現 在的 libhardware 作法,就有「stub」的味道了。HAL stub 是一種代理人(proxy)的概念,stub 雖然仍是以 *.so檔的形式存在,但 HAL 已經将 *.so 檔隐藏起來了。Stub 向 HAL「提供」操作函數(operations),而 runtime 則是向 HAL 取得特定子產品(stub)的 operations,再 callback 這些操作函數。這種以 indirect function call 的實作架構,讓HAL stub 變成是一種「包含」關系,即 HAL 裡包含了許許多多的

stub(代理人)。Runtime 隻要說明「類型」,即 module ID,就可以取得操作函數。對于目前的HAL,可以認為Android定義了HAL層結構架構,通過幾個接口通路硬體進而統一了調用方式。

4 HAL_legacy和HAL的對比 

HAL_legacy:舊式的HAL是一個子產品,采用 共享庫形式,在編譯時會調用到。由于采用function 

call形式調用,是以可被多個程序使用,但會被mapping到多個程序空間中,造 成浪費,同時需要考慮代碼能否安全重入的問題(thread safe)。

HAL:新式的HAL采用HAL module和HAL stub結合形式,HAL stub不是一個share library,編譯時上層隻擁有通路HAL stub的函數指針,并不需要HAL stub。上層通過HAL module提供的統一接口擷取并操作HAL stub,so檔案隻會被mapping到一個程序,也不存在重複mapping和重入問題。

5 HAL module架構

HAL moudle主要分為三個結構:

struct hw_module_t;

struct hw_module_methods_t;

struct hw_device_t;

他 們的繼承關系如下圖:

Android HAL(硬體抽象層)介紹以及調用

圖4:Android HAL結構繼承關系

6 HAL使用方法 

(1)Native code通過hw_get_module調 用擷取HAL stub:

hw_get_module (LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module)

(2)通過繼承hw_module_methods_t的callback來 open裝置:

module->methods->open(module,

            LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);

(3)通過繼承 hw_device_t的callback來控制裝置:

sLedDevice->set_on(sLedDevice, led);

sLedDevice->set_off(sLedDevice, led);

7 HAL stub編寫方法

(1)定義自己的HAL結構體,編寫頭檔案led.h, hardware/hardware.h

struct led_module_t {

   struct hw_module_t common;

};

struct led_control_device_t {

   struct hw_device_t common;

   int fd;             /* file descriptor of LED device */

   /* supporting control APIs go here */

   int (*set_on)(struct led_control_device_t *dev, int32_t led);

   int (*set_off)(struct led_control_device_t *dev, int32_t led);

繼 承關系如下圖:

Android HAL(硬體抽象層)介紹以及調用

圖5:HAL stub與HAL module繼承關系

(2) 設計led.c 完成功能實作和HAL stub注冊

(2.1)led_module_methods繼承 hw_module_methods_t,實作open的callback

struct hw_module_methods_t led_module_methods = {

    open: led_device_open

(2.2)用 HAL_MODULE_INFO_SYM執行個體led_module_t,這個名稱不可修改

tag:需要制定為 HARDWARE_MODULE_TAG

id:指定為 HAL Stub 的 module ID

methods:struct hw_module_methods_t,為 HAL 所定義的「method」

const struct led_module_t HAL_MODULE_INFO_SYM = {

    common: {

        tag: HARDWARE_MODULE_TAG,

        version_major: 1,

        version_minor: 0,

        id: LED_HARDWARE_MODULE_ID,

        name: "Sample LED Stub",

        author: "The Mokoid Open Source Project",

        methods: &led_module_methods,

    }

    /* supporting APIs go here. */

(2.3)open是一個必須實作的callback API,負責申請結構體空間,填充資訊,注冊具體操作API接口,打開Linux驅動。

       由于存在多重繼承關系,隻需對子結構體hw_device_t對象申請空間即可。

int led_device_open(const struct hw_module_t* module, const char* name,

        struct hw_device_t** device) 

{

       struct led_control_device_t *dev;

       dev = (struct led_control_device_t *)malloc(sizeof(*dev));

       memset(dev, 0, sizeof(*dev));

       dev->common.tag = HARDWARE_DEVICE_TAG;

       dev->common.version = 0;

       dev->common.module = module;

       dev->common.close = led_device_close;

       dev->set_on = led_on;

       dev->set_off = led_off;

       *device = &dev->common;

       /*

         * Initialize Led hardware here.

         */

        dev->fd = open(LED_DEVICE, O_RDONLY);

       if (dev->fd < 0) 

           return -1;

       led_off(dev, LED_C608);

       led_off(dev, LED_C609);

success:

       return 0;

}

(2.4)填充具體API操作代碼

int led_on(struct led_control_device_t *dev, int32_t led)

       int fd;

       LOGI("LED Stub: set %d on.", led);

       fd = dev->fd;

       switch (led) {

              case LED_C608: 

                     ioctl(fd, 1, &led);

                     break;

              case LED_C609:

              default:

                     return -1;

       }

return 0;

int led_off(struct led_control_device_t *dev, int32_t led)

       LOGI("LED Stub: set %d off.", led);

                     ioctl(fd, 2, &led);

Android HAL 是如何被調用的

Android 對硬體的調用, google 推薦使用 HAL 的方式進行調用,對于 Andriod HAL 的寫法,可以參考android 源碼裡的 hardware 目錄下幾個子產品的模版。

在看 HAL 的編寫方法的過程中,會發現整個子產品貌似沒有一個入口。一般說來子產品都要有個入口,比如應用程式有 main 函數,可以為加載器進行加載執行, dll 檔案有 dllmain ,而對于我們自己寫的動态連結庫,我們可以對庫中導出的任何符号進行調用。

問題來了, Android 中的 HAL 是比較具有通用性的,需要上層的函數對其進行加載調用, Android 的 HAL加載器是如何實作對不同的 Hardware Module 進行通用性的調用的呢?

帶着這個疑問檢視 Android 源碼,會發現 Android 中實作調用 HAL 是通過 hw_get_module 實作的。

int hw_get_module(const char *id, const struct hw_module_t **module);

這是其函數原型, id 會指定 Hardware 的 id ,這是一個字元串,比如 sensor 的 id 是

#define SENSORS_HARDWARE_MODULE_ID "sensors" ,如果找到了對應的 hw_module_t 結構體,會将其指針放入 *module 中。看看它的實作。。。。

    /* Loop through the configuration variants looking for a module */

    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {

        if (i < HAL_VARIANT_KEYS_COUNT) {

             // 擷取 ro.hardware/ro.product.board/ro.board.platform/ro.arch 等 key 的值。

            if (property_get(variant_keys[i], prop, NULL) == 0) {

                continue;

            }

            snprintf(path, sizeof(path), "%s/%s.%s.so",

                    HAL_LIBRARY_PATH, id, prop);

              // 如果開發闆叫做 mmdroid, 那麼這裡的 path 就是system/lib/hw/sensor.mmdroid.so

        } else {

            snprintf(path, sizeof(path), "%s/%s.default.so",

                    HAL_LIBRARY_PATH, id);// 預設會加載 /system/lib/hw/sensor.default.so

         }

        if (access(path, R_OK)) {

            continue;

        }

        /* we found a library matching this id/variant */

        break;

    }

    status = -ENOENT;

    if (i < HAL_VARIANT_KEYS_COUNT+1) {

        /* load the module, if this fails, we're doomed, and we should not try

         * to load a different variant. */

        status = load(id, path, module);// 調用 load 函數打開動态連結庫

擷取了動态連結庫的路徑之後,就會調用 load 函數打開它,下面會打開它。

奧秘在 load 中

static int load(const char *id,

        const char *path,

        const struct hw_module_t **pHmi)

    int status;

    void *handle;

    struct hw_module_t *hmi;

    /*

     * load the symbols resolving undefined symbols before

     * dlopen returns. Since RTLD_GLOBAL is not or'd in with

     * RTLD_NOW the external symbols will not be global

     */

    handle = dlopen(path, RTLD_NOW);// 打開動态庫

    if (handle == NULL) {

        char const *err_str = dlerror();

        LOGE("load: module=%s/n%s", path, err_str?err_str:"unknown");

        status = -EINVAL;

        goto done;

    /* Get the address of the struct hal_module_info. */

    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;// 被定義為了“ HMI ”

    hmi = (struct hw_module_t *)dlsym(handle, sym);// 查找“ HMI ”這個導出符号,并擷取其位址

    if (hmi == NULL) {

        LOGE("load: couldn't find symbol %s", sym);

/* Check that the id matches */

// 找到了 hw_module_t 結構!!!

    if (strcmp(id, hmi->id) != 0) {

        LOGE("load: id=%s != hmi->id=%s", id, hmi->id);

    hmi->dso = handle;

    /* success */

    status = 0;

    done:

    if (status != 0) {

        hmi = NULL;

        if (handle != NULL) {

            dlclose(handle);

            handle = NULL;

    } else {

        LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",

                id, path, *pHmi, handle);

  // 凱旋而歸

    *pHmi = hmi;

     return status;

從上面的代碼中,會發現一個很奇怪的宏 HAL_MODULE_INFO_SYM_AS_STR ,它直接被定義為了#define HAL_MODULE_INFO_SYM_AS_STR  "HMI" ,為何根據它就能從動态連結庫中找到這個 hw_module_t結構體呢?我們檢視一下我們用到的 hal 對應的 so 就可以了,在 linux 中可以使用 readelf XX.so –s 檢視。

Symbol table '.dynsym' contains 28 entries:

   Num:     Value  Size Type    Bind   Vis      Ndx Name

     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 00000594     0 SECTION LOCAL  DEFAULT    7

     2: 00001104     0 SECTION LOCAL  DEFAULT   13

     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND ioctl

     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND strerror

     5: 00000b84     0 NOTYPE  GLOBAL DEFAULT  ABS __exidx_end

     6: 00000000     0 OBJECT  GLOBAL DEFAULT  UND __stack_chk_guard

     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0

     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND __errno

     9: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS _bss_end__

    10: 00000000     0 FUNC    GLOBAL DEFAULT  UND malloc

    11: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start__

    12: 00000000     0 FUNC    GLOBAL DEFAULT  UND __android_log_print

    13: 00000b3a     0 NOTYPE  GLOBAL DEFAULT  ABS __exidx_start

    14: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail

    15: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_end__

    16: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start

    17: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset

    18: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_uidiv

    19: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __end__

    20: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS _edata

    21: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS _end

    22: 00000000     0 FUNC    GLOBAL DEFAULT  UND open

    23: 00080000     0 NOTYPE  GLOBAL DEFAULT  ABS _stack

    24: 00001104   128 OBJECT  GLOBAL DEFAULT   13 HMI

    25: 00001104     0 NOTYPE  GLOBAL DEFAULT   13 __data_start

    26: 00000000     0 FUNC    GLOBAL DEFAULT  UND close

    27: 00000000     0 FUNC    GLOBAL DEFAULT  UND free

從上面中,第 24 個符号,名字就是“ HMI ”,對應于 hw_module_t 結構體。再去對照一下 HAL 的代碼。

/*

  * The COPYBIT Module

  */

struct copybit_module_t HAL_MODULE_INFO_SYM = {

    common: {

        tag: HARDWARE_MODULE_TAG,

        version_major: 1,

        version_minor: 0,

        id: COPYBIT_HARDWARE_MODULE_ID,

        name: "QCT MSM7K COPYBIT Module",

        author: "Google, Inc.",

        methods: &copybit_module_methods

這裡定義了一個名為 HAL_MODULE_INFO_SYM 的 copybit_module_t 的結構體, common 成員為hw_module_t 類型。注意這裡的 HAL_MODULE_INFO_SYM 變量必須為這個名字,這樣編譯器才會将這個結構體的導出符号變為“ HMI ”,這樣這個結構體才能被 dlsym 函數找到!

綜上,我們知道了 andriod HAL 子產品也有一個通用的入口位址,這個入口位址就是HAL_MODULE_INFO_SYM 變量,通過它,我們可以通路到 HAL 子產品中的所有想要外部通路到的方法。

轉自:http://blog.csdn.net/k229650014/article/details/5801397

繼續閱讀