Android 的 HAL(Hardware Abstract Layer硬體抽象層)是Google因應廠商「希望不公開源碼」的要求下,所推出的新觀念,其架構如下圖。雖然 HAL 現在的「抽象程度」還不足,現階段實作還不是全面符合 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 的過去
圖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 的現況
圖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;
他 們的繼承關系如下圖:
圖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);
繼 承關系如下圖:
圖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