---
title: 'HAL 設計、加載分析'
disqus: kyleAlien
---
HAL - HAL 設計、加載分析
===
## Overview of Content
如有引用參考請詳註出處,感謝 :cat:
[TOC]
## Android HAL 概述
**Android 的硬體抽象層 (Hardware Abstact Layer, HAL) 運行在 ++使用者空間++**,並對上層 Native 提供硬體存取服務;
### Android HAL 建立 - 理由
* 傳統 Linux 驅動:一般會有兩個主要行為參雜在內部 ^1.^ 訪問硬體暫存器的程式、^2.^ 處理硬體資訊的業務邏輯
```mermaid
graph TB;
subgraph 傳統_Linux_驅動
訪問硬體暫存器的程式
處理硬體資訊的業務邏輯
end
```
* Android 裝置則是有更多個硬體廠商會參與(有更多的利益關係),而 Linux 是支援 GPL 協議的,所以必須公開代碼,但這並不是廠商想見的
> 廠商會想保護自身硬體操作的業務邏輯
藉此 Android 系統設計 **兩層來支援硬體**
1. **使用者空間 - HAL 設計**
:::info
* 相較之下傳統的 Linux 會將硬體支援全部寫在 **核心空間**
:::
2. **核心空間 - Linux 核心代碼**
詳細說說協議吧~ Android 為何畫分兩層 ? 其實就像上面所說的,都是「利益」、「財產關係」呀!
:::success
* **GPL 規範**:為了規避 GPL 規範,又保證硬體廠商的利益
GPL 是由自由軟體基金會(FSF)開發的一種軟體授權協議,它主要強調軟體的自由和共享。GPL 的主要特點包括:
* 任何使用、修改、分發 GPL 軟體的衍生作品的人,都必須遵循 GPL 的條款,這包括「**公開發佈原始碼**」
> 基於廠商利益,廠商通常不喜歡 GPL 協議
* 衍生軟體必須同樣使用 GPL 授權(代表 **衍生軟體必須開源**),以確保後續的版本也能夠自由分發和修改
* **Apache 規範**:允許硬體廠商修改 Android Source Code 而不用公開
Apache 許可協議是一種寬鬆的軟體授權協議,它由 Apache 軟體基金會開發;與 GPL 不同,**Apache 許可協議允許更大的彈性,並且主張開發者可以更容易地將他們的軟體與其他軟體集成**;主要特點包括:
* 允許將 Apache 授權的軟體與封閉源碼軟體結合,並可以在封閉源碼軟體中使用。
* **不要求衍生軟體使用相同的授權協議**(只須標注 Notice 文件)
> 把對於硬體的支援實現在 使用者空間,就可以隱藏硬體參數,但這是不可能做到的,因為 **只有核心空間有權限操作硬體裝置**
:::
### Android HAL 優點 - 方式
* Android 最終方案:**分別在 `核心` & `使用者` 空間 實現硬體支援**
* **核心**:核心空間仍使用硬體驅動來支援,但它只是簡單地提供了硬體存取通道 (跟硬體溝通)
> 簡易符合 GPL 規範
* **使用者**:使用者空間以硬體抽象層模組的形式支援 (這樣就可以實現 Apache 2.0 規範,封裝硬體時現的細節、參數)
> Apache 規範可以隱藏細節,在程式中只須附上 Notice 文件即可
```mermaid
graph LR;
subgraph 使用者空間_HAL
業務邏輯
end
使用者空間_HAL -.-> |符合 Apache| Apache_可不公開
subgraph 核心空間_Linux_驅動
訪問暫存器
end
核心空間_Linux_驅動 -.-> |符合 GPL| GPL_公開
業務邏輯 --> 訪問暫存器
```
> 
* **Android HAL 設計的優點在於**
1. **統一硬體的調用界面**:
相較於 Linux 對於硬體規劃的多種界面層級(函數指標),Android HAL 會更加有「標準」
2. **解決了利益問題**:
讓硬體廠商保有各自的硬體訪問邏輯,將邏輯建構在 Android HAL 層
3. **讓硬體訪問使用者空間**:
某些特殊硬體會有需求,要訪問使用者空間的一些資源(或是在內核空間不方便完成的工作),這實在使用者空間訪問就相對方便
### Android HAL - 架構
* Android HAL 架構大致上可以分為兩種
1. **早期 Android HAL**
設計較為簡單,基本原理就是
* Android 系統中使用動態庫(`.so` 文件),並透過 HAL 中的動態庫訪問核心 Linux 驅動
* 上層應用 (Application) 則透過 NDK,或是直接訪問的方式去調用 HAL 中的程序庫(`.so` 文件)
```mermaid
graph TB;
subgraph 使用者空間
應用層 --> NDK_JNI --> HAL
應用層 --> HAL
end
subgraph 核心空間
Linux_驅動
end
HAL --> Linux_驅動
```
2. **穩定版 Android HAL**
基礎的架構沒有變,主要是新增了一些層級、設計,使的這些 `.so` 庫,可以 **自動被 Android 系統識別並調用**
> 也就是多了架構設計
* 不須手動加載 `.so` 庫(驅動層程式),而是**透過 ID 訪問** 並由系統幫我們加載
* 在 HAL 跟 JNI 之前之間多了 Service 程序庫(某個 `.so` 庫)
```mermaid
graph TB;
subgraph 使用者空間
Service.so
應用層 --> NDK_JNI --> Service.so
應用層 --> Service.so -.-> |將 so 文件隱藏 | HAL
end
subgraph 核心空間
Linux_驅動
end
HAL --> Linux_驅動
```
### Android HAL 執行檔位置
* Android HAL 源代碼的位置並不固定,但大致上會放在兩個位置
1. **早期設計 `<android source>/hardware/libhardware_legacy` 目錄**:用來儲存舊有 HAL 架構的源代碼
> 
2. **穩定設計 `<android source>/hardware/libhardware` 、`<android source>/hardware` 目錄**:
> 
* Android source 經過編譯過後,會在 `<android source> /out/target/product/generic/system/lib/hw` 目錄之下產生對應的 `.so` 文件
```shell=
# 查找 hw 資料夾
find . -name hw -type d
```
> 
## Android HAL 結構設計
我們知道 Android HAL 層的功能是定義一個抽象接口(函數指標),**讓 Android 軟體 與 裝置硬體做出隔離**
>HAL 也是與 Linux Kernel 之間通信的唯一接口,HAL 有以下特點
1. **抽象接口**:解開與每個硬體裝置的耦合度 (每個 BSP 都會根據硬體實現),有了這個 protocol 就可以不理會硬體實現
2. **穩定性**:HAL 接口必須穩定,不能常常修改,也保證其兼融性
3. **靈活性**:方便上層對於不同硬體使用
對於不同硬體手機廠商而言,**硬體廠商只需要專注在對於 Andoid HAL 層的定義實現**; **軟體廠商只需要將 HAL 的 so 取來放在指定路徑即可**
> 
### Android HAL 區塊分類
* 撰寫 Android HAL 的步驟是稍加麻煩的(這是因為多了一層架構的原因,所以比起撰寫 Linux 驅動複雜),但也是因為架構,會讓 Android HAL 驅動更加具有獨立性、好維護性
具體來說會經過以下步驟
1. **編寫 Linux 驅動**(核心空間)
這個步驟並非一般的 Linux 驅動程序,而是只有簡單的訪問(讀取、寫入… 等等)指定硬體的暫存器,如果是驅動的業務邏輯則留在 HAL 層(不留在這層)
2. **編寫 HAL 驅動業務邏輯**(使用者空間)
撰寫 HAL 驅動業務邏輯,並且經過編譯後,就會產出一個 `.so` 文件(相當於 Linux Library)
:::info
* 並且這類 `.so` 庫只會有一個界面,而 Android 系統會透過 **`HAL_MODULE_INFO_SYM` 符號** 加載 `.so` 庫!
:::
3. **編寫 Service Library**,非必須但 Android 官方建議(使用者空間)
Service Library 也是 `.so` 文件,可以是 Linux Library,也可以是 JNILibrary
```mermaid
graph TB;
Java_應用 --> ServiceManager --> |ServiceLibrary| OS --> HAL --> Linux_驅動
```
### Android HAL - 三大結構
> 接下來看 Android HAL 的一些重要結構
1. **`hw_module_t` 結構**:(設定 Android HAL 資訊)
* **`hw_module_t` 下的 `tag` 成員必須是 `HARDWARE_MODULE_TAG`**
* **模組中必須要有 `HAL_MODULE_INFO_SYM` 符號**,而這個符號的類型就是 `hw_module_t`
> `HARDWARE_MODULE_TAG` 就是 `HWMT`
>
> `HAL_MODULE_INFO_SYM` 就是 `HMI`
:::success
這三大結構之中,`hw_module_t` 結構是最先被找到的
:::
```c=
// hardware.h
#define HAL_MODULE_INFO_SYM HMI
#define MAKE_TAG_CONSTANT(A,B,C,D) (((A) << 24) | ((B) << 16) | ((C) << 8) | (D))
#define HARDWARE_MODULE_TAG MAKE_TAG_CONSTANT('H', 'W', 'M', 'T')
typedef struct hw_module_t {
// 必須是 `HARDWARE_MODULE_TAG`
uint32_t tag;
uint16_t module_api_version;
#define version_major module_api_version
uint16_t hal_api_version;
#define version_minor hal_api_version
/** Identifier of module */
// 通過 id 可以找到該模塊
const char *id;
/** Name of this module */
const char *name;
/** Author/owner/implementor of the module */
const char *author;
/** Modules methods */
// 與模塊相關的函數指標
struct hw_module_methods_t* methods;
/** module's dso */
void* dso;
#ifdef __LP64__
uint64_t reserved[32-7];
#else
/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];
#endif
} hw_module_t;
```
:::info
* dso 是載入 module 的控制句柄,載入 so 的簡單順序如下
1. `dlopen` 開啟 xxx.so
2. 設定給 `hw_module_t`#`dso` 成員
:::
2. **`hw_device_t` 結構**:(驅動相關訊息)
每個硬體必須定義自己的硬體裝置結構體,透過該結構可以關閉硬體裝置;**透過該結構可以 ++設定硬體裝置所需的操作函數++**
* **`hw_module_t`其成員 `tag` 必須是 `HARDWARE_DEVICE_TAG`**
> `HARDWARE_DEVICE_TAG` 就是 `HWDT`
```c=
// hardware.h
#define MAKE_TAG_CONSTANT(A,B,C,D) (((A) << 24) | ((B) << 16) | ((C) << 8) | (D))
#define HARDWARE_DEVICE_TAG MAKE_TAG_CONSTANT('H', 'W', 'D', 'T')
typedef struct hw_device_t {
// 必須是 `HARDWARE_DEVICE_TAG`
uint32_t tag;
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** padding reserved for future use */
#ifdef __LP64__
uint64_t reserved[12];
#else
uint32_t reserved[12];
#endif
/** Close this device */
// 關閉設備的指標函數
int (*close)(struct hw_device_t* device);
} hw_device_t;
```
:::info
* 開啟硬體是由 Module 負責,關閉則是由硬體裝置負責
:::
3. **`hw_module_methods_t` 結構**:
該結構相當於 HAL 驅動的入口,並會在這之中找到 `hw_module_t`、`hw_device_t` 並初始化結構
```c=
// hardware.h
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module,
const char* id,
// 這時傳入的是 自己定義的 hw_device_t 結構
struct hw_device_t** device);
} hw_module_methods_t;
```
:::info
* 一個抽象模組可以對應到多個硬體裝置,而這個我們可以透過 `id` 參數來分辨
:::
結構的關係如下
> 
### Android HAL 設定範例
* 舉一個例子 (Power),HAL 任何 Module 都必須按照規則
1. **Symbol 規定**:每個 Module 必須要有一個 `HAL_MODULE_INFO_SYM` 變量; 以下為 [**power**](https://cs.android.com/android/platform/superproject/+/master:hardware/qcom/power/power.c) 模塊定義
```c=
// /qcom/power/power.c
// 宣告 HAL_MODULE_INFO_SYM 變量 (符合條件 1)
struct power_module HAL_MODULE_INFO_SYM = {
// .common 也就是 hw_module_t (符合條件 2)
.common = {
// 必須使用 `HARDWARE_MODULE_TAG` tag (符合條件 3)
.tag = HARDWARE_MODULE_TAG,
.module_api_version = POWER_MODULE_API_VERSION_0_2,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = POWER_HARDWARE_MODULE_ID,
.name = "Qualcomm Power HAL",
.author = "The Android Open Source Project",
.methods = &power_module_methods,
},
.init = power_init,
.setInteractive = power_set_interactive,
.powerHint = power_hint,
};
```
2. **`hw_module_t` 拓展**:每個 struct 必須要用 `hw_module_t` 作為開頭;同樣我們用 power 模塊,查看 `power_module` 結構
```c=
// hardware/libhardware/include/hardware/power.h
typedef struct power_module {
// 以 hw_module_t 開頭
struct hw_module_t common;
...
} power_module_t;
```
> 
### 撰寫 Android HAL 順序
> 接下來針對「編寫 HAL 驅動業務邏輯」這個步驟來拓展
* **「宣告」結構**(宣告兩個新構體,並將指定結構至於「第一位置」,類似於「繼承」的概念)
1. **宣告包裝 `hw_module_t` 結構**:
HAL 會要求使用者,使用一個結構体(`struct`)包裝 `hw_module_t` 結構,並且要 **++放置第一個位置++**
```c=
struct my_module_t {
// 包裝指定的 hw_module_t 結構
struct hw_module_t common;
};
```
2. **宣告包裝 `hw_device_t` 結構**
HAL 同樣會要求使用者,使用一個結構体(`struct`)包裝 `hw_device_t` 結構,並且要 **++放置第一個位置++**
```c=
struct my_device_t {
// 符合規定以 `hw_device_t` 開頭
struct hw_device_t common;
// 在這個結構中也可定義操作驅動的私人方法
};
```
:::info
* **設定 HAL Moduel ID**:
由於 Android 會自動幫我們加載驅動(我們不須手動加載 so),而我們需要做的則是設定一個 ID… 之後透過該 ID 來取得驅動服務!
> 之後大多是給 NDK 層使用
```c=
#define MY_HARDWARE_ID "my_hardware"
```
:::
* **「實做」結構**
1. **實做 ++關閉驅動++ 裝置的函數**
這個步驟主要是要釋放 `my_device_t` 結構
```c=
int dev_fd = 0;
static int my_device_close(struct hw_device_t* device) {
my_device_t* dev = (my_device_t*) device;
if(dev) {
free(dev); // 釋放記憶體空間
}
close(dev_fd); // 關閉文件
}
```
2. **實做 ++初始化驅動++ 裝置的函數**:
並在初始化函數之中使用 `malloc` 創建,自己的 `my_device_t` 結構;並在這裡設定基礎的驅動訊息(像是 tag, version, module, close 函數... 等等)
* **`hw_module_t`#tag 必須定義為 HARDWARE_DEVICE_TAG**
* **轉型 `hw_module_t`#module** 為自己的 Module 類型
* **設置 `hw_module_t`#close 函數**,當驅動關閉時,自動會呼叫該函數
* **使用 open 函數開啟驅動,並保存返回的 fd 值**
```c=
static int my_device_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device) {
struct my_device_t *myDev;
// 動態分配驅動內存
myDev = (strucr my_device_t *) malloc(sizeof(*myDev));
// 初始化結構
memset(myDev, 0, sizeof(*myDev));
// 基礎,必須設置的成員
myDev->hw_device.tag = HARDWARE_DEVICE_TAG;
myDev->hw_device.version = 0;
myDev->hw_device.module = (my_module_t*) module; // 強制轉型為自己的 module
myDev->hw_device.close = my_device_close;
dev_fd = open("/dev/my_dev", O_RDWR);
}
```
3. **實做 `hw_module_methods_t` 結構**:
把開啟驅動的函數(上面的 `my_device_open`)設置給 `hw_module_methods_t`#open 成員
```c=
static struct hw_module_methods_t my_module_methods {
open. = my_device_open
}
```
3. **定義「`HAL_MODULE_INFO_SYM`」符號,並實做 `my_module_t` 結構**:
而其中的 `.tag` 要定義為「`HARDWARE_MODULE_TAG`」
:::success
* **`HAL_MODULE_INFO_SYM` 符號正是 Android HAL 架構下的入口!**(所有的 HAL 模塊都需要有)
:::
```c=
struct my_module_t HAL_MODULE_INFO_SYM = {
.common =
{
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = MY_HARDWARE_MODULE_ID, // 定義 Module ID
.name = "",
.author = MODULE_AUTHOR,
.methods = &my_module_methods, // 定義開啟硬體方法(上一步)
},
};
```
### Android HAL 拓展 - gralloc
* 對於某一類硬體設備來說,它提供的 HAL 接口必須穩定不變,而 Android 系統也有預定好的 HAL 接口 ([**libhardware**](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/include/hardware/) 資料夾下),而其他硬體廠商只需要實作這個接口即可
> 
從上圖我們可以發現 `gralloc.h` 檔案,它就有使用 HAL 定義的接口 (`hw_module_t`),並在該結構中拓展自己需要的函數指標
```c=
// gralloc.h
typedef struct gralloc_module_t {
// HAL Module 規範
struct hw_module_t common;
// 以下為 gralloc 模組拓展的函數指標
int (*registerBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle);
int (*unregisterBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle);
int (*lock)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr);
int (*unlock)(struct gralloc_module_t const* module,
buffer_handle_t handle);
int (*perform)(struct gralloc_module_t const* module,
int operation, ... );
int (*lock_ycbcr)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
struct android_ycbcr *ycbcr);
int (*lockAsync)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr, int fenceFd);
int (*unlockAsync)(struct gralloc_module_t const* module,
buffer_handle_t handle, int* fenceFd);
int (*lockAsync_ycbcr)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
struct android_ycbcr *ycbcr, int fenceFd);
int32_t (*getTransportSize)(
struct gralloc_module_t const* module, buffer_handle_t handle, uint32_t *outNumFds,
uint32_t *outNumInts);
int32_t (*validateBufferSize)(
struct gralloc_module_t const* device, buffer_handle_t handle,
uint32_t w, uint32_t h, int32_t format, int usage,
uint32_t stride);
/* reserved for future use */
void* reserved_proc[1];
} gralloc_module_t;
```
### HAL 編譯方式
* 由於 Android HAL 模塊屬於 Android 系統的一部分(不屬於 Kernel),所以需要依賴 Android source 中許多頭文件… 有以下方式可以作到
1. **將驅動源碼放置到 `<Android source>/hardware/` 目錄下**
2. **透過 `ln` 指令,將驅動目錄鏈結到 `<Android source>/harware` 目錄下**
* 並在自己的「驅動」源碼目錄下創建 `Android.mk` 檔
```shell=
## Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 模組編譯或是源碼編譯都可以
LOCAL_MODULE_TAG := optional
# 預編譯模式
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_RELATIVE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.cpp
# 驅動 module (最終會輸出的指定目錄)
LOCAL_MODULE := hello.default
# so library
include $(BUILD_SHARED_LIBRARY)
```
編譯後,實際輸出 `hello.default.so` 到 `<Android source>/system/lib64/hw/` 目錄下
> 
## Android HAL 加載分析
Android HAL 的加載,官方建議使用 Service 程序庫(也是個 `.so` 檔),通常指的就是 JNI
### hw_get_module 判斷庫 - 路徑規則
* **Android 載入硬體驅動**:
**使用 [hardware](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/hardware.c)#`hw_get_module` 函數加載 HAL 模塊**
我們不在需要手動加載 so 庫,只要 **給定指定的 ID 就可以加載** (要與註冊的驅動裝置相同 ID),接下來主要分析 `hw_get_module` 函數
```cpp=
// hardware.h
// 回傳 0 代表成功,小於 0 代表失敗
int hw_get_module(const char *id, const struct hw_module_t **module);
```
:::info
* 範例 - **Gralloc 模塊加載**:
```cpp=
// gralloc.h
#define GRALLOC_HARDWARE_MODULE_ID "gralloc"
// --------------------------------------------------------
// hardware.c
// @ 追蹤 hw_get_module
hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
```
:::
* **分析 `hw_get_module` 方法**:
這個方法可以拿來加載 HAL 硬體廠商實現的 `.so` 或是自己實現的 `.so` 檔(也就是 HAL 驅動的 `.so` 檔)
> Android 系統尋找 HAL Module(`.so` 檔)是有一定順序(策略)的
```cpp=
// hardware.c
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
int hw_get_module(const char *id, const struct hw_module_t **module)
{
// @ 追蹤 hw_get_module_by_class
return hw_get_module_by_class(id, NULL, module);
}
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int i = 0;
// PATH_MAX 是 4096
char prop[PATH_MAX] = {0}; // 最終 so 的名稱
char path[PATH_MAX] = {0};
char name[PATH_MAX] = {0}; // so 的起頭名稱
char prop_name[PATH_MAX] = {0}; // 屬性名稱
if (inst)
... 省略目前傳入 NULL
else
// 將 "Module_ID" 複製到 name 中
strlcpy(name, class_id, PATH_MAX);
// 1. 判斷是否有 `ro.hardware.<Module_ID>` 屬性
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
// 取得對應的 so 名稱
if (property_get(prop_name, prop, NULL) > 0) {
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
// 2. 判斷是否有指定屬性,有的話則將該屬性的值放到 prop 陣列
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
// 3. 以上預設都找不到,則呼叫 hw_module_exists 函數(並帶入 `default`)
// @ 查看 hw_module_exists 方法
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
found:
// 加載 Module
// @ 查看 load 函數
return load(class_id, path, module);
}
```
**Android HAL 加載 so 庫的判斷策略如下**:
1. **載入 `ro.hardware.*` 開頭屬性**:如果有設定 `ro.hardware.<Module_ID>` 屬性,取得對應的 so 庫
Android 系統的屬性文件共有 4 個
* `/default/prop`
* `/system/build.prop`
* `/system/default.prop`
* `/data/local.prop`
:::info
* 每個版本的屬性設置都有可能不同
我們可以透過 `_system_properties.h` 文件查看到對應的宏設置
```shell=
# 切換到 android source 目錄
cd android_source/
# 查看定義屬性的宏設定
./bionic/libc/include/sys/_system_properties.h
```
> 
:::
2. **找屬性文件… 同樣先判斷系統是否有指定的屬性,再查找指定預設的檔案**:這些屬性都會對應到另外一個數值 (就是 key / value)
* <Module_ID>.`ro.hardware`.so
:::success
* `ro.hardware` 具體的設定值為 init 進程負責
1. init 進程首先會讀取 `/proc/cmdline` 檔案,查看是否有一個 `androidboot.hardware` 屬性,如果有則取出當成 `ro.hardware` 值
> 
2. 如果第一點沒有,則判斷 `/proc/cpuinfo` 的內容,並賦予 `ro.hardware` 值
>
:::
* <Module_ID>.`ro.product.board`.so
* <Module_ID>.`ro.board.platform`.so
* <Module_ID>.`ro.arch`.so
3. **如果都找不到 so ! 則使用預設的 <MODULE_ID\>.`default`.so 檔案**
```cpp=
hw_module_exists(path, sizeof(path), name, "default")
```
:::info
* 這邊可以看到,**它帶入了 "default" 關鍵字**
這有就是為甚麽我們在編寫驅動程式時,`LOCAL_MODULE` 的值都會設置有一個 `default` 名;
因為要讓最終編譯出來的驅動 so 檔名為 `<module>.default.so`
```shell=
## Android.mk
...
LOCAL_MODULE := hello.default
```
:::
* **分析 `hw_module_exists` 函數**:判斷指定路徑(`path`)下是否有檔案,其路徑判斷同樣有順序 (`{64}` 是 64 位元裝置使用)… 預設路徑則如下
1. 預設路徑:`/system/lib{64}/hw`
> 我們自己撰寫的驅動檔經過編譯後,都會保存到 `/system/lib{64}/hw` 目錄下
2. 預設路徑:`/vendor/lib{64}/hw`
3. 預設路徑:`/odm/lib{64}/hw`
```c=
// hardware.c
// HAL 模塊所在的位置
#if defined(__LP64__) // 64 位元
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib64/hw"
#else // 32 位元
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib/hw"
#endif
static int hw_module_exists(char *path, size_t path_len, const char *name,
const char *subname)
{
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH3, name, subname);
if (path_in_path(path, HAL_LIBRARY_PATH3) && access(path, R_OK) == 0)
return 0;
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (path_in_path(path, HAL_LIBRARY_PATH2) && access(path, R_OK) == 0)
return 0;
#ifndef __ANDROID_VNDK__
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
if (path_in_path(path, HAL_LIBRARY_PATH1) && access(path, R_OK) == 0)
return 0;
#endif
return -ENOENT;
}
```
### load 加載庫
* 在上一步中 `hw_get_module` 函數找到驅動 `.so` 的路徑後,就會呼叫 `load` 函數來加載,我們先來概略看一下 `load` 函數
```c=
// hardware.c
#if defined(__LP64__)
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib64/hw"
#else
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib/hw"
#endif
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status = -EINVAL;
void *handle = NULL;
struct hw_module_t *hmi = NULL;
#ifdef __ANDROID_VNDK__
const bool try_system = false;
#else
const bool try_system = true;
#endif
/*
* 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
*/
if (try_system &&
strncmp(path, HAL_LIBRARY_PATH1, strlen(HAL_LIBRARY_PATH1)) == 0) {
// 如果 lib 在系統,就不用確認 sphal namespace,直接使用 `dlopen` 開啟
handle = dlopen(path, RTLD_NOW);
} else {
...
}
if (handle == NULL) {
// 加載失敗
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
// 取得 `hal_module_info` 地址
const char *sym = HAL_MODULE_INFO_SYM_AS_STR; // 其值為 "HMI"
// 尋找 so 中的 HMI 符號 !
// 也就是我們 Moudle 中有規範的 `HAL_MODULE_INFO_SYM`
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
// 成功取得硬體模組後,判斷 id 是否相同
if (strcmp(id, hmi->id) != 0) {
// 不同
ALOGE("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 {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, hmi, handle);
}
*pHmi = hmi; // 到這裡完成硬體驅動模組的取得 !
return status;
}
```
**分析 load 函數**:我們來分析幾個關鍵步驟
1.
取得硬體驅動模組 (so) 的操控句柄
## 其他
### C 語言的抽象 - struct 繼承
* Java 來這種物件導向的語言來說,定義抽象接口並實做(繼承)是相當容易的 (如下示範)
```java=
// 抽象接口
public interface IHAL {
void start();
void close();
}
// --------------------------------------
public class Board_AT_32_403 implements IHAL {
public void start() {
// 細節
}
public void close() {
// 細節
}
}
```
但對於底層 C 語言來說就不容易 (應該說有別種方法),要達成繼承的概念,**須使用 struct 數據結構,並 ++定義第一個成員變量是父類結構++**,以下以 [**gralloc.h**](https://cs.android.com/android/platform/superproject/+/master:hardware/libhardware/include/hardware/gralloc.h) 為例
> 
### sturct 繼承範例
* struct 繼承範例如下
```c=
#include <malloc.h>
#include <stdio.h>
struct Person {
long id;
};
struct Boy {
struct Person person;
char* boyName;
};
struct Girl {
struct Person person;
char* girlName;
};
void createInformation(struct Person **person, char isBoy) {
if(isBoy == 1) {
struct Boy *boy = (struct Boy*) malloc(sizeof(struct Boy));
boy->person.id = 5566;
boy->boyName = "Kyle";
// 重新設定指標位置
*person = (struct Person*) boy;
} else {
struct Girl *girl = (struct Girl*) malloc(sizeof(struct Girl));
girl->person.id = 2020;
girl->girlName = "Lulu";
// 重新設定指標位置
*person = (struct Person*) girl;
}
}
int main() {
// 創建指標
struct Person *girl, *boy;
// 傳入指標的位置
createInformation(&girl, 0);
printf("Girl id:%ld, name:%s\n", girl->id, ((struct Girl*) girl)->girlName);
createInformation(&boy, 1);
printf("Boy id:%ld, name:%s", boy->id, ((struct Boy*) boy)->boyName);
return 0;
}
```
> 
:::danger
* 如果沒有將要繼承的 struct 放置在第一個位置,就會導致 C 語言轉型 struct 失敗,如下圖
```c=
struct Boy {
char* boyName;
// 沒有置為第一位
struct Person person;
};
```
> 
:::
## Appendix & FAQ
:::info
:::
###### tags: `Template`