深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析

通过前两节HAL框架分析和JNI概述,我们对Android提供的Stub HAL有了比较详细的了解了,下面我们来看下led的实例,写驱动点亮led灯,就如同写程序,学语言打印HelloWorld一样,如果说打印HelloWorld是一门新语言使用的第一声吆喝,那么点亮led灯就是我们学习HAL的一座灯塔,指挥我们在后面的复杂的HAL代码里准确找到方向。

 LedHAL实例架构

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析_第1张图片

上图描述了我们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操作结构体中封装的函数指针。

其调用时序如下:

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析_第2张图片

Led HAL实例代码分析

我们来看下led实例的目录结构:

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析_第3张图片

主要文件如下:

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:

[plain]  view plain copy
  1. include $(call all-subdir-makefiles)  

代码很简单,表示包含当前目录下所有的Android.mk文件

先来看下led_app目录下的③号Android.mk:

[plain]  view plain copy
  1. # 调用宏my-dir,这个宏返回当前Android.mk文件所在的路径  
  2. LOCAL_PATH:= $(call my-dir)                                       
  3.   
  4. # 包含CLEAR_VARS变量指向的mk文件build/core/clear_vars.mk,它主要用来清除编译时依赖的编译变量  
  5. include $(CLEAR_VARS)                                      
  6.   
  7. # 指定当前目标的TAG标签,关于其作用见前面Android编译系统章节  
  8. LOCAL_MODULE_TAGS := user  
  9.   
  10. # 当前mk文件的编译目标模块  
  11. LOCAL_PACKAGE_NAME := LedDemo  
  12.   
  13. # 编译目标时依赖的源码,它调用了一个宏all-java-files-under,该宏在build/core/definitions.mk中定义  
  14. # 表示在当前目录下查找所有的java文件,将查找到的java文件返回  
  15. LOCAL_SRC_FILES := $(callall-java-files-under, src)  
  16.   
  17. # 在编译Android应用程序时都要指定API level,也就是当前程序的编译平台版本  
  18. # 这里表示使用当前源码的版本  
  19. LOCAL_SDK_VERSION := current  
  20.   
  21. # 最重要的就是这句代码,它包含了一个文件build/core/package.mk,根据前面设置的编译变量,编译生成Android包文件,即:apk文件  
  22. 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:

[java]  view plain copy
  1. package com.hello;  
  2.   
  3.  import com.hello.LedService;  
  4.   
  5.  import com.hello.R;  
  6.   
  7.  importandroid.app.Activity;  
  8.   
  9.  importandroid.os.Bundle;  
  10.   
  11.  importandroid.util.Log;  
  12.   
  13.  importandroid.view.View;  
  14.   
  15.  import android.view.View.OnClickListener;  
  16.   
  17.  importandroid.widget.Button;  
  18.   
  19.    
  20.   
  21.  public classLedDemo extends Activity {  
  22.      privateLedService led_svc;  
  23.      private Buttonbtn;  
  24.      private booleaniflag = false;  
  25.      private Stringtitle;  
  26.   
  27.       /** Calledwhen the activity is first created. */  
  28.      @Override  
  29.      public void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.main);  
  32.   
  33.         Log.i("Java App""OnCreate");  
  34.          led_svc =new LedService();  
  35.          btn =(Button) this.findViewById(R.id.Button01);  
  36.         this.btn.setOnClickListener(new OnClickListener() {  
  37.             public void onClick(View v) {  
  38.                 Log.i("Java App""btnOnClicked");  
  39.                 if (iflag) {  
  40.                     title = led_svc.set_off();  
  41.                     btn.setText("Turn On");  
  42.                     setTitle(title);  
  43.                     iflag = false;  
  44.                 } else {  
  45.                     title = led_svc.set_on();  
  46.                     btn.setText("Turn Off");  
  47.                     setTitle(title);  
  48.                     iflag = true;  
  49.                 }  
  50.              }  
  51.          });  
  52.      }  
  53.  }  

代码很简单,Activity上有一个按钮,当Activity初始化时创建LedService对象,按钮按下时通过LedService对象调用其方法set_on()和set_off()。

 

3)        LedService代码分析

我们来看下LedService的代码:

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

[java]  view plain copy
  1. package com.hello;  
  2. import android.util.Log;  
  3.   
  4. public class LedService {  
  5.   
  6.     /* 
  7.      * loadnative service. 
  8.      */  
  9.     static {         // 静态初始化语言块,仅在类被加载时被执行一次,通常用来加载库  
  10.         Log.i ("Java Service" , "Load Native Serivce LIB" );  
  11.        System.loadLibrary ( "led_runtime" );  
  12.     }  
  13.   
  14.     // 构造方法  
  15.     public LedService() {        
  16.         int icount ;  
  17.   
  18.         Log.i ("Java Service" , "do init Native Call" );  
  19.         _init ();           
  20.         icount =_get_count ();  
  21.         Log.d ("Java Service" , "led count = " + icount );  
  22.         Log.d ("Java Service" , "Init OK " );  
  23.     }  
  24.   
  25.     /* 
  26.      * LED nativemethods. 
  27.      */  
  28.     public Stringset_on() {  
  29.         Log.i ("com.hello.LedService" , "LED On" );  
  30.         _set_on();  
  31.         return"led on" ;  
  32.      }  
  33.   
  34.      public String set_off() {  
  35.          Log.i ("com.hello.LedService" , "LED Off" );  
  36.          _set_off();  
  37.          return"led off" ;  
  38.      }  
  39.   
  40.      /* 
  41.      * declare all the native interface. 
  42.      */  
  43.      private static native boolean _init();  
  44.      private static native int _set_on();  
  45.      private static native int _set_off();  
  46.      private static native int _get_count();  
  47.   
  48.   }  

通过分析上面代码可知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:

[plain]  view plain copy
  1. LOCAL_PATH:= $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3.   
  4. LOCAL_MODULE_TAGS := eng  
  5. LOCAL_MODULE:= libled_runtime                    # 编译目标模块  
  6. LOCAL_SRC_FILES:= \  
  7.        services/jni/com_farsight_LedService.cpp  
  8.   
  9.    
  10. LOCAL_SHARED_LIBRARIES := \                       # 编译时依赖的动态库  
  11.        libandroid_runtime  \  
  12.        libnativehelper    \  
  13.         libcutils         \  
  14.         libutils          \  
  15.        libhardware  
  16.   
  17.  LOCAL_C_INCLUDES += \                                  #编译时用到的头文件目录  
  18.        $(JNI_H_INCLUDE)  
  19.   
  20. LOCAL_PRELINK_MODULE := false                            # 本目标为非预链接模块  
  21. 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_farsight_LedService.cpp:

[cpp]  view plain copy
  1. #define LOG_TAG "LedService"  
  2. #include "utils/Log.h"  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. #include <unistd.h>  
  6. #include <assert.h>  
  7. #include <jni.h>  
  8. #include "../../../hardware/led.h"  
  9.   
  10. static led_control_device_t *sLedDevice = 0;  
  11. static led_module_t* sLedModule=0;   
  12.   
  13. static jint get_count(void)  
  14. {  
  15.     LOGI("%sE", __func__);  
  16.    if(sLedDevice)  
  17.         returnsLedDevice->get_led_count(sLedDevice);  
  18.     else  
  19.        LOGI("sLedDevice is null");  
  20.     return 0;  
  21. }  
  22.   
  23.   
  24. static jint led_setOn(JNIEnv* env, jobject thiz) {  
  25.     LOGI("%sE", __func__);  
  26.     if(sLedDevice) {  
  27.        sLedDevice->set_on(sLedDevice);  
  28.     }else{  
  29.        LOGI("sLedDevice is null");  
  30.     }  
  31.     return 0;  
  32.  }   
  33.   
  34.  static jint led_setOff(JNIEnv* env, jobject thiz) {  
  35.     LOGI("%s E", __func__);  
  36.      if(sLedDevice) {  
  37.         sLedDevice->set_off(sLedDevice);  
  38.      }else{  
  39.          LOGI("sLedDevice is null");  
  40.      }  
  41.      return 0;  
  42.  }  
  43.   
  44. static inline int led_control_open(const structhw_module_t* module,  
  45.      structled_control_device_t** device) {  
  46.     LOGI("%s E ", __func__);  
  47.      returnmodule->methods->open(module,  
  48.         LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);  
  49. }  
  50.   
  51. static jint led_init(JNIEnv *env, jclass clazz)  
  52. {  
  53.     led_module_tconst * module;  
  54.     LOGI("%s E ", __func__);  
  55.      if(hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0){  
  56.         LOGI("get Module OK");  
  57.         sLedModule = (led_module_t *) module;  
  58.         if(led_control_open(&module->common, &sLedDevice) != 0) {  
  59.            LOGI("led_init error");  
  60.            return-1;  
  61.         }  
  62.     }   
  63.   
  64.     LOGI("led_init success");  
  65.     return 0;  
  66. }  
  67.   
  68.    
  69.   
  70.  /* 
  71.   * 
  72.   * Array ofmethods. 
  73.   * Each entryhas three fields: the name of the method, the method 
  74.   * signature,and a pointer to the native implementation. 
  75.   */  
  76. static const JNINativeMethod gMethods[] = {  
  77.     {"_init",    "()Z",(void*)led_init},  
  78.     {"_set_on",   "()I",(void*)led_setOn },  
  79.     {"_set_off""()I",(void*)led_setOff },  
  80.     {"_get_count""()I",(void*)get_count },  
  81. };  
  82.   
  83.   static int registerMethods(JNIEnv* env) {  
  84.      static constchar* const kClassName = "com/hello/LedService";  
  85.      jclass clazz;  
  86.      /* look upthe class */  
  87.      clazz =env->FindClass(kClassName);  
  88.      if (clazz ==NULL) {  
  89.         LOGE("Can't find class %s\n", kClassName);  
  90.          return-1;  
  91.      }   
  92.   
  93.      /* registerall the methods */  
  94.      if(env->RegisterNatives(clazz, gMethods,  
  95.             sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)  
  96.      {  
  97.         LOGE("Failed registering methods for %s\n", kClassName);  
  98.          return -1;  
  99.      }  
  100.      /* fill outthe rest of the ID cache */  
  101.      return 0;  
  102.  }  
  103.   
  104.    
  105.   
  106.  /* 
  107.   * This iscalled by the VM when the shared library is first loaded. 
  108.   */  
  109.  jint JNI_OnLoad(JavaVM* vm, void* reserved) {  
  110.      JNIEnv* env= NULL;  
  111.      jint result= -1;  
  112.     LOGI("JNI_OnLoad");  
  113.      if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  114.         LOGE("ERROR: GetEnv failed\n");  
  115.          gotofail;  
  116.      }  
  117.   
  118.      assert(env!= NULL);  
  119.      if(registerMethods(env) != 0) {  
  120.          LOGE("ERROR: PlatformLibrary nativeregistration failed\n");  
  121.          gotofail;  
  122.      }  
  123.      /* success-- return valid version number */  
  124.      result =JNI_VERSION_1_4;   
  125.   
  126.  fail:  
  127.      return result;  
  128.  }  

         这儿的代码不太容易读,因为里面是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方法到本地方法的映射。

[cpp]  view plain copy
  1. typedef struct {  
  2.          constchar* name;  
  3.          constchar* signature;  
  4.          void*fnPtr;  
  5. } 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()。

[cpp]  view plain copy
  1. // 构造方法  
  2.     public LedService() {        
  3.         int icount ;  
  4.         Log.i ("Java Service" , "do init Native Call" );  
  5.         _init ();           
  6.         icount =_get_count ();  
  7.         Log.d ("Java Service" , "led count = " + icount );  
  8.         Log.d ("Java Service" , "Init OK " );  
  9.     }  

在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指向的函数来间接调用驱动操作代码。

好吧,让我们再来看一个详细的时序图:

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析_第4张图片

不用多解释了。

最后一个文件,HAL对应的Android.mk文件:

@ hardware/Android.mk:

[plain]  view plain copy
  1. LOCAL_PATH := $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3.   
  4. LOCAL_C_INCLUDES += \  
  5.          include/  
  6.   
  7. LOCAL_PRELINK_MODULE := false  
  8. LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw  
  9. LOCAL_SHARED_LIBRARIES := liblog  
  10. LOCAL_SRC_FILES := led.c  
  11. LOCAL_MODULE := led.default  
  12. include $(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) 深入理解

我们从进程空间的概念来分析下我们上面写的代码。

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析_第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架构。

深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析_第6张图片





原文地址 http://blog.csdn.net/mr_raptor/article/details/8082360

你可能感兴趣的:(深入浅出 - Android系统移植与平台开发(十) - led HAL简单设计案例分析)