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

1 HAL簡介


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

Android HAL 分析 - On<wbr>ce - 許興旺的博客

圖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 分析 - On<wbr>ce -  許興旺的博客

圖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 分析 - On<wbr>ce -  許興旺的博客

圖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 分析 - On<wbr>ce - 許興旺的博客

圖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 分析 - On<wbr>ce - 許興旺的博客

圖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:
                     ioctl(fd, 1, &led);
                     break;
              default:
                     return -1;
       }
return 0;
}

int led_off(struct led_control_device_t *dev, int32_t led)
{
       int fd;
       LOGI("LED Stub: set %d off.", led);
       fd = dev->fd;
       switch (led) {
              case LED_C608:
                     ioctl(fd, 2, &led);
                     break;
              case LED_C609:
                     ioctl(fd, 2, &led);
                     break;
              default:
                     return -1;
       }
       return 0;
}

 

 

 

 

<!-- /* Font Definitions */ @font-face {font-family:宋體; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@宋體"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋體; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} -->

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);

        status = -EINVAL;

        goto done;

    }

 

/* Check that the id matches */

// 找到了 hw_module_t 結構!!!

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

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

        status = -EINVAL;

        goto done;

    }

 

    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 模塊中的所有想要外部訪問到的方法。

發佈了9 篇原創文章 · 獲贊 6 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章