深入淺出 – Android系統移植與平台開發(十) – led HAL簡單設計案例分析

來源:互聯網
上載者:User

通過前兩節HAL架構分析和JNI概述,我們對Android提供的Stub HAL有了比較詳細的瞭解了,下面我們來看下led的執行個體,寫驅動點亮led燈,就如同寫程式,學語言列印HelloWorld一樣,如果說列印HelloWorld是一門新語言使用的第一聲吆喝,那麼點亮led燈就是我們學習HAL的一座燈塔,指揮我們在後面的複雜的HAL代碼裡準確找到方向。

 LedHAL執行個體架構

描述了我們Led執行個體的架構層次:

l  LedDemo.java:是我們寫的Android應用程式

l  LedService.java:是根據Led HAL封裝的Java架構層的API,主要用於嚮應用層提供架構層API,它屬於Android的架構層

l  libled_runtime.so:由於Java代碼不能訪問HAL層,該庫是LedService.java對應的本地代碼部分

l  led.default.so:針對led硬體的HAL代碼

LedDemo通過LedService提供的架構層API訪問Led裝置,LedService對於LedDemo應用程式而言是Led裝置的服務提供者,LedService運行在Dalvik中沒有辦法直接存取Led硬體裝置,它只能將具體的Led操作交給本地代碼來實現,通過JNI來調用Led硬體操作的封裝庫libled_runtime.so,由HAL Stub架構可知,在libled_runtime.so中首先尋找註冊為led的硬體裝置module,找到之後儲存其操作介面指標在本地庫中等待架構層LedService調用。led.default.so是HAL層代碼,它是上層操作的具體實施者,它並不是一個動態庫(也就是說它並沒有被任何進程載入並連結),它只是在本地代碼尋找硬體裝置module時通過ldopen”殺雞取卵”找module,返回該硬體module對應的device操作結構體中封裝的函數指標。

其調用時序如下:

Led HAL執行個體程式碼分析

我們來看下led執行個體的目錄結構:

主要檔案如下:

com.hello.LedService.cpp:它在frameworks/services/jni目錄下,是的Led本地服務代碼

led.c:HAL代碼

led.h:HAL代碼標頭檔

LedDemo.java:應用程式代碼

LedService.java:Led架構層服務代碼

在Android的源碼目錄下,架構層服務代碼應該放在frameworks/services/java/包名/目錄下,由Android的編譯系統統一編譯產生system/framework/services.jar檔案,由於我們的測試代碼屬於廠商定製代碼,盡量不要放到frameworks的源碼樹裡,我將其和LedDemo應用程式放在一起了,雖然這種方式從Android架構層次上不標準。

另外,本地服務代碼的檔案名稱要和對應的架構層Java代碼的名字匹配(包名+類檔案名稱,包目錄用“_“代替)。有源碼目錄裡都有對應的一個Android.mk檔案,它是Android編譯系統的指導檔案,用來編譯目標module。

1)        Android.mk檔案分析

先來看下led源碼中①號Android.mk:

include $(call all-subdir-makefiles)

代碼很簡單,表示包含目前的目錄下所有的Android.mk檔案

先來看下led_app目錄下的③號Android.mk:

# 調用宏my-dir,這個宏返回當前Android.mk檔案所在的路徑LOCAL_PATH:= $(call my-dir)                                     # 包含CLEAR_VARS變數指向的mk檔案build/core/clear_vars.mk,它主要用來清除編譯時間依賴的編譯變數include $(CLEAR_VARS)                                    # 指定當前目標的TAG標籤,關於其作用見前面Android編譯系統章節LOCAL_MODULE_TAGS := user# 當前mk檔案的編譯目標模組LOCAL_PACKAGE_NAME := LedDemo# 編譯目標時依賴的源碼,它調用了一個宏all-java-files-under,該宏在build/core/definitions.mk中定義# 表示在目前的目錄下尋找所有的java檔案,將尋找到的java檔案返回LOCAL_SRC_FILES := $(callall-java-files-under, src)# 在編譯Android應用程式時都要指定API level,也就是當前程式的編譯平台版本# 這裡表示使用當前源碼的版本LOCAL_SDK_VERSION := current# 最重要的就是這句代碼,它包含了一個檔案build/core/package.mk,根據前面設定的編譯變數,編譯產生Android包檔案,即:apk檔案include $(BUILD_PACKAGE)

上述代碼中都加了注釋,基本上每一個編譯目標都有類似上述的編譯變數的聲明:

LOCAL_MODULE_TAGS

LOCAL_PACKAGE_NAME

LOCAL_SRC_FILES

由於所有的Android.mk最終被編譯系統包含,所以在編譯每個目標模組時,都要通過LOCAL_PATH:= $(call my-dir)指定當前目標的目錄,然後調用include $(CLEAR_VARS)先清除編譯系統依賴的重要的編譯變數,再產生新的編譯變數。

讓我們來看看LedDemo目標對應的源碼吧。

2)        LedDemo程式碼分析

學習過Android應用的同學對其目錄結構很熟悉,LedDemo的源碼在src目錄下。

@ led_app/src/com/farsight/LedDemo.java:

package com.hello; import com.hello.LedService; import com.hello.R; importandroid.app.Activity; importandroid.os.Bundle; importandroid.util.Log; importandroid.view.View; import android.view.View.OnClickListener; importandroid.widget.Button;  public classLedDemo extends Activity {     privateLedService led_svc;     private Buttonbtn;     private booleaniflag = false;     private Stringtitle;      /** Calledwhen the activity is first created. */     @Override     public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        Log.i("Java App", "OnCreate");         led_svc =new LedService();         btn =(Button) this.findViewById(R.id.Button01);        this.btn.setOnClickListener(new OnClickListener() {            public void onClick(View v) {                Log.i("Java App", "btnOnClicked");                if (iflag) {                    title = led_svc.set_off();                    btn.setText("Turn On");                    setTitle(title);                    iflag = false;                } else {                    title = led_svc.set_on();                    btn.setText("Turn Off");                    setTitle(title);                    iflag = true;                }             }         });     } }

代碼很簡單,Activity上有一個按鈕,當Activity初始化時建立LedService對象,按鈕按下時通過LedService對象調用其方法set_on()和set_off()。

 

3)        LedService程式碼分析

我們來看下LedService的代碼:

@led_app/src/com/farsight/LedService.java:

package com.hello;import android.util.Log;public class LedService {    /*     * loadnative service.     */    static {         // 靜態初始化語言塊,僅在類被載入時被執行一次,通常用來載入庫        Log.i ("Java Service" , "Load Native Serivce LIB" );       System.loadLibrary ( "led_runtime" );    }    // 構造方法    public LedService() {              int icount ;        Log.i ("Java Service" , "do init Native Call" );        _init ();                 icount =_get_count ();        Log.d ("Java Service" , "led count = " + icount );        Log.d ("Java Service" , "Init OK " );    }    /*     * LED nativemethods.     */    public Stringset_on() {        Log.i ("com.hello.LedService" , "LED On" );        _set_on();        return"led on" ;     }     public String set_off() {         Log.i ("com.hello.LedService" , "LED Off" );         _set_off();         return"led off" ;     }     /*     * declare all the native interface.     */     private static native boolean _init();     private static native int _set_on();     private static native int _set_off();     private static native int _get_count();  }

通過分析上面代碼可知LedService的工作:

l   載入本地服務的庫代碼

l   在構造方法裡調用_init本地代碼,對Led進行初始化,並調用get_count得到Led燈的個數

l   為LedDemo應用程式提供兩個API:set_on和set_off,這兩個API方法實際上也是交給了本地服務代碼來操作的

由於Java代碼無法直接操作底層硬體,通過JNI方法將具體的操作交給本地底層代碼實現,自己只是一個API Provider,即:服務提供者。

讓我們來到底層本地代碼,先看下底層代碼的Android.mk檔案:

@ frameworks/Android.mk:

LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := engLOCAL_MODULE:= libled_runtime                    # 編譯目標模組LOCAL_SRC_FILES:= \       services/jni/com_farsight_LedService.cpp LOCAL_SHARED_LIBRARIES := \                       # 編譯時間依賴的動態庫       libandroid_runtime  \       libnativehelper    \        libcutils         \        libutils          \       libhardware LOCAL_C_INCLUDES += \                                  #編譯時間用到的標頭檔目錄       $(JNI_H_INCLUDE)LOCAL_PRELINK_MODULE := false                            # 本目標為非預連結模組include $(BUILD_SHARED_LIBRARY)                # 編譯產生共用動態庫

結合前面分析的Android.mk不難看懂這個mk檔案。之前的mk檔案是編譯成Android apk檔案,這兒編譯成so共用庫,所以LOCAL_MODULE和include $(BUILD_SHARED_LIBRARY)與前面mk檔案不同,關於Android.mk檔案裡的變數作用,請查看Android編譯系統章節。

總而言之,本地代碼編譯產生的目標是libled_runtime.so檔案。

 

4)        Led本地服務程式碼分析

我們來看下本地服務的源碼:

@ frameworks/services/jni/com_hello_LedService.cpp:

#define LOG_TAG "LedService"#include "utils/Log.h"#include <stdlib.h>#include <string.h>#include <unistd.h>#include <assert.h>#include <jni.h>#include "../../../hardware/led.h"static led_control_device_t *sLedDevice = 0;static led_module_t* sLedModule=0; static jint get_count(void){    LOGI("%sE", __func__);   if(sLedDevice)        returnsLedDevice->get_led_count(sLedDevice);    else       LOGI("sLedDevice is null");    return 0;}static jint led_setOn(JNIEnv* env, jobject thiz) {    LOGI("%sE", __func__);    if(sLedDevice) {       sLedDevice->set_on(sLedDevice);    }else{       LOGI("sLedDevice is null");    }    return 0; }  static jint led_setOff(JNIEnv* env, jobject thiz) {    LOGI("%s E", __func__);     if(sLedDevice) {        sLedDevice->set_off(sLedDevice);     }else{         LOGI("sLedDevice is null");     }     return 0; }static inline int led_control_open(const structhw_module_t* module,     structled_control_device_t** device) {    LOGI("%s E ", __func__);     returnmodule->methods->open(module,        LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);}static jint led_init(JNIEnv *env, jclass clazz){    led_module_tconst * module;    LOGI("%s E ", __func__);     if(hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0){        LOGI("get Module OK");        sLedModule = (led_module_t *) module;        if(led_control_open(&module->common, &sLedDevice) != 0) {           LOGI("led_init error");           return-1;        }    }     LOGI("led_init success");    return 0;}  /*  *  * Array ofmethods.  * Each entryhas three fields: the name of the method, the method  * signature,and a pointer to the native implementation.  */static const JNINativeMethod gMethods[] = {    {"_init",    "()Z",(void*)led_init},    {"_set_on",   "()I",(void*)led_setOn },    {"_set_off", "()I",(void*)led_setOff },    {"_get_count", "()I",(void*)get_count },};  static int registerMethods(JNIEnv* env) {     static constchar* const kClassName = "com/hello/LedService";     jclass clazz;     /* look upthe class */     clazz =env->FindClass(kClassName);     if (clazz ==NULL) {        LOGE("Can't find class %s\n", kClassName);         return-1;     }      /* registerall 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 outthe rest of the ID cache */     return 0; }  /*  * This iscalled 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");     if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {        LOGE("ERROR: GetEnv failed\n");         gotofail;     }     assert(env!= NULL);     if(registerMethods(env) != 0) {         LOGE("ERROR: PlatformLibrary nativeregistration failed\n");         gotofail;     }     /* success-- return valid version number */     result =JNI_VERSION_1_4;  fail:     return result; }

         這兒的代碼不太容易讀,因為裡面是JNI的類型和JNI特性的代碼,看代碼先找入口。LedService.java架構代碼一載入就調用靜態初始化語句塊裡的System.loadLibrary ( "led_runtime" ),載入libled_runtime.so,該庫剛好是前面Android.mk檔案的目標檔案,也就是說LedService載入的庫就是由上面的本地代碼產生的。當一個動態庫被Dalvik載入時,首先在Dalvik會回調該庫代碼裡的JNI_OnLoad函數。也就是說JNI_OnLoad就是本地服務代碼的入口函數。

         JNI_OnLoad的代碼一般來說是死的,使用的時候直接拷貝過來即可,vm->GetEnv會返回JNIEnv指標,而這個指標其實就是Java虛擬機器的環境變數,我們可以通過該指標去調用JNI提供的方法,如FindClass等,調用registerMethods方法,在方法裡通過JNIEnv的FindClass尋找LedService類的引用,然後在該類中註冊本地方法與Java方法的映射關係,上層Java代碼可以通過這個映射關係調用到本地代碼的實現。RegisterNatives方法接收三個參數:

l  第一個參數jclass:要註冊哪個類裡的本地方法映射關係

l  第二個參數JNINativeMethod*:這是一個本地方法與Java方法映射數組,JNINativeMethod是個結構體,每個元素是一個Java方法到本地方法的映射。

typedef struct {         constchar* name;         constchar* signature;         void*fnPtr;} JNINativeMethod;

         name:表示Java方法名

         signature:表示方法的簽名

         fnPtr:Java方法對應的本地方法指標

l  第三個參數size:映射關係個數

由代碼可知,Java方法與本地方法的映射關係如下:

Java方法

本地方法

void _init()

jint led_init(JNIEnv *env, jclass clazz)

int _set_on()

jint led_setOn(JNIEnv* env, jobject thiz)

int _set_off()

jint led_setOff(JNIEnv* env, jobject thiz)

int _get_count()

jint get_count(void)

通過上表可知,本地方法參數中預設會有兩個參數:JNIEnv* env, jobject thiz,分別表示JNI環境和調用當前方法的對象引用,當然你也可以不設定這兩個參數,在這種情況下你就不能訪問Java環境中的成員。本地方法與Java方法的簽名必須一致,傳回值不一致不會造成錯誤。

現在我們再來回顧下我們的調用調用流程:

l  LedDemo建立了LedService對象

l  LedService類載入時載入了對應的本地服務庫,在本地服務庫裡Dalvik自動調用JNI_OnLoad函數,註冊Java方法和本地方法映射關係。

根據Java語言特點,當LedDemo對象建立時會調用其構造方法LedService()。

// 構造方法    public LedService() {              int icount ;        Log.i ("Java Service" , "do init Native Call" );        _init ();                 icount =_get_count ();        Log.d ("Java Service" , "led count = " + icount );        Log.d ("Java Service" , "Init OK " );    }

在LedService構造方法裡直接調用了本地方法_init和_get_count(通過native保留字聲明),也就是說調用了本地服務代碼裡的jint led_init(JNIEnv *env, jclass clazz)和jintget_count(void)。

在led_init方法裡的內容就是我們前面分析HAL架構代碼的使用規則了。

l  通過hw_get_module方法查到到註冊為LED_HARDWARE_MODULE_ID,即:”led”的module模組。

l  通過與led_module關聯的open函數指標開啟led裝置,返回其device_t結構體,儲存在本地代碼中,有的朋友可能會問,不是本地方法不能持續儲存一個引用嗎?由於device_t結構是在open裝置時通過malloc分配的,只要當前進程不死,該指標一直可用,在這兒本地代碼並沒有儲存Dalvik裡的引用,儲存的是mallco的分配空間地址,但是在關閉裝置時記得要將該地址空間free了,否則就記憶體流失了。

l  拿到了led裝置的device_t結構之後,當LedDemo上的按鈕按下時調用LedService對象的set_on和set_off方法,這兩個LedService方法直接調用了本地服務代碼的對應映射方法,本地方法直接調用使用device_t指向的函數來間接調用驅動作業碼。

好吧,讓我們再來看一個詳細的時序圖:

不用多解釋了。

最後一個檔案,HAL對應的Android.mk檔案:

@ hardware/Android.mk:

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_C_INCLUDES += \         include/LOCAL_PRELINK_MODULE := falseLOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hwLOCAL_SHARED_LIBRARIES := liblogLOCAL_SRC_FILES := led.cLOCAL_MODULE := led.defaultinclude $(BUILD_SHARED_LIBRARY)

註:LOCAL_PRELINK_MODULE:= false要加上,否則編譯出錯

指定目標名為:led.default

目標輸入目錄LOCAL_MODULE_PATH為:/system/lib/hw/,不指定會預設輸出到/system/lib目錄下。

根據前面HAL架構分析可知,HAL Stub庫預設載入地址為:/vendor/lib/hw/或/system/lib/hw/,在這兩個目錄尋找:硬體id名.default.so,所以我們這兒指定了HAL Stub的編譯目標名為led.default,編譯成動態庫,輸出目錄為:$(TARGET_OUT_SHARED_LIBRARIES)/hw,TARGET_OUT_SHARED_LIBRARIES指/system/lib/目錄。

5) 深入理解

我們從進程空間的概念來分析下我們上面寫的代碼。

我們前面的範例程式碼中,將LedDemo.java和LedService.java都放在了一個APK檔案裡,這也就意味著這個應用程式編譯完之後,它會運行在一個Dalvik虛擬機器執行個體中,即:一個進程裡,在LedService.java中載入了libled_runtime.so庫,通過JNI調用了本地代碼,根據動態庫的運行原理,我們知道,libled_runtime.so在第一次引用時會被載入到記憶體中並映射到引用庫的進程空間中,我們可以簡單理解為引用庫的程式和被引用的庫在一個進程中,而在libled_runtime.so庫中,又通過dlopen開啟了庫檔案led.default.so(該庫並沒有被庫載入器載入,而是被當成一個檔案開啟的),同樣我們可以理解為led.default.so和libled_runtime.so在同一個進程中。

由此可見,上面樣本的Led HAL代碼全部都在一個進程中實現,在該樣本中的LedService功能比較多餘,基本上不能算是一個服務。如果LedDemo運行在兩個進程中,就意味著兩個進程裡的LedService不能複用,通常我們所謂的Service服務一般向用戶端提供服務並且同時可以為多個用戶端服務(如),所以我們的樣本Led HAL代碼不是完美的HAL模型,我們後面章節會再實現一個比較完美的HAL架構。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.