Android应用层到Framework到HAL再到驱动层的整个流程分析

Posted Jarry_le

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android应用层到Framework到HAL再到驱动层的整个流程分析相关的知识,希望对你有一定的参考价值。

本篇参考老罗的实例进行总结。老罗写六篇,层层嵌套,他告诉了我们流程,但没有说编程思想,所以,即使知道怎么做也很快会忘调,因此打算总结下每层之间是怎么调用的,以加深印象。不对细节进行探讨。细节可以参见老罗的blog:http://blog.csdn.net/luoshengyang/article/details/6567257

老罗的分析是从驱动到应用层的,但我想从app开发者的角度去反思这个流程,我反过来说吧。


Tips:老罗这个例子,太多hello相关的函数和类了,要区分的话,目录是个好东西!要注意当前说的层在哪个目录!我会把它加粗。

Tips2:封装是理清各层关系的关键,除了驱动,上面的app/framework(JNI)/HAL层主要工作都是封装。

应用层->Framwork层->HAL层

问题一.作为app开发者,如果我想调用硬件驱动的一个功能,我要怎么做?

1.先按常规办法,做好UI界面。可以IDE中调试好。

2.在事件触发函数里,调用SystemService,获取底层的服务,并把它转化为aidl接口

[javascript] view plain copy
  1. import android.os.IHelloService;  
  2. public class Hello extends Activity implements OnClickListener    
  3.    private IHelloService helloService = null;  
  4.     .....  
  5.    public void onCreat(Bundle savedInstanceState)  
  6.     .....  
  7.        helloService = IHelloService.Stub.asInterface( ServiceManager.getService("hello"));   
  8.     .....  
  9.     
  10.   
3.在onClick函数里调用该接口,让service执行目标功能。 [cpp] view plain copy
  1. public void onClick(View v)    
  2.   .....  
  3.   helloService.setVal(val);  
  4.   .....  
  5.   

问题二.如果要在SDK源码里测试,有什么要注意?——Android.mk

1.在SDK里,aidl文件,会产生在out/target/common/obj目录下,自己去搜吧(参照http://blog.csdn.net/xzongyuan/article/details/38119551)

2.如果你在Eclipse上写aidl文件,会产生在apk源码目录的gen下。因此,如果要把源码复制到SDK,要把gen目录删掉,不然这个目录会生成aidl相关的java文件,会和第一步生成的产生冲突。

3.在源码目录新增加Android.mk,这样SDK编译的时候,才会把该源码编译进去。例如:可以把自己的测试代码放到:/package/experimental/hello 下,并在该目录新增Android.mk,这点可以查看兄弟目录的文件结构。

Android.mk的文件内容如下:

LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := Hello include $(BUILD_PACKAGE)

问题三.要怎样设计一个IHelloService供上层调用?

1.进入到frameworks/base/core/java/android/os目录,新增IHelloService.aidl接口定义文件:

      USER-NAME@MACHINE-NAME:~/Android$ cd frameworks/base/core/java/android/os

      USER-NAME@MACHINE-NAME:~/Android/frameworks/base/core/java/android/os$ vi IHelloService.aidl

      IHelloService.aidl定义了IHelloService接口:

[cpp] view plain copy
  1. package android.os;    
  2.      
  3. interface IHelloService     
  4.     void setVal(int val);    
  5.     int getVal();    
  6.     
2.为啥必须是os文件夹下呢?android下级目录还有很多其它目录,我猜测应该都可以放进去的。只需要你改下

/framework/base下的Android.mk,可见Android.mk在SDK里面是很重要的,它是个组织文件的Makefile。例子:

返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加IHelloService.aidl源文件

[cpp] view plain copy
  1. LOCAL_SRC_FILES += /  
  2.   
  3.    ....................................................................  
  4.   
  5.    core/java/android/os/IVibratorService.aidl /  
  6.   
  7.    core/java/android/os/IHelloService.aidl /  
  8.   
  9.    core/java/android/service/urlrenderer/IUrlRendererService.aidl /  
  10.   
  11.    .....................................................................  

编译后,会生成 IHelloService.java(就是上面提到的/out/target/common下的目录),这个文件的IHelloService接口,会实现一个Stub子接口,该子接口提供了一个函数, [cpp] view plain copy
  1. public static com.styleflying.AIDL.forActivity asInterface(android.os.IBinder obj)    
  2.     
  3.    if ((obj==null))     
  4.    return null;    
  5.     

这个函数就是前面提到的供Activity用的asInterface了。

activity里的使用方法如下,把具体的服务调出来了:

[cpp] view plain copy
  1. helloService = IHelloService.Stub.asInterface( ServiceManager.getService("hello"));   

3.这个IHelloService对象应该放在哪里?

定一个类,继承它,并封装它的函数,最后把它注册到ServiceManager就行了

进入到frameworks/base/services/java/com/android/server目录,新增HelloService.java文件:

[cpp] view plain copy
  1. <span><span class="keyword">package</span><span> com.android.server;  </span></span><span><span class="keyword">  
  2. import</span><span> android.content.Context;  </span></span><span><span></span></span><span><span class="keyword">  
  3. import</span><span> android.os.IHelloService;  </span></span><span></span><span><span class="keyword">  
  4. import</span><span> android.util.Slog;    
  5. </span></span>public class HelloService extends IHelloService.Stub     
  6.     private static final String TAG = "HelloService";    
  7.     /*封装IHelloService接口的函数*/  
  8.     HelloService()       
  9.         init_native();    
  10.         
  11.     public void setVal(int val)     
  12.         setVal_native(val);    
  13.            
  14.     public int getVal()     
  15.         return getVal_native();    
  16.         
  17.         
  18.     private static native boolean init_native();    
  19.     private static native void setVal_native(int val);    
  20.     private static native int getVal_native();    
  21. ;    
[cpp] view plain copy
  1. 要注意,service是怎么调用jni的,是通过后面三句声明:private static native xxx();  
修改同目录的SystemServer.java文件,在ServerThread::run函数中增加加载HelloService的代码: [cpp] view plain copy
  1. @Override  
  2.   
  3.     public void run()   
  4.   
  5.     ....................................................................................  
  6.   
  7.            try   
  8.   
  9.                  Slog.i(TAG, "DiskStats Service");  
  10.   
  11.                  ServiceManager.addService("diskstats"new DiskStatsService(context));  
  12.   
  13.             catch (Throwable e)   
  14.   
  15.                  Slog.e(TAG, "Failure starting DiskStats Service", e);  
  16.   
  17.              
  18.   
  19.            try   
  20.   
  21.                  Slog.i(TAG, "Hello Service");  
  22.   
  23.                  ServiceManager.addService("hello"new HelloService());  
  24.   
  25.             catch (Throwable e)   
  26.   
  27.                  Slog.e(TAG, "Failure starting Hello Service", e);  
  28.   
  29.              
  30.   
  31.     ......................................................................................  
  32.   
  33.             

4.JNI怎么关联JAVA和C语言?和上面的对象有什么关系?

上面提到的都是Java文件,包括SystemServer也是!JAVA到驱动,肯定有个转化,JNI就负责这个转化功能,那究竟是怎么实现的?其实第3点提到的HelloService.java所封装的IHelloService.Stub接口的函数,就是JNI往上层提供的函数。

这时,再回想下第1点,它提供了setVal函数,但是封装函数时,变为了setval_native。哪里做了这个转化?在JNI类里定义的,JNI类里主要做了以下事情:

1).引入HAL层的头文件

2).通过hw_get_module函数(HAL层通用函数)获取HAL层中,定义好的对应ID为(HELLO_HARDWARE_MODULE_ID)的module。

在HAL层的hardware/hello.h这个自定义头文件中定义了:

[cpp] view plain copy
  1. #define HELLO_HARDWARE_MODULE_ID "hello"   
3).有了module,就调用这个module的open方法来获得 hw_device_t关键类!这个是在HAL层定义的“硬件接口结构体”)。用处后面会提到。为了使得JNI层的设计更加模块化,为该调用动作做一个封装。因此有: [cpp] view plain copy
  1. static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device)     
  2.         return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);    
  3.         
这个函数作用:传入第二步获得的HAL层的module,和一个空hw_device_t结构体。调用module的method(这个method在HAL层定义,详情请看后面介绍),这样,module调用open函数,打开了hw_device_t结构体变量hello_device。之后,setVal和getVal就利用这个hello_device实现HAL层读写操作了。可见,JNI层的关键是通过module的method来初始化hw_device_t,这是其它函数的根本。

4).最后,把jni文件(cpp文件)定义的函数,作封装,创建“方法表”,以供aidl对应的java文件调用。以老罗的例子来说就是供IHelloService.java里的Stub接口调用

[cpp] view plain copy
  1. static const JNINativeMethod method_table[] =     
  2.      "init_native""()Z", (void*)hello_init,    
  3.      "setVal_native""(I)V", (void*)hello_setVal,    
  4.      "getVal_native""()I", (void*)hello_getVal,    
  5.  ;    
这时,我们能看到setVal_native了,可见,java中的setVal_native对应jni文件的hello_setVal。仅仅是一个映射,没作什么特别高深的事。

5)注册上面的方法表。怎么注册?因为是在onload.cpp进行注册,而不是直接在该类文件下注册,所以得先把jniRegisterNativeMethods封装为register_android_server_HlloService。然后把该封装放到同目录下的onload.cpp文件

[cpp] view plain copy
  1. int register_android_server_HelloService(JNIEnv *env)     
  2.    return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));    
  3.        
在onload.cpp里 [cpp] view plain copy
  1. namespace android   
  2.   
  3. ..............................................................................................  
  4.   
  5. int register_android_server_HelloService(JNIEnv *env);  
  6.   
  7. ;  
  8. //在JNI_onLoad增加register_android_server_HelloService函数调用:  
  9. extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved)  
  10.   
  11.  .................................................................................................  
  12.  register_android_server_HelloService(env);  
  13.  .................................................................................................  
  14.   

这样,在Android系统初始化时,就会自动加载该JNI方法调用表。

6.最后在Android.mk这个组织者文件下添加说明:

修改同目录下的Android.mk文件,在LOCAL_SRC_FILES变量中增加一行:

      LOCAL_SRC_FILES:= \\       com_android_server_AlarmManagerService.cpp \\       com_android_server_BatteryService.cpp \\       com_android_server_InputManager.cpp \\ 。。。。。。。。。。。。。。。。。。。。。。。。。。        com_android_server_HelloService.cpp /       onload.cpp

如下面的例子:

进入到frameworks/base/services/jni目录,新建com_android_server_HelloService.cpp文件:

包含如下头文件:

[cpp] view plain copy
  1. #define LOG_TAG "HelloService"    
  2. #include "jni.h"    
  3. #include "JNIHelp.h"    
  4. #include "android_runtime/AndroidRuntime.h"    
  5. #include <utils/misc.h>    
  6. #include <utils/Log.h>    
  7. #include <hardware/hardware.h>    
  8. #include <hardware/hello.h>    
  9. #include <stdio.h>    
注意:头文件引入了HAL层定义的头文件hello.h和hardware.h。还有个AndroidRuntime.h(还没研究过,估计是jni需要的吧)

接着定义hello_init、hello_getVal和hello_setVal三个JNI方法:

[cpp] view plain copy
  1. namespace android    
  2.     
  3.     /*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/    
  4.         struct hello_device_t* hello_device = NULL;    
  5.     /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/    
  6.         static void hello_setVal(JNIEnv* env, jobject clazz, jint value)     
  7.         int val = value;    
  8.         LOGI("Hello JNI: set value %d to device.", val);    
  9.         if(!hello_device)     
  10.             LOGI("Hello JNI: device is not open.");    
  11.             return;    
  12.             
  13.             
  14.         hello_device->set_val(hello_device, val);    
  15.         
  16.         /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/    
  17.     static jint hello_getVal(JNIEnv* env, jobject clazz)     
  18.         int val = 0;    
  19.         if(!hello_device)     
  20.             LOGI("Hello JNI: device is not open.");    
  21.             return val;    
  22.             
  23.         hello_device->get_val(hello_device, &val);    
  24.             
  25.         LOGI("Hello JNI: get value %d from device.", val);    
  26.         
  27.         return val;    
  28.         
  29.         /*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/    
  30.     static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device)     
  31.         return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);    
  32.         
  33.         /*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/    
  34.     static jboolean hello_init(JNIEnv* env, jclass clazz)     
  35.         hello_module_t* module;    
  36.             
  37.         LOGI("Hello JNI: initializing......");    
  38.         if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0)     
  39.             LOGI("Hello JNI: hello Stub found.");    
  40.             if(hello_device_open(&(module->common), &hello_device) == 0)     
  41.                 LOGI("Hello JNI: hello device is open.");    
  42.                 return 0;    
  43.                 
  44.             LOGE("Hello JNI: failed to open hello device.");    
  45.             return -1;    
  46.             
  47.         LOGE("Hello JNI: failed to get hello stub module.");    
  48.         return -1;          
  49.         
  50.         /*JNI方法表*/    
  51.     static const JNINativeMethod method_table[] =     
  52.         "init_native""()Z", (void*)hello_init,    
  53.         "setVal_native""(I)V", (void*)hello_setVal,    
  54.         "getVal_native""()I", (void*)hello_getVal,    
  55.     ;    
  56.         /*注册JNI方法*/    
  57.     int register_android_server_HelloService(JNIEnv *env)     
  58.     return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));    
  59.         
  60. ;    

小结

至此,就打通了app和HAL的通道了。

1.hw_device_t是关键,jni文件里的函数就是调用了它的函数,并没有作硬件操作,如下面函数:

[cpp] view plain copy
  1. static void hello_setVal(JNIEnv* env, jobject clazz, jint value)     
  2.        int val = value;    
  3.        ...............//判断hw_device_t变量是否为空  
  4.        hello_device->set_val(hello_device, val);    
  5.        
2.JNI的注册也并不难理解,就是根据语法填写方法表method_table[],然后封装一个函数,并把这个函数 声明在onload.cpp中。

3.另外一个关键是自定义的Service类,这个类主要是用来实现IHelloService.aidl提供的接口;要注意的是,系统会自动把IHelloService.aidl编译为IHelloSerivce.java。这个文件提供了Stub这个代理接口,所以,Service类主要是实现该java的代理接口Stub。如下:

[cpp] view plain copy
  1. public class HelloService extends IHelloService.Stub     
  2.     private static final String TAG = "HelloService";    
  3.     /*封装IHelloService接口的函数*/  
  4.     HelloService()       
  5.         init_native();    
  6.         
  7.     public void setVal(int val)     
  8.         setVal_native(val);    
  9.            
  10.     public int getVal()     
  11.         return getVal_native();    
  12.         
怎么实现的呢?就是把JNI对上层声明的xxx_native函数封装在接口里。可见,Service类也只是封装,没什么特别高深的。

4.最后就是activity怎么调用HAL层的函数的问题了:先通过Stub代理类获得Service后,然后通过Service封装好的函数(调用JNI函数),实现JAVA到C的过渡。不过这一步还没达到驱动,只是到了HAL层。HAL层实际上也只是封装驱动的操作,所以,下面要讨论,HAL层是怎么“封装”驱动的。


HAL层的实现

HAL头文件分析


进入到在hardware/libhardware/include/hardware目录,新建hello.h文件

下面的Module ID,就是上一节提到的,会被JNI文件调用,用来获取对应的module实例。还记得module实例用来干吗吧,用来open一个hw_device_t变量。下面的module和device结构体都属于HAL层的,不要和驱动混淆了,它仅仅是起封装作用(一个中介而已)。

两个结构体都有一个成员变量:common,作者这样写有什么含义?它意思应该是该HAL模块和设备结构只是在通用结构体(hw_module_t和hw_device_t)上加了一些成员变量和方法,封装了一个通用的HAL接口(其它HAL对象也会这样做),该通用结构还有很多函数,可以定义成其它名,如module或device。只要你在JNI引用HAL函数的时候,知道你调用的hello_module_t和hello_device_t里面还有一个通用结构体就行了。另外它还有一个作用,下一小节会提到。

[cpp] view plain copy
  1. #ifndef ANDROID_HELLO_INTERFACE_H    
  2. #define ANDROID_HELLO_INTERFACE_H    
  3. #include <hardware/hardware.h>    
  4.     
  5. __BEGIN_DECLS    
  6.     
  7. /*定义模块ID*/    
  8. #define HELLO_HARDWARE_MODULE_ID "hello"    
  9.     
  10. /*硬件模块结构体*/    
  11. struct hello_module_t     
  12.     struct hw_module_t common;    
  13. ;    
  14.     
  15. /*硬件接口结构体*/    
  16. struct hello_device_t     
  17.     struct hw_device_t common;    
  18.     int fd;    //对应我们将要处理的设备文件"/dev/hello"  
  19.     int (*set_val)(struct hello_device_t* dev, int val);    
  20.     int (*get_val)(struct hello_device_t* dev, int* val);    
  21. ;    
  22.     
  23. __END_DECLS    
  24.     
  25. #endif    
让我们看看hw_module_t有什么作用?源码解释:

/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of this data structure must begin with hw_module_t
 * followed by module specific information.
 */

typedef struct hw_module_t



HAL实现文件分析:

module必须通过下面的宏HAL_MODULE_INFO_SYM来初始化通用结构体,tag也是固定的宏,这是HAL规范要求。

[cpp] view plain copy
  1. /*模块方法表*/    
  2. static struct hw_module_methods_t hello_module_methods =     
  3.     open: hello_device_open    
  4. ;    
[cpp] view plain copy
  1. /** 
  2.  * Name of the hal_module_info 
  3.  */  
  4. #define HAL_MODULE_INFO_SYM         HMI  
  5.   
  6. /*模块实例变量*/    
  7. struct hello_module_t HAL_MODULE_INFO_SYM =     
  8.     common:     
  9.         tag: HARDWARE_MODULE_TAG,    
  10.         version_major: 1,    
  11.         version_minor: 0,    
  12.         id: HELLO_HARDWARE_MODULE_ID,    
  13.         name: MODULE_NAME,    
  14.         author: MODULE_AUTHOR,    
  15.         methods: &hello_module_methods,  //对上层提供了函数表,只提供了open,其它函数如close/getVal/setVal都在hello_device_t的成员变量中  
  16.         
  17. ;    

初始化完后,就有了name/method/author等变量值了,现在module的任务已经完成,可以不管这个变量了。

然后就是实现hello_device_open,用来填充hello_module_t里的methods成员,将会被JNI层调用。这个函数主要是填充了hello_device_t里的成员变量,包括通用的hw_device_t结构体。如下:

[cpp] view plain copy
  1. static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)     
  2.     struct hello_device_t* dev;dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));    
  3.         
  4.     if(!dev)     
  5.         LOGE("Hello Stub: failed to alloc space");    
  6.         return -EFAULT;    
  7.         
  8.     
  9.     memset(dev, 0, sizeof(struct hello_device_t));    
  10.     dev->common.tag = HARDWARE_DEVICE_TAG;    
  11.     dev->common.version = 0;    
  12.     dev->common.module = (hw_module_t*)module;    
  13.     dev->common.close = hello_device_close;    
  14.     dev->set_val = hello_set_val;dev->get_val = hello_get_val;    
  15.     
  16.     if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1)     
  17.         LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));free(dev);    
  18.         return -EFAULT;    
  19.         
  20.     
  21.     *device = &(dev->common);    
  22.     LOGI("Hello Stub: open /dev/hello successfully.");    
  23.     
  24.     return 0;    
  25.     

最后那个*device=&(dev->common)有什么作用?device是传进来的参数,是hw_device_t指针的指针,这里把hello_devict_t 的common的指针传给它。回想JNI层做了什么?它通过这个hw_device_t指针,调用HAL的函数。且慢,hw_device_t是个成员变量,它怎么调用身为兄弟成员变量的函数? 有读者已经分析了,如下:

JNI层的com_android_server_HelloService.cpp

/*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device)
      return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);

由此可见*device=&(dev->common)返回的是(struct hw_device_t*),然后又传给了(struct hello_device_t*)...,问题是何不支持返回*device =dev?

后来查看了一下hardware.h,得到以下原型:
typedef struct hw_module_methods_t
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id,
    struct hw_device_t** device);
hw_module_methods_t;
原来Open函数的原型中,第三个参数的类型明确指出是: struct hw_device_t** device,而不能随便是我们定义的(struct hello_device_t**)


因为hello_device_t的第一个成员变量是common,它的类型为hw_device_t,所以可以把一个hello_device_t指针强制转换为hw_device_t指针。这种用法在linux内核中很普遍。


 DEVICE_NAME定义为"/dev/hello"。由于设备文件是在内核驱动里面通过device_create创建的,而device_create创建的设备文件默认只有root用户可读写,而hello_device_open一般是由上层APP来调用的,这些APP一般不具有root权限,这时候就导致打开设备文件失败:

      Hello Stub: failed to open /dev/hello -- Permission denied.       解决办法是类似于Linux的udev规则,打开Android源代码工程目录下,进入到system/core/rootdir目录,里面有一个名为ueventd.rc文件,往里面添加一行:        /dev/hello 0666 root root

HAL小结

methods->open规定了第三个参数只能是通用结构体。但JNI操作的是自定义的hello_device_t结构体的内部函数,所以把common放在第一个成员变量的好处是,两者可以互相强制转化,既保证了methods->open的通用性,又保证了JNI能通过通用结构体hw_device_t获得自定义的hello_device_t,这样,JNI就能通过通用结构体的指针调用HAL函数了。这种办法很巧妙,要好好记住。

定义hello_device_close、hello_set_val和hello_get_val这三个函数:

前面的open函数里,已经把close函数指针赋给了“common通用结构体”(hw_device_t)和setVal/getVal函数指针赋给“自定义的变量”(hello_device_t)。下面是实现方法,大家关注下这些方法是怎么实现的。

close函数传给common变量,它主要是通过hello_device_t的文件描述符fd,来执行读写操作,调用了文件操作函数close/write/read(#include <fcntl.h> 
)。

[cpp] view plain copy
  1. static int hello_device_close(struct hw_device_t* device)     
  2.     struct hello_device_t* hello_device = (struct hello_device_t*)device;    
  3.     
  4.     if(hello_device)     
  5.         close(hello_device->fd);    
  6.         free(hello_device);    
  7.         
  8.         
  9.     return 0;    
  10.     
  11.     
  12. static int hello_set_val(struct hello_device_t* dev, int val)     
  13.     LOGI("Hello Stub: set value %d to device.", val);    
  14.     
  15.     write(dev->fd, &val, sizeof(val));    
  16.     
  17.     return 0;    
  18.     
  19.     
  20. static int hello_get_val(struct hello_device_t* dev, int* val)     
  21.     if(!val)     
  22.         LOGE("Hello Stub: error val pointer");    
  23.         return -EFAULT;    
  24.         
  25.     
  26.     read(dev->fd, val, sizeof(*val));    
  27.     
  28.     LOGI("Hello Stub: get value %d from device", *val);    
  29.     
  30.     return 0;    
  31.     

最后,还要修改Android.mk文件

  LOCAL_PATH := $(call my-dir)

      include $(CLEAR_VARS)       LOCAL_MODULE_TAGS := optional       LOCAL_PRELINK_MODULE := false       LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw       LOCAL_SHARED_LIBRARIES := liblog       LOCAL_SRC_FILES := hello.c       LOCAL_MODULE := hello.default       include $(BUILD_SHARED_LIBRARY)
      注意,LOCAL_MODULE的定义规则,hello后面跟有default,hello.default能够保证我们的模块总能被硬象抽象层加载到。

USER-NAME@MACHINE-NAME:~/Android$ mmm hardware/libhardware/modules/hello
      编译成功后,就可以在out/target/product/generic/system/lib/hw目录下看到hello.default.so文件了。 HAL层的文件,编译后,都会在out/target下生成so文件。

怎么调用HAL生成的.so文件?

这是我猜测的:

应该是在系统启动的时候,把所有的.so文件读进来,这样,系统里有了module实例,上层(JNI)通过hw_get_module函数找到对应的module,然后进行后续的操作:先open,得到一个hello_device_t,然后调用它的成员变量实现各种底层操作。

HAL层->驱动层

这两层之间的交互,主要是通过文件节点。底层的驱动,通常会在/dev  , /sys/class , /proc下生成不同驱动的文件节点。然后HAL通过上面一节提到的write/read/open/close函数对其进行操作。还有ioctl或者其它通讯机制,之前有学过udev,它使用netlink机制,而不是文件节点。


驱动编写要点

进入到kernel/common/drivers目录,新建hello目录:

 USER-NAME@MACHINE-NAME:~/Android$ cd kernel/common/drivers

 USER-NAME@MACHINE-NAME:~/Android/kernel/common/drivers$ mkdir hello

在hello目录中增加hello.h文件:
[cpp] view plain copy
  1. #ifndef _HELLO_ANDROID_H_    
  2. #define _HELLO_ANDROID_H_    
  3.     
  4. #include <linux/cdev.h>    
  5. #include <linux/semaphore.h>    
  6.     
  7. #define HELLO_DEVICE_NODE_NAME  "hello"    
  8. #define HELLO_DEVICE_FILE_NAME  "hello"    
  9. #define HELLO_DEVICE_PROC_NAME  "hello"    
  10. #define HELLO_DEVICE_CLASS_NAME "hello"    
  11.     
  12. struct hello_android_dev     
  13.     int val;    
  14.     struct semaphore sem;    
  15.     struct cdev dev;    
  16. ;    
  17.     
  18. #endif   

为了让文件节点能被fctl.h的read/write/close/open操作,必须在驱动提供file_operations。突然感觉,从上层分析到底层,比较好理解这里为啥定义了个file_operations。

[cpp] view plain copy
  1. /*传统的设备文件操作方法*/    
  2. static int hello_open(struct inode* inode, struct file* filp);    
  3. static int hello_release(struct inode* inode, struct file* filp);    
  4. static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);    
  5. static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);    
  6.     
  7. /*设备文件操作方法表*/    
  8. static struct file_operations hello_fops =     
  9.     .owner = THIS_MODULE,    
  10.     .open = hello_open,    
  11.     .release = hello_release,    
  12.     .read = hello_read,    
  13.     .write = hello_write,     
  14. ;    

这个hello_fops使用传统的设备操作方法进行初始化,那什么是不传统的呢?就是使用了统一接口。上层只需要用标准的fctl.h函数就可以操作fd对应的文件节点了,而不需要知道这个module的read函数是命名为xx_read,还是yy_read。

那传统的hello_open设备设备操作方法里,又做了什么事?

[cpp] view plain copy
  1. /*打开设备方法*/    
  2. static int hello_open(struct inode* inode, struct file* filp)     
  3.     struct hello_android_dev* dev;            
  4.         
  5.     /*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/    
  6.     dev = container_of(inode->i_cdev, struct hello_android_dev, dev);    
  7.     filp->private_data = dev;    
  8.         
  9.     return 0;    
  10.     
看来,只是通过已经初始化的inode->i_cdev变量(cdev类型),初始化一个hello_android_dev类型。这个转化是怎么实现的?container_of第三个参数dev是第二个参数的成员变量,而头文件把这个dev定义为cdev,只要第一个和第三个参数同类型,就可以基于已有的第一个参数初始化第二个参数。

那么第一个参数inode->i_cdev是在哪里初始化的呢?

module_init(hello_init)->hello_init()->hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL)->__hello_setup_dev(hello_dev)

->cdev_init(&(dev->dev), &hello_fops)-> cdev_add(&(dev->dev),devno, 1)

这样,cdev在系统初始化时就init了,并注册到字符设备列表中。

注意:cdev_init(&(dev->dev), &hello_fops)把传统操作函数传给了字符设备。

到这里,我觉得还是没说清楚什么是传统函数,我查了下file_operation的结构体。它已经规定了函数的输入参数类型,即我们要增加新的操作时候,要参考该结构体来编写。

[cpp] view plain copy
  1. struct file_operations   
  2.   
  3.   struct module *owner;  
  4.   
  5.   ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);   
  6.   
  7.   ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);  
  8.   
  9.   unsigned int (*poll) (struct file *, struct poll_table_struct *);  
  10. .............................................  
  11.   int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);  
  12.   
  13.   int (*mmap) (struct file *, struct vm_area_struct *);  
  14.   
  15.   int (*open) (struct inode *, struct file *);  
  16.   
  17.   int (*release) (struct inode *, struct file *);  
  18.    
  19. ;<span></span>   

最后是创建文件节点

linux提供了易用的函数,没什么难度,就不详谈了。

/*在/sys/class/目录下创建设备类别目录hello*/

class_create();

 /*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/ 

device_create()

 /*在/sys/class/hello/hello目录下创建属性文件val*/

device_create_file()

dev_set_drvdata(temp, hello_dev);

/*创建/proc/hello文件*/    

hello_create_proc();

对应的有destroy函数,具体可以参见http://blog.csdn.net/luoshengyang/article/details/6568411

以上是关于Android应用层到Framework到HAL再到驱动层的整个流程分析的主要内容,如果未能解决你的问题,请参考以下文章

android hal层怎么跟framework层通信

从java层到framework到JNI到HAL到kernel的hello 例子

android4.2.2---1. hal层与framework层的调用

[Android P]OpenCamera详细分析(Camera2+Hal3)

android treble项目&&HIDL学习总结

Android深度探索HAL读书笔记9