這是 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 程式碼。
HAL 的過去
過去的 libhardware_legacy 作法,比較是傳統的「module」方式,也就是將 *.so 檔案當做「shared library」來使用,在 runtime(JNI 部份)以 direct function call 使用 HAL module。透過直接函數呼叫的方式,來操作驅動程式。
當然,應用程式也可以不需要透過 JNI 的方式進行,直接以載入 *.so 檔(dlopen)的做法呼叫 *.so 裡的符號(symbol)也是一種方式。
HAL 的現實狀況
現在的 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 的未來發展?
新的 HAL 做法,傾向全面採用 JNI 的方式進行。也就是,在 Android 的架構中,修改 Android runtime 實作(即「Core Library」),在取得 HAL 模組的 operations 後再做 callback 操作。將 HAL 模組完全放在 HAL 裡面。
如圖1,應用程式存取驅動程式的過程,可區分為以下二種:
採用Service架構方式是比較標準的做法,即圖上藍色線的部份;紅色線的部份為非Service架構式的做法。先前,在「Android 驅動開發關鍵技術:HAL及移植要領」演講中最後所提出的一個LED範例,即是一種非架構式的做法,簡報上有一段範例程式碼,歡迎下載參考。
Android HAL Introduction: libhardware and its legacy採取Service架構的方式,是建議的做法,當然因為這是標準架構,也應該採用。
Service在Android框架裡的角色是「存取底層硬體」,往上層的話,可以和應用程式溝通。因此,採用標準的Service做法,好處是在 資料存取(data communication)的處理較為系統化。這方面,Android提供了標準的處理架構,後續再進行討論。
圖上的「core libraries」即是Service程式碼的實作,也就是,Android應用程式透過JNI(Dalvik)來到Service這一層,再透過Service載入*.so檔;而非標準做法則是讓應用程式直接透過JNI來載入*.so檔。
不使用Service架構
紅色的過程,因為不使用Service架構,因此「框架整合」的工作量比較小,甚致大部份的實作都不需要改動框架本身。這樣的做法,就有點像是「跳過framework」的方式;相對的,此時應用程式開發者需要考慮的設計議題就比較多。
例如,當遇到block operation時,是否需要獨立的Java thread來處理?
前二篇教學提到「採用Service架構整合HAL的做法」。這裡再針對HAL如何採用Service架構與框架整合做一個概念的介紹。
Android的Service分為二種:Android Service與Native Service。
Android Service
Android Service又稱為Java Service,是實作在框架層(framework)裡的「Server」。這裡所講的「Service」是System Service,又稱為Server,與應用程式設計上所討論的Service(android.app.Service)不同。Android Service以Java撰寫。
Native Service
Native Service則是實作在Runtime層裡的Server。架構設計上,我們有二個選擇,一個是實作Android Service、再透過JNI與HAL stub溝通;另一個選擇是,跳過Android Service,讓Application(Manager API)直接與Native Service溝通。
未來的Android發展趨勢,應會以第二種做法為主,即Manager API直接與Native Service溝通,以達到更好的效能表現。
目前為止,我們提了「HAL Stub」的觀念,了解到 stub 是一種代理人的觀念,架構設計上,採取「provide operations」與「callback」機制,而不是採用「module」即 library 的 direct function call 做法。
接著,又提到「採取Service架構的方式」。在講解HAL stub的實作細節前,需要大略了解一下Android Service與HAL stub的關係;因為,架構設計上是「透過Android Service取得HAL stub提供的operations」。
取得HAL stub operations的程式碼實際上是實作在Native Service裡,相關的實作細節留待後續再做說明。
Android Service與HAL stub的關係
應用程式(Application)使用了Manager API,Manager API經由Android Service來到Native Service層,最後Native Service層再引用(invoke)HAL stub。這個過程,總共經過了以下3個介面,如下圖。
1. 這是一個 remote interface,應用程式與Android Service在不同的 process 上執行。
2. 這是 Java native interface,實作上透過 JNI table 來對應 native method 與 native function。
3. 理論上,這是 HAL 層,實作上則是使用 HAL API 先取得 stub operations,再由 native service callback stub。
撰寫 HAL stub 除了要具備系統程式(systems software)的觀念外(這是基礎),「思考方式的改變」也是重要的一堂課。
思考方式哪裡不同?
實作 HAL stub 的首要工作是「繼承 struct hw_module_t 抽象型別」。Class(類別)屬於一種抽象型號(ADT)。
首先,引入最重要的標頭檔(header file):
#include <hardware/hardware.h>
接著,再定義一個「MODULE ID」。這個 mdoule ID 將會被 HAL 層用來尋找 HAL stub。我們舉最簡單的裝置類型「LED」來做為範例:
#define LED_HARDWARE_MODULE_ID "led"繼承 HAL 的 struct hw_module_t 抽象型別(即 base class 的概念),並取名為 struct led_module_t(即 derived class):
struct led_module_t { struct hw_module_t common; };
以資料結構的角度來看,這裡的做法只是宣告了一個抽象資料型別(Abstract Data Type),以提升程式碼的結構化特性。但是,這裡需要以架構的角度來解釋,「Android HAL規定不要直接使用struct hw_module_t」,原文的意思是要我們做類別繼承。
實作繼承
在C語言裡實作繼承的方式,大致如下:
1. 宣告一個 data structure 將原始的基本結構包裝起來
2. 將原始的基本結構放在第一個 field
因此,可以思考如下:
struct led_module_t { struct hw_module_t common; /* Place attributes here. */ /* Place methods here. */ };
唯一,這裡的實作在OO特性上,缺乏像是public與private的封裝性。但這裡的重心是,以OO的方式思考,會如何改變過去的 C 程式寫作習慣?最明顯的地方是,程式碼的寫作風格有了很大的改變。
在討論了不少基本概念後,在這裡小結一下 HAL stub 的實作步驟。HAL stub 的起頭是「繼承 HAL 的 struct hw_module_t」,這是 HAL stub 的設計理念,除了讓架構的條理分明外,也容易做後續擴充與維護。以下改用實用上的習慣用語,小結一下 HAL stub 實作步驟,並提供一段例。
HAL Stub 實作步驟(Implementation)
1. 設計自已的wrapper data structure
* 編寫led.h
* 定義 struct led_module_t
* 框架提供的 struct hw_module_t 必須放在第一個 field、並取名為 common
* 請參考 hardware/hardware.h
2. led_module_t的意義
宣告初始化時期(new object)的 supporting API、在 constructor 裡會使用到。
3. 定義 led_control_device_t
宣告控制時期的 supporting API、在 Manager API 裡會使用到。設計上的細節在後續文章再做整理。
4. 每個 HAL stub 都要宣告 module ID
5. 宣告 Stub operations 並實作 callback functions
Stub 的 operations 結構符號名稱須取名為 HAL_MODULE_INFO_SYM、此符號名不可更改。
範例:led.h
#include <hardware/hardware.h> #include <fcntl.h> #include <errno.h> #include <cutils/log.h> #include <cutils/atomic.h> /*****************************************************************************/ struct led_module_t { struct hw_module_t common; }; struct led_control_device_t { struct hw_device_t common; /* 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); }; /*****************************************************************************/ #define LED_HARDWARE_MODULE_ID "led"實作上的細節在這裡不再多做說明,關於設計上的細節將另行整理。
延續上一則日記的介紹,在完成HAL Stub的實作後,緊接著的工作就是撰寫Native Service。
談了許多「Android Service」以及「HAL Stub」,這裡再補充一點。Android作業系統啟動時,會執行一個process稱為servicemanager。Servicemanager process負責管理、提供並保存「Android Service」。Android Service為Java層,因此接下來會透過JNI來呼叫C/C++層的Native Service。
廣義來說,Native Service也提供Runtime的功能,即Core Library層。Runtime的重要工作之一為「取得HAL Stub所提供的API」,因此這是撰寫完整Native Service的前哨站。
什麼是 Proxy Object?
Native Service呼叫HAL的hw_get_module() API取得stub物件,即HAL_MODULE_INFO_SYM。
HAL會去尋找HAL Stub檔案,HAL Stub是以*.so檔的形式存在,並佈署於/system/lib/hw目錄下。HAL會根據module ID以及”ro.product.board”去尋找相對應的*.so檔,以我們的LED範例來說,HAL會回傳回led_module_t結構的物件 (an instance of led_module_t class)給Native Service。
我們把HAL回傳給Native Service的資料稱為「Stub Object」或是「Proxy Object」,即先前所提及的「代理人」觀念。Native Service透過代理人與 Linux 驅動程式溝通。這個過程的觀念如圖1所示。
承日記「Android 的 HAL 技術, #6: 小結 HAL stub 實作步驟」與「Android 的 HAL 技術, #7: 取得 Proxy Object」。在了解基本的觀念,以及架構上的設計後,接著就可以開始實作 HAL Stub 了。以下是 LED Stub 的實作範例,將程式碼儲存為 led.c:
static struct hw_module_methods_t led_module_methods = { open: led_device_open }; 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: "Simple LED module", author: "The Mokoid Open Source Project", methods: &led_module_methods, } /* supporting APIs go here */ };
這段程式碼實作了圖1的設計, led_module_t 是 Stub 的主體結構(或稱為主類別),其符號名稱須取名為 HAL_MODULE_INFO_SYM,不可更改。任何的 Stub 主類別名稱都須命名為 HAL_MODULE_INFO_SYM。
幾個重要的欄位如下:
"Open" 是一個介面(interface),這意味著 HAL Stub 必須實作此介面,這個部份更是 HAL 的重點。
一年多前,「Android 的 HAL 技術」系列日記闡釋了 Android HAL 的觀念與基本設計方法;在小弟的「Android HAL & Framework: 軟硬整合實作訓練 」訓練規劃中,也對 Android Service 與 HAL 做了完整的介紹。
現在,再度回到 Android HAL 的主題;主要是 Android 2.3 在部份硬體單元(Component)做了一些架構調整。隨著 AOSP 程式碼的大幅進步,硬體廠需要加強對 Android HAL 的技術掌握度,這是十萬火急的工作了。因為,硬體廠唯有具備發展高品質軟硬整合程式碼的能力,才能在 Android 產品開發工作上具備競爭力。
「Native 化」
過去在「Android 的 HAL 技術, #3: 小探Android Service與Native Service」 裡提及 Android 應用程式存取硬體的做法,大致分為二個路徑:Android Service 與 Native Service。在「Android HAL & Framework: 軟硬整合實作訓練 」裡,分別以「紅色路徑」與「綠色路徑」來表示。
在日記「Android 2.3 的更新:SensorService 的 Native 化」裡提到的「Native 化」會成為日後 Android 作業系統發展的趨勢之一。「也就是紅色路徑慢慢往綠色路徑移動」。
理想中的 Android HAL 架構
要了解這項發展趨勢,可以從理想中的 Android HAL 架構說起,先在腦海裡建立大方向圖(Big Picture)。根據 Android 各版本的發展,可以推測出 Android HAL 將朝向下圖的架構來發展。
這個架構不是憑空想像,事實上也不是 Android 作業系統的創舉。
研究作業系統的朋友可以知道,Hardware Abstraction Layer (HAL) 的觀念已經行之多年了,在許多重量級的 OS 裡都能看到。為什麼在過去的 Android OS 裡,HAL 的架構或程式碼如此陽春?簡單講,「就是還沒有發展成熟。」
根據我們的研究,理想中且完善的 Android HAL 架構包含四個部份:
Service Layer 實作在 Application Framework 層,為了提供更上層(Application)存取硬體而設計,因此,在有些研究上,也稱之為「Application Service」;等同於過去我們所講的 Android Service。Application Service 最多就是提供 API 而已。
這個架構並非一種創新,因為在許多重量級的作業系統裡都能看到這樣的設計,例如:Mac OS X。統一架構,讓硬體存取一致化,實作整齊劃一。不過,以上的觀點都只是理論,目的是推測 Android 在這個部份的發展輪廓。
實務上,看到的程式碼與理論研究總是有點落差。現行的 AOSP 程式碼實作,還是與我們所討論的架構不一樣;至於未來如何發展,只能密切與 AOSP 同步。螢幕上的程式碼,背後總是有許多思惟邏輯,有些能從程式碼推敲一二,更多則是沒有文獻記載,技術研究的樂趣在於發覺這些內涵。