一、概述
本文希望通過分析臺灣的Jollen的mokoid 工程代碼,和在s5pc100平臺上實現過程種遇到的問題,解析Andorid HAL的開發方法。
二、HAL介紹
現有HAL架構由Patrick
Brady (Google) 在2008 Google I/O演講中提出的,如下圖。
Android的HAL是為了保護一些硬體提供商的智慧財產權而提出的,是為了避開linux的GPL束縛。思路是把控制硬體的動作都放到了Android HAL中,而linux driver僅僅完成一些簡單的資料交互作用,甚至把硬體寄存器空間直接映射到user space。而Android是基於Aparch的license,因此硬體廠商可以只提供二進位碼,所以說Android只是一個開放的平臺,並不是一個開源的平臺。也許也正是因為Android不遵從GPL,所以Greg
Kroah-Hartman才在2.6.33內核將Andorid驅動從linux中刪除。GPL和硬體廠商目前還是有著無法彌合的裂痕。Android想要把這個問題處理好也是不容易的。
總結下來,Android HAL存在的原因主要有:
1. 並不是所有的硬體設備都有標準的linux kernel的介面
2. KERNEL DRIVER涉及到GPL的版權。某些設備製造商並不原因公開硬體驅動,所以才去用HAL方 式繞過GPL。
3. 針對某些硬體,An有一些特殊的需求
三、HAL內容
1、HAL 主要的儲存於以下目錄:
(注意:HAL在其它目錄下也可以正常編譯)
l
libhardware_legacy/ - 舊的架構、採取程式庫模組的觀念進行
l
libhardware/ - 新架構、調整為 HAL stub 的觀念
l
ril/ - Radio Interface Layer
l
msm7k QUAL平臺相關
主要包含以下一些模組:Gps、Vibrator、Wifi、Copybit、Audio、Camera、Lights、Ril、Overlay等。
2、兩種 HAL 架構比較
目前存在兩種HAL架構,位於libhardware_legacy目錄下的“舊HAL架構”和位於libhardware目錄下的“新HAL架構”。兩種框架如下圖所示。
圖3.1 舊HAL架構 |
圖3.2 新HAL架構 |
libhardware_legacy 是將 *.so 檔當作shared library來使用,在runtime(JNI 部份)以
direct function call 使用 HAL module。通過直接函式呼叫的方式,來操作驅動程式。當然,應用程式也可以不需要通過 JNI 的方式進行,直接載入 *.so (dlopen)的做法調用*.so 裡的符號(symbol)也是一種方式。總而言之是沒有經過封裝,上層可以直接操作硬體。
現在的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層結構框架,通過幾個介面訪問硬體從而統一了調用方式。
下面結合實例來分析HAL程式設計方法。
四、mokoid 工程代碼下載與結構分析
1、mokid項目概述
modkoid工程提供了一個LedTest示例程式,是臺灣的Jollen用於培訓的。對於理解android層次結構、Hal程式設計方法都非常有意義。
2、下載方法
#svn
checkout http://mokoid.googlecode.com/svn/trunk/mokoid-read-only
3、結構分析
|-- Android.mk
|-- apps //兩種應用測試方法
| |-- Android.mk
| |-- LedClient //直接調用service來調用jni
| |
|-- AndroidManifest.xml
| |
|-- Android.mk
| |
`-- src
| | `-- com
| |
`-- mokoid
| | `-- LedClient
| | `-- LedClient.java //第1種方式應用程式實現代碼
| `-- LedTest //通過manager來調用jni
| |-- AndroidManifest.xml
| |-- Android.mk
| `-- src
| `-- com
| `-- mokoid
| `-- LedTest
| |-- LedSystemServer.java //開啟了一個後臺service,下文會有解釋
| `-- LedTest.java //第2種方式應用程式實現代碼
|--
dma6410xp //這個目錄可以不要
| |-- AndroidBoard.mk
| |-- AndroidProducts.mk
| |-- BoardConfig.mk
| |-- dma6410xp.mk
| |-- init.dma6410xp.rc
| |-- init.goldfish.sh
| `-- init.rc
|--
frameworks //框架代碼
| |-- Android.mk
| `-- base
| |-- Android.mk
| |-- core
| |
`-- java
| |
`-- mokoid
| | `-- hardware
| | |-- ILedService.aidl
| | `-- LedManager.java //實現了Manager,給第2種方法用
| `-- service
| |-- Android.mk
| |-- com.mokoid.server.xml
| |-- java
| |
`-- com
| | `-- mokoid
| | `-- server
| | `-- LedService.java //Framework service代碼
| `-- jni
| |-- Android.mk
| `-- com_mokoid_server_LedService.cpp //jni代碼
|-- hardware
| |-- Android.mk
| |-- libled
| |
|-- Android.mk
| |
`-- libled.c
| `-- modules
| |-- Android.mk
| |-- include
| |
`-- mokoid
| |
`-- led.h
| `-- led
| |-- Android.mk
| `-- led.c //led stub 硬體控制代碼
`-- README.txt
Android的HAL的實現需要通過JNI(Java
Native Interface),JNI簡單來說就是java程式可以調用C/C++寫的動態連結程式庫,這樣的話,HAL可以使用C/C++語言編寫,效率更高。在Android下訪問HAL大致有以下兩種方式:
(1)Android的app可以直接通過service調用.so格式的jni
(2)經過Manager調用service
上面兩種方法應該說是各有優缺點,第一種方法簡單高效,但不正規。第二種方法實現起來比較複雜,但更符合目前的Android框架。第二種方法中,LegManager和LedService(java)在兩個進程中,需要通過進程通訊的方式來通訊。
mokoid工程中實現了上述兩種方法。下面將詳細介紹這兩種方法的實現原理。
4、第一種方法:直接調用service方法的實現過程
下面分析第一種方法中,各層的關鍵代碼。
(1)HAL層
一般來說HAL moudle需要涉及的是三個關鍵結構體:
struct
hw_module_t;
struct
hw_module_methods_t;
struct
hw_device_t;
下麵結合代碼說明這3個結構的用法。部分代碼經過修改,後面的章節會給出修改的原因。
文件:mokoid-read-only/hardware/modules/include/mokoid/led.h
- struct led_module_t {
- struct hw_module_t common;
- };
- //HAL 規定不能直接使用hw_module_t結構,因此需要做這麼一個繼承。
- struct led_control_device_t {
- //自訂的一個針對Led控制的結構,包含hw_device_t和支援的API操作
- struct hw_device_t common;
- /* attributes */
- int fd; //可用於具體的設備描述符
- /* 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"
- //定義一個MODULE_ID,HAL層可以根據這個ID找到我們這個HAL stub
文件:mokoid-read-only/hardware/modules/led/led.c
- #define LOG_TAG "MokoidLedStub"
- #include <hardware/hardware.h>
- #include <fcntl.h>
- #include <errno.h>
- #include <cutils/log.h>
- #include <cutils/atomic.h>
- //#include <mokoid/led.h>
- #include "../include/mokoid/led.h"
- /*****************************************************************************/
- int fd; //硬體led的設備描述符 。你也可以用led_control_device_t結構中定義的fd
- #define GPG3DAT2_ON 0x4800 //ioctl控制命令
- #define GPG3DAT2_OFF 0x4801
- int led_device_close(struct hw_device_t* device)
- {
- struct led_control_device_t* ctx = (struct led_control_device_t*)device;
- if (ctx) {
- free(ctx);
- }
- close(fd);
- return 0;
- }
- int led_on(struct led_control_device_t *dev, int32_t led)
- {
- LOGI("LED Stub: set %d on.", led);
- ioctl(fd,GPG3DAT2_ON,NULL); //控制Led亮滅,和硬體相關
- return 0;
- }
- int led_off(struct led_control_device_t *dev, int32_t led)
- {
- LOGI("LED Stub: set %d off.", led);
- return 0;
- }
- static 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; //將產生實體後的led_control_device_t地址返回給jni層
- //這樣jni層就可以直接調用led_on、led_off、led_device_close方法了。
- if((fd=open("/dev/led",O_RDWR))==-1) //打開硬體設備
- {
- LOGE("LED open error");
- }
- else
- LOGI("open ok");
- success:
- return 0;
- }
- static struct hw_module_methods_t led_module_methods = {
- open: led_device_open
- };
- const struct led_module_t HAL_MODULE_INFO_SYM = {
- //定義這個物件等於向系統註冊了一個ID為LED_HARDWARE_MODULE_ID的stub。注意這裡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, //實現了一個open的方法供jni層調用,
- //從而產生實體led_control_device_t
- }
- /* supporting APIs go here */
- };
(2)JNI層
文件:mokoid-read-only/frameworks/base/service/jni/com_mokoid_server_LedService.cpp
- struct led_control_device_t *sLedDevice = NULL;
-
- static jboolean mokoid_setOn(JNIEnv* env, jobject thiz, jint led)
- {
- LOGI("LedService JNI: mokoid_setOn() is invoked.");
-
- if (sLedDevice == NULL) {
- LOGI("LedService JNI: sLedDevice was not fetched correctly.");
- return -1;
- } else {
- return sLedDevice->set_on(sLedDevice, led);//調用hal層的註冊的方法
- }
- }
-
- static jboolean mokoid_setOff(JNIEnv* env, jobject thiz, jint led)
- {
- LOGI("LedService JNI: mokoid_setOff() is invoked.");
-
-
- if (sLedDevice == NULL) {
- LOGI("LedService JNI: sLedDevice was not fetched correctly.");
- return -1;
- } else {
- return sLedDevice->set_off(sLedDevice, led); //調用hal層的註冊的方法
- }
- }
-
- /** helper APIs */
- static inline int led_control_open(const struct hw_module_t* module,
- struct led_control_device_t** device) {
- return module->methods->open(module,
- LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
- //這個過程非常重要,jni通過LED_HARDWARE_MODULE_ID找到對應的stub
- }
-
- static jboolean mokoid_init(JNIEnv *env, jclass clazz)
- {
- led_module_t* module;
- LOGI("jni init-----------------------.");
- if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
- //根據LED_HARDWARE_MODULE_ID找到hw_module_t,參考hal層的實現
- LOGI("LedService JNI: LED Stub found.");
- if (led_control_open(&module->common, &sLedDevice) == 0) {
- //通過hw_module_t找到led_control_device_t
- LOGI("LedService JNI: Got Stub operations.");
- return 0;
- }
- }
-
- LOGE("LedService JNI: Get Stub operations failed.");
- return -1;
- }
-
- /*
- * Array of methods.
- * Each entry has three fields: the name of the method, the method
- * signature, and a pointer to the native implementation.
- */
- static const JNINativeMethod gMethods[] = {
- { "_init", "()Z", (void *)mokoid_init },//Framework層調用_init時促發
- { "_set_on", "(I)Z", (void *)mokoid_setOn },
- { "_set_off", "(I)Z", (void *)mokoid_setOff },
- };
- /*
- *JNINativeMethod是jni層註冊的方法,Framework層可以使用這些方法
- *_init 、_set_on、_set_off是在Framework中調用的方法名稱,函數的類型及返回值如下:
- *()Z 無參數 返回值為bool型
- * (I)Z 整型參數 返回值為bool型
- */
- static int registerMethods(JNIEnv* env) {
- static const char* const kClassName =
- "com/mokoid/server/LedService";//注意:必須和你Framework層的service類名相同
- jclass clazz;
- /* look up the class */
- clazz = env->FindClass(kClassName);
- if (clazz == NULL) {
- LOGE("Can't find class %s/n", kClassName);
- return -1;
- }
- /* register all the methods */
- if (env->RegisterNatives(clazz, gMethods,
- sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
- {
- LOGE("Failed registering methods for %s/n", kClassName);
- return -1;
- }
- /* fill out the rest of the ID cache */
- return 0;
- }
- jint JNI_OnLoad(JavaVM* vm, void* reserved) {//Framework層載入jni庫時調用
- JNIEnv* env = NULL;
- jint result = -1;
- LOGI("JNI_OnLoad LED");
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed/n");
- goto fail;
- }
- assert(env != NULL);
- if (registerMethods(env) != 0) { //註冊你的JNINativeMethod
- LOGE("ERROR: PlatformLibrary native registration failed/n");
- goto fail;
- }
- /* success -- return valid version number */
- result = JNI_VERSION_1_4;
- fail:
- return result;
- }
(3)service (屬於Framework層)
文件:frameworks/base/service/java/com/mokoid/server/LedService.java
- package com.mokoid.server;
- import android.util.Config;
- import android.util.Log;
- import android.content.Context;
- import android.os.Binder;
- import android.os.Bundle;
- import android.os.RemoteException;
- import android.os.IBinder;
- import mokoid.hardware.ILedService;
- public final class LedService extends ILedService.Stub {
- //對於這種直接模式不需要進程通訊,所以可以不加extends ILedService.Stub,此處加上主要是為了後面的第二種模式.
- static {
- System.load("/system/lib/libmokoid_runtime.so");//載入jni的動態庫
- }
- public LedService() {
- Log.i("LedService", "Go to get LED Stub...");
- _init();
- }
- /*
- * Mokoid LED native methods.
- */
- public boolean setOn(int led) {
- Log.i("MokoidPlatform", "LED On");
- return _set_on(led);
- }
- public boolean setOff(int led) {
- Log.i("MokoidPlatform", "LED Off");
- return _set_off(led);
- }
- private static native boolean _init(); //聲明jni庫可以提供的方法
- private static native boolean _set_on(int led);
- private static native boolean _set_off(int led);
- }
(4)APP
測試程式 (屬於APP層)
文件:apps/LedClient/src/com/mokoid/LedClient/LedClient.java
- package com.mokoid.LedClient;
- import com.mokoid.server.LedService;// 導入Framework層的LedService
- import android.app.Activity;
- import android.os.Bundle;
- import android.widget.TextView;
-
- public class LedClient extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // Call an API on the library.
- LedService ls = new LedService(); //產生實體LedService
- ls.setOn(1); //通過LedService提供的方法,控制底層硬體
- ls.setOff(2);
-
- TextView tv = new TextView(this);
- tv.setText("LED 1 is on. LED 2 is off.");
- setContentView(tv);
- }
- }
5、第二種方法:經過Manager調用service
HAL、JNI兩層和第一種方法一樣,所以後面只分析其他的層次。
(1)Manager (屬於Framework層)
APP通過這個Manager和service通訊。
文件:mokoid-read-only
/frameworks/base/core/java/mokoid/hardware/LedManager.java
- package mokoid.hardware;
- import android.content.Context;
- import android.os.Binder;
- import android.os.Bundle;
- import android.os.Parcelable;
- import android.os.ParcelFileDescriptor;
- import android.os.Process;
- import android.os.RemoteException;
- import android.os.Handler;
- import android.os.Message;
- import android.os.ServiceManager;
- import android.util.Log;
- import mokoid.hardware.ILedService;
-
- /*
- * Class that lets you access the Mokoid LedService.
- */
- public class LedManager
- {
- private static final String TAG = "LedManager";
- private ILedService mLedService;
- public LedManager() {
- mLedService = ILedService.Stub.asInterface(ServiceManager.getService("led"));
- /*
- *這一步是關鍵,利用ServiceManager獲取到LedService,從而調用它提供的方法。這要求LedService必
- *須已經添加到了ServiceManager中,這個過程將在App中的一個service進程中完成。
- */
- if (mLedService != null) {
- Log.i(TAG, "The LedManager object is ready.");
- }
- }
- public boolean LedOn(int n) {
- boolean result = false;
- try {
- result = mLedService.setOn(n);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in LedManager.LedOn:", e);
- }
- return result;
- }
- public boolean LedOff(int n) {
- boolean result = false;
- try {
- result = mLedService.setOff(n);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in LedManager.LedOff:", e);
- }
- return result;
- }
- }
因為LedService和LedManager在不同的進程,所以要考慮到進程通訊的問題。Manager通過增加一個aidl檔來描述通訊介面。
文件:mokoid-read-only/frameworks/base/core/java/mokoid/hardware/ILedService.aidl
- package mokoid.hardware;
- interface ILedService
- {
- boolean setOn(int led);
- boolean setOff(int led);
- }
- //系統的aidl工具會將ILedService.aidl文件ILedService.java檔,實現了ILedService
(2)SystemServer (屬於APP層)
文件:mokoid-read-only/apps/LedTest/src/com/mokoid/LedTest/LedSystemServer.java
- package com.mokoid.LedTest;
- import com.mokoid.server.LedService;
- import android.os.IBinder;
- import android.os.ServiceManager;
- import android.util.Log;
- import android.app.Service;
- import android.content.Context;
- import android.content.Intent;
-
- public class LedSystemServer extends Service {
- //注意這裡的Service是APP中的概念,代表一個後臺進程。注意區別和Framework中的service的概念。
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- public void onStart(Intent intent, int startId) {
- Log.i("LedSystemServer", "Start LedService...");
-
- /* Please also see SystemServer.java for your interests. */
- LedService ls = new LedService();
- try {
- ServiceManager.addService("led", ls); //將LedService添加到ServiceManager中
- } catch (RuntimeException e) {
- Log.e("LedSystemServer", "Start LedService failed.");
- }
- }
- }
(3)APP 測試程式(屬於APP層)
文件:mokoid-read-only/apps/LedTest/src/com/mokoid/LedTest/LedTest.java
- package com.mokoid.LedTest;
- import mokoid.hardware.LedManager;
- import com.mokoid.server.LedService;
- import android.app.Activity;
- import android.os.Bundle;
- import android.util.Log;
- import android.widget.TextView;
- import android.widget.Button;
- import android.content.Intent;
- import android.view.View;
-
- public class LedTest extends Activity implements View.OnClickListener {
- private LedManager mLedManager = null;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // Start LedService in a seperated process.
- startService(new Intent("com.mokoid.systemserver"));//開啟後臺進程
- Button btn = new Button(this);
- btn.setText("Click to turn LED 1 On");
- btn.setOnClickListener(this);
- setContentView(btn);
- }
- public void onClick(View v) {
- // Get LedManager.
- if (mLedManager == null) {
- Log.i("LedTest", "Creat a new LedManager object.");
- mLedManager = new LedManager(); //產生實體Framework層中的Manager
- }
- if (mLedManager != null) {
- Log.i("LedTest", "Got LedManager object.");
- }
- /** Call methods in LedService via proxy object
- * which is provided by LedManager.
- */
- mLedManager.LedOn(1);
- TextView tv = new TextView(this);
- tv.setText("LED 1 is On.");
- setContentView(tv);
- }
- }
五、實驗中需要注意的問題
將下載後的源碼放到你的android源碼目錄下,然後編譯系統。本實驗用的android版本為2.1。實驗的過程中大致出現過以下幾個問題:
1、目標系統中沒有生成LedClient.apk或LedTest.apk應用程式
編譯完成後,沒有在目標系統的system/app/目錄下找到LedClient.apk或LedTest應用程式。只有通過單獨編譯LedClient或LedTest才能在目標目錄中生成。方法如下:
#mmm
mokoid-read-only/apps/LedTest/
檢查原因後發現mokoid-read-only/apps/LedTest/Android.mk
LOCAL_MODULES_TAGS :=user
而我們的s5pc100系統在配置時tapas時選擇的是eng,所以沒有裝載到目標系統
所以修改LedTest和LedClient的Android.mk
LOCAL_MODULES_TAGS :=user eng
再次編譯即可自動裝載到目標系統/system/app/目錄下。
2、啟動後沒有圖示,找不到應用程式
目標系統啟動後找不到兩個應用程式的圖示。仔細閱讀logcat輸出的資訊發現:
E/PackageManager( 2717): Package
com.mokoid.LedClient requires unavailable shared library com.mokoid.server;
failing!
原因是找不到 com.mokoid.server。檢查mokoid-read-only/frameworks/base/Android.mk發現系統將LedManager和LedService編譯成 mokoid.jar庫文件。為了讓應用程式可以訪問到這個庫,需要通過com.mokoid.server.xml
來設定其對應關係。解決方法:拷貝com.mokoid.server.xml到目標系統的system/etc/permissions/目錄下
此時兩個應用的程式的圖示都正常出現了。
3、提示找不到 JNI_OnLoad
按照以前的實驗加入下列代碼:
- static int registerMethods(JNIEnv* env) {
- static const char* const kClassName ="com/mokoid/server/LedService";
- jclass clazz;
- /* look up the class */
- clazz = env->FindClass(kClassName);
- if (clazz == NULL) {
- LOGE("Can't find class %s/n", kClassName);
- return -1;
- }
- /* register all the methods */
- if (env->RegisterNatives(clazz, gMethods,
- sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
- {
- LOGE("Failed registering methods for %s/n", kClassName);
- return -1;
- }
- /* fill out the rest of the ID cache */
- return 0;
- }
- /*
- * This is called by the VM when the shared library is first loaded.
- */
- jint JNI_OnLoad(JavaVM* vm, void* reserved) {
- JNIEnv* env = NULL;
- jint result = -1;
- LOGI("JNI_OnLoad LED");
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed/n");
- goto fail;
- }
- assert(env != NULL);
- if (registerMethods(env) != 0) {
- LOGE("ERROR: PlatformLibrary native registration failed/n");
- goto fail;
- }
- /* success -- return valid version number */
- result = JNI_VERSION_1_4;
- fail:
- return result;
- }
4、需要針對你的目標平臺修改HAL的Makefile
修改mokoid-read-only/hardware/modules/led/Android.mk
LOCAL_MODULE := led.default
5、在eclipse中編譯不了LedSystemServer.java
原因是程式中要用到ServiceManager.addService,這需要系統許可權。
解決方法可以把應用程式放入Android源碼中編譯,並確保以下兩點:
(1)在應用程式的AndroidManifest.xml中的manifest節點中加入android:sharedUserId="android.uid.system"這個屬性。
(2)修改Android 加入LOCAL_CERTIFICATE :=
platform.
當然:mokoid工程源碼中已經做了這些。
(2)經過Manager調用service
沒有留言:
張貼留言