android驱动初探(kernel->hal->jni->service->app)

      按照罗老师的android教程,这里把最近andorid驱动调试的一些过程记录在此,菜鸟经历,请指正。

      之前是有点linux驱动基础的,现在着手学习android驱动,首先第一点认识就是: linux内核遵守的是GNU开源协议,所以内核的代码要求开源。如果android设备将硬件驱动完全放在内核,就会导致厂商利益受损。而android系统代码遵循apache协议,并不要求开源。基于这点原因,手机的开发者将最基础的驱动放在内核来实现,而实际对硬件的操作放在用户空间来完成。例如使用串口操作一个GPS模块,内核驱动只是提供串口驱动,而实际上对模块的读写,控制命令都在用户空间中完成。这样也就实现了厂商的保护。    第二点认识就是更加清晰的层级关系。如罗老师配图:

内核层:

      最底层是内核空间(实现最基础的驱动),内核驱动的编写即时linux内核驱动的编写,以字符设备为例,头文件中定义硬件设备的结构体,源文件实现读写,init等操作;完成内核驱动编写后可以加入这个驱动重新编译内核来驱动模拟器,通过adb命令应当能够实现对设备文件的读写操作;这一步测试正常后,就可以在android系统中编写一段C代码来测试驱动的正确性了。可以在external目录下建立一个文件夹,写一段C代码实现设备打开,读写操作,这段代码与在linux系统下测试驱动的代码并无二致。不一样的是这里用的是andoird.mk文件做makefile。这里使用android编译工具mmm编译,编译完后生成的目录在out/target/product/gerneric/system/bin中,有一个可执行文件。用make snod重新打包系统,开启模拟器,之前生成的可执行文件被放在system/bin目录下,用adb进入到这个目录,然后./执行这个文件,就能看到.c文件中printf中打印的信息,通过这些信息来判断设备打开,读写是否正常。


HAL层:

      接下来是HAL层,HAL层属于用户空间,对于HAL层,我的理解就是调用内核中基本的驱动,实现具体的硬件设备驱动,其操作最终的结果是得到一个so库给上层来调用。有一些模块厂商在供货的时候会提供现成so库以及接口说明,按照提供的接口说明,我们就可以直接使用这个库。对于HAL层的学习,我有了以下几点理解:

   1.关于so库的名称问题:

    HAL层提供的so库为什么能够被系统正确的加载?因为他满足系统的书写的规范要求。HAL层驱动模块的名称规范定义在hardware/libhardware/hardware.c中。罗老师的代码中生成的so库的名称为hello.default.so。对于命名这块我的理解是:

 34 /**
 35  * There are a set of variant filename for modules. The form of the filename
 36  * is "<MODULE_ID>.variant.so" so for the led module the Dream variants 
 37  * of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
 38  *
 39  * led.trout.so
 40  * led.msm7k.so
 41  * led.ARMV6.so
 42  * led.default.so
 43  */
 44 
 45 static const char *variant_keys[] = {
 46     "ro.hardware",  /* This goes first so that it can pick up a different
 47                        file on the emulator. */
 48     "ro.product.board",
 49     "ro.board.platform",
 50     "ro.arch"
 51 };
 52 

   

所以在罗老师代码编译生成的so库命名为 hello.default.so.个人理解,望大神指正

2.关于HAl层代码的编写规范:

hardware/libhardware/include/hardware/hardware.h 中对HAL层的书写贵范做出要求,对照这个文件来看HAL层代码就比较清晰了。

在这个系统.h文件中,给出了这些要求: 

        (1)对于硬件模块结构体的定义:实例命名必须为 HAL_MODULE_INFO_SYM

                                               结构体的第一部分必须为hw_module_t  关于hw_module_t的实例化参考hardware.h

        (2)对于硬件设备结构体的定义:结构体的第一部分必须以hw_device_t为开始 关于hw_device_t的实例化也参考hardware.h

                                 结构体接下来的部分是对设备的其他读写操作;

参考罗老师的代码,具体的实现如下:

       首先 在hardware/libhardware/hardware/include/hardware中新建hello.h文件

             这个文件中定义模块ID,声明硬件模块结构体hello_module_t,成员为hw_module_t结构体;

                                                     声明硬件设备接口结构体hello_device_t,第一个成员为hw_device_t结构体,接下来成员为文件指针及读写操作函数;

       接下来,在hardware/libhardware/modules中创建hello目录,编写hello.c文件

             这个文件包含hardware.h hello.h这两个头文件;

             首先按照上述规则实现模块结构体的实例化,第一个成员也是唯一一个成员hw_module_t的最后一个成员是一个方法hw_module_method_t类型的hello_module_methods指针,同时实例hw_module_method_t结构体为hello_module_methods,这一方法结构体变量的成员为设备打开函数。

             接下来具体实现设备打开函数,在设备打开函数中,为设备结构体赋值,执行文件打开操作(fd = open());

             接下来实现文件 读写 关闭操作;read write close等函数实现。

最后给HAL层的hello目录添加android.mk进行编译,重新打包,就可以生成hello.default.so库文件了。

这里有一个注意事项:罗老师有讲过app一般是没有权限调用/dev/hello这一设备文件的,会出现 Hello Stub:failed to open /dev/hello  -- permission denied.

解决问题是在源码目录system/core/rootdir下的ueventd.rc文件添加一行:  /dev/hello 0666 root root   然后重新编译源码

  我照做后发现仍然没解决,网上搜索android5以上又增加了其他安全权限机制,但我用的是4.4,应该是可以的,折腾许久才意识到,这里要求重新编译源码,即直接make -j8这样的操作,而我混淆了打包与编译,仅仅用了make snod。重新make -j8后问题解决。


JNI


有了so库文件之后,接下来就是准备让系统的java代码来调用库了,然而java代码直接调用C函数是不行的,所以必须中间再添加一个翻译的中间层,即JNI层,这一层实现的功能是编写JNI方法,提供一个中间层,使得上层代码能够调用下层的硬件服务。关于JNI,我个人看了《android框架揭秘》这本书上的描述(金泰延 等韩国编的书),写的比较详细,可以参考。

    主要的实现包含:包含相关头文件;

                                   实现hello_init ,hello_getVal, hello_setVal三个JNI方法;

                                   注册这个文件,加入到onload中

    具体的实现见罗老师的教程;


Framework


得到了JNI的三个方法以后,为了让应用app调用方便,我们在framework层添加硬件服务,个人的理解,这样这个服务加载到系统服务中,就可以开机启动,这样方便应用程序直接调用JNT函数。

    这一层的做法是:

    首先:在framework/base/core/java/android/os目录新增一个aidl接口文件的定义(这里建议对应用程序IPC服务有一定了解)

              接口中定义了setVal getVal这两个方法;

    接下来,把这个aidl文件添加到LOCAL_SRC_FILES中,(framework/base目录)

    编译base目录mmm  会生成 IHelloService.Stub

    接下来,进入framework/base/service/java/com/android/server目录新增HelloService.java文件。

  1. package com.android.server;  
  2. import android.content.Context;  
  3. import android.os.IHelloService;  
  4. import android.util.Slog;  
  5. public class HelloService extends IHelloService.Stub {  
  6.     private static final String TAG = "HelloService";  
  7.     HelloService() {  
  8.         init_native();  
  9.     }  
  10.     public void setVal(int val) {  
  11.         setVal_native(val);  
  12.     }     
  13.     public int getVal() {  
  14.         return getVal_native();  
  15.     }  
  16.       
  17.     private static native boolean init_native();  
  18.         private static native void setVal_native(int val);  
  19.     private static native int getVal_native();  
  20. };  
同目录的SystemServer.java文件,在ServerThread::run函数中增加加载HelloService的代码,再make snod重新打包,这样系统启动时就能自动启动这个服务了。


App


最后就是APP层,操作比较简单

在onCreate时候获取服务:

                              

  1.     helloService = IHelloService.Stub.asInterface(  
  2.         ServiceManager.getService("hello"));  
    需要的时候调用即可:

写:helloService.setVal(val);  

读:helloService.getVal();  


以上是学习罗老师博客中的一点笔记记录,请大神指正。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值