1. 程式人生 > >Android HAL(硬體抽象層)介紹以及呼叫

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