如何编写、测试android驱动

         最近接手了一个android系统车载电子项目,我是零android基础,零Java基础的,因此开始慢慢的探索之路。

我的目标首先是打通一条从apk到linux kernel的通路,了解清楚android驱动层次、如何工作、如何编写之后再去扩展相关知识,没想到一头扎进去就是一个多星期。

         一个传统型的(旧架构)android驱动应该包含这几块:kernel驱动、HAL驱动、JNI层、service层,再往上就是apk了,程序运行时候的调用关系是:apk-->service--->JNI-->HAL-->kernel驱动。

 一、编写kernel驱动

          一个典型的例子是helloworld,编写kernel驱动主要完成两部分的工作:

          1、配置编译环境-------主要有三个工作:

              a.  设置linux的系统配置菜单,打开文件 android/kernel/drivers/char/Kconfig,添加下面的内容,              

config  HELLO

        tristate"HELLO driver support"

        defaulty

        help

          hellodriver test

         b.把hello.c添加进makefile中,打开文件android/kernel/drivers/char/Makefile,添加下面的内容

             obj-$(CONFIG_HELLO)             += hello.o

         c.修改default_config文件,打开文件  android/kernel/arch/arm/configs/tcc893x_nav360_defconfig,添加下面内容:

             CONFIG_HELLO= y      //  配置成y表示把hello这个驱动编译进内核,当然可以把hello以模块的方式编译,这时就要设置成m

     2、编写代码

           在目录/nav360/android/kernel/drivers/char下创建两个文件,分别是hello.c, hello.h,两个文件的内容如下:

//hello.h

#ifndef  _HELLO_ANDROID_H_  

#define_HELLO_ANDROID_H_  

#include<linux/init.h>  

#include<linux/module.h>  

#include<linux/types.h>  

#include<linux/fs.h>  

#include<linux/proc_fs.h>  

#include<linux/device.h>  

#include <asm/uaccess.h>  

#include<linux/miscdevice.h> 

#include<linux/cdev.h>  

#include<linux/semaphore.h>  

#define  DEBUG   "<0>"

#endif


//hello.c

#include"hello.h"  

 

static inthello_open(struct inode* inode, struct file* filp) { 

    printk(DEBUG"hello_open...\n");

    return 0; 

 

static inthello_release(struct inode* inode, struct file* filp) { 

       printk(DEBUG"hello_release...\n");

    return 0; 

 

static ssize_thello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos){ 

       printk(DEBUG"hello_read...\n");

    return 0; 

 

static ssize_thello_write(struct file* filp, const char __user *buf, size_t count, loff_t*f_pos) { 

       printk(DEBUG"hello_write...\n");

    return 0; 

 

static longhello_ioctl(struct file *filp, unsigned int cmd, unsigned int arg){

       printk(DEBUG"hello_ioctl...\n");

       switch(cmd) {   

       case 0:

              break;

       case 1:

              break;

       default:

              break;

       }

}

 

static structfile_operations DEV_FOPS = {

.owner            = THIS_MODULE,

.open              = hello_open

    .release             = hello_release

    .read                       =hello_read

    .write               =hello_write,

.unlocked_ioctl    = hello_ioctl,

};

 

 

static structmiscdevice misc = {

       .minor = MISC_DYNAMIC_MINOR,

       .name = "HELLO",

       .fops = &DEV_FOPS,

};

 

static int __init hello_init(void){

       printk(DEBUG"hello_init...\n");

       misc_register(&misc);

       return 0;

}

 

static void __exithello_exit(void){

       printk(DEBUG"hello_exit...\n");

       misc_deregister(&misc);

}

 

module_init(hello_init); 

module_exit(hello_exit); 

MODULE_LICENSE("GPL"); 

MODULE_DESCRIPTION("FirstAndroid Driver"); 


至此,kernel驱动就编写完毕,编译kernel之后这个驱动就在内核中了。把系统下载到目标机之后,在/dev目录下会看到一个设备文件HELLO。注意,这个设备文件名是我们在代码中指定的(上面有一行代码是.name = "HELLO",这个HELLO就是设备名字),上一层就是根据这个设备名字来打开驱动的。还需要注意的是, 设备文件HELLO需要root权限才能执行,因此在目标机上更改一下这个文件的执行权限:chmod 777 /dev/HELLO,这种方法比较麻烦。当然也可以有一劳永逸的方法,那就更改init.rc文件,让系统一上电就去自动更改权限,暂时不作详细描述。


二、编写HAL层驱动

        编写HAL层驱动只要按照固定的格式套进去,然后再注意一些细节的地方就OK了。首先关注到android自带的三个结构体,分别是hw_module_t,hw_module_methods_t,hw_device_t,这三个结构体的功能有点绕,通过代码来讲解一下。

        1、进入 到在 hardware/libhardware/include/hardware 目录,添加hello.h文件,添加如下内容

//hello.h

#ifndef  ANDROID_HELLO_INTERFACE_H
#define  ANDROID_HELLO_INTERFACE_H
#include  <hardware/hardware.h>

#define HELLO_HARDWARE_MODULE_ID "hello"

struct hello_module_t
{
    struct hw_module_t common;
};
struct hello_device_t
{
    struct hw_device_t common;
    int fd;
    int (*set_val)(struct hello_device_t* dev,int val );
    int (*get_val)(struct hello_device_t* dev ,int*val);
};
#endif

按照硬件抽象层HAL的规范要求,定义模块ID 即HELLO_HARDWARE_MODULE_ID、模块结构体以及硬件接口结构体。其中fd表示文件描述符,set_val、get_val是要向上一层暴露的接口,如果还有别的接口,可以再后面接续添加。需要注意的是:结构体hello_module_t的第一个成员必须是hw_module_t  变量、结构体hello_device_t的第一成员必须是 hw_device_t变量,为什么?这里其实是用C语言实现JAVA里边的多态。想想看,hw_device_t是android标准的,而hello_device_t是用户的,用户的东西如何跟标准的东西融合在一起?换个角度来说,我们可以hw_device_t看成是父类,而hello_device_t是子类,子类里边有父类的特征(struct hw_device_t common)。子类的拓展功能(set_val、get_val等)如何在标准规范中实现?且看,当上一次函数要求返回hw_device_t(父类)的指针,此时我们把hello_device_t(子类)的指针作为返回值,同样不会出错,因为子类的第一个成员是父类的变量。但是通过这种方式我成功地把其余拓展的接口也给到了上一层。


2、进入到 hardware/libhardware/modules 目录,新建 hello 目录,并添加hello.c 文件,分段讲解一下

#define  LOG_TAG  "HelloStub"

#include  <hardware/hardware.h>
#include  <hardware/hello.h>
#include  <fcntl.h>
#include  <errno.h>
#include  <cutils/log.h>
#include  <cutils/atomic.h>

#define  DEVICE_NAME  "/dev/HELLO"      //在上一章中,我们指定了设备名称,并且事先知道设备文件会放到/dev目录下
#define  MODULE_NAME  "Hello"
#define  MODULE_AUTHOR  "qiufengsyb"

static  int  hello_device_open(const  struct  hw_module_t*  module,  const
char*  name,  struct  hw_device_t**  device);
static  int  hello_device_close(struct  hw_device_t*  device);

static  int  hello_set_val(struct  hello_device_t*  dev,  int  val);
static  int  hello_get_val(struct  hello_device_t*  dev,  int*  val);

static  struct  hw_module_methods_t  hello_module_methods  =  
{
       open:  hello_device_open
};


// hello_module_t的变量名必须为HAL_MODULE_INFO_SYM,因为其实HAL_MODULE_INFO_SYM是一个宏,在hardware.h中有定义为HMI

//系统在库文件中查找该模块的时候是按照HMI这关键字来查找的

 struct  hello_module_t  HAL_MODULE_INFO_SYM=  { 
    common:  
    {
         tag:  HARDWARE_MODULE_TAG,
         version_major:  1,
         version_minor:  0,
         id:  HELLO_HARDWARE_MODULE_ID,
         name:  MODULE_NAME,
         author:  MODULE_AUTHOR,
         methods:  &hello_module_methods,
    }
};

//下面这个函数会在JNI层被调用,也就上一层。且看其传进来的是标准的参数hw_device_t**  device,但是返回的时候

//其实用的是用户定义结构体的hello_device_t的强制转换,这样,set_val, get_val这两个用户接口就以标准的方式向上一层暴露了
static  int  hello_device_open(const  struct  hw_module_t*  module,  const
char*  name,  struct  hw_device_t**  device)  
{
         struct  hello_device_t*  dev;
         ALOGI("Hello  HAL:  read to open device.");
         dev  =  (struct  hello_device_t*)malloc(sizeof(struct  hello_device_t));

         if(!dev)  
         {
            ALOGE("Hello  Stub:  failed  to  alloc  space");
            return  -EFAULT;
         }

         memset(dev,  0,  sizeof(struct  hello_device_t));
        dev->common.tag  =  HARDWARE_DEVICE_TAG;
        dev->common.version  =  0;
        dev->common.module  =  (hw_module_t*)module;
        dev->common.close  =  hello_device_close;
        dev->set_val  =  hello_set_val;
        dev->get_val  =  hello_get_val;

        if((dev->fd  =  open(DEVICE_NAME,  O_RDWR))  ==  -1)  
        {
            ALOGE("Hello  HAL:  failed  to  open  /dev/hello  --%s.",  strerror(errno));free(dev);
            return  -EFAULT;
        }
            *device  =  &(dev->common);
            ALOGI("Hello  HAL:  open  /dev/hello  successfully.");

        return  0;
}

static  int  hello_device_close(struct  hw_device_t*  device)
 {
         struct  hello_device_t*  hello_device  =  (struct  hello_device_t*)device;
        ALOGI("Hello  HAL:  read to close device.");
         if(hello_device)  
         {
            close(hello_device->fd);
            free(hello_device);
         }

         return  0;
}

static  int  hello_set_val(struct  hello_device_t*  dev,  int  val)  
{
        ALOGI("Hello  HAL:  set  value  %d  to  device.",  val);
        write(dev->fd,  &val,  sizeof(val));
        return  0;
}

static  int  hello_get_val(struct  hello_device_t*  dev,  int*  val)  
{
        ALOGI("Hello  HAL:  ready to get value.");
        if(!val)  
        {
            ALOGE("Hello  HAL:  error  val  pointer");
            return  -EFAULT;
        }

        read(dev->fd,  val,  sizeof(*val));
        ALOGI("Hello  Stub:  get  value  %d  from  device",  *val);

        return  0;
}

3、在 hello 目录下新建 Android.mk 文件,Android.mk相当于linux 下的makefile文件,内容如下:

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)

关于Android.mk文件的意思不在这里详细解说,主要关注3个地方,LOCAL_SRC_FILES表示生成目标所依赖的文件,这里是hello.c;LOCAL_MODULE表示生成的目标名;

最后一行include $(BUILD_SHARED_LIBRARY)表示要生成什么样的目标,这里是BUILD_SHARED_LIBRARY,表示生成一个动态库。最后我们的目标文件就是hello.default.so文件,也就通常说的so文件。

4、编译

             在android目录下执行下面命令:mmm  hardware/libhardware/modules/hello, 执行mmm命令之前需要android目录下执行两个命令: . build/envsetup.sh以及   lunch,

5、重新打包 Android 系统镜像 system.img:

      make  snod

重新打包后,system.img 就包含我们定义的硬件抽象层模块 hello.default 了



三、编写JNI层驱动

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

          在 com_android_server_HelloService.cpp 文件中,实现 JNI 方法。注意文件的命令方 法,com_android_server 前缀表示的是包名,表示硬件服务 HelloService 是放在
frameworks/base /services/java 目录下的 com/android/server 目录的,即存在一个命令为com.android.server.HelloService 的类。这里,我们暂时略去 HelloService 类的描述,在下一篇文章中,我们将回 到 HelloService 类来。简单地说,HelloService 是一个提供 Java 接口的硬件访问服务类。


//com_android_server_HelloService.cpp

#define  LOG_TAG  "HelloService"
#include  "jni.h"
#include  "JNIHelp.h"
#include  "android_runtime/AndroidRuntime.h"
#include  <utils/misc.h>
#include  <utils/Log.h>
#include  <hardware/hardware.h>
#include  <hardware/hello.h>
#include  <stdio.h>

namespace android
{
     /*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/
    struct  hello_device_t*  hello_device  =  NULL;
    
    /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器 val 的值*/
    static  void  hello_setVal(JNIEnv*  env,  jobject  clazz,  jint  value)  
    {
        int  val  =  value;
        ALOGI("Hello  JNI:  set  value  %d  to  device.",  val);
        if(!hello_device)  
        {
        ALOGI("Hello  JNI:  device  is  not  open.");
            return;
        }
        hello_device->set_val(hello_device,  val);
   }
    
    /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器 val 的值*/
    static  jint  hello_getVal(JNIEnv*  env,  jobject  clazz)  
    {
         int  val  =  0;
         ALOGI("Hello  JNI:  get value......");
        if(!hello_device)  
        {
            ALOGI("Hello  JNI:  device  is  not  open.");
            return  val;
        }
         hello_device->get_val(hello_device,  &val);

         ALOGI("Hello  JNI:  get  value  %d  from  device.",  val);
         return  val;
    }
    
    /*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
    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);
    }
    
    static  jboolean  hello_init(JNIEnv*  env,  jclass  clazz)  
    {
        hello_module_t*  module;

         ALOGI("Hello  JNI:  initializing......");
         ALOGI("HELLO_HARDWARE_MODULE_ID = %s",HELLO_HARDWARE_MODULE_ID);
        
        if(hw_get_module(HELLO_HARDWARE_MODULE_ID,  (const  struct hw_module_t**)&module)  ==  0)  
        {
             ALOGI("Hello  JNI:  hello  Stub  found.");
             if(hello_device_open(&(module->common),  &hello_device)  ==  0)  
             {
                 ALOGI("Hello  JNI:  hello  device  is  open.");
                 return  0;
             }
            ALOGE("Hello  JNI:  failed  to  open  hello  device.");
            return  -1;
        }
        ALOGE("Hello  JNI:  failed  to  get  hello  stub  module.");
        return  -1;
    }
    static  void  hello_close(JNIEnv*  env,  jobject  clazz)
    {
        ALOGI("Hello  JNI:  close module......");
        if(!hello_device)  
        {
            ALOGI("Hello  JNI:  device  is  not  open.");
            return;
        }
         ALOGI("Hello  JNI:  close module");
        hello_device->common.close((struct  hw_device_t*)hello_device);
        hello_device = NULL;
    }
    /*JNI 方法表*/

//通过这个注册表,可以实现C/C++与JAVA的相互调用,其功能是把右边一列的C/C++接口映射成左边一列的JAVA接口,中间一列描述了这个接口

//的参数以及返回值,比如第一行的"()z",“()”表示参数为空,"z"表示返回值为布尔型。换句话说,JAVA层调用init_native(),其本质是调用hello_init()

    static  const  JNINativeMethod  method_table[]  =  
    {
         {"init_native",  "()Z",  (void*)hello_init},
         {"setVal_native",  "(I)V",  (void*)hello_setVal},
         {"getVal_native",  "()I",  (void*)hello_getVal},
         {"close_native",   "()V",(void*)hello_close},
    };
    
    /*注册 JNI 方法*/
    int  register_android_server_HelloService(JNIEnv  *env)  
    {
        return  jniRegisterNativeMethods(env,  "com/android/server/HelloService",  method_table,  NELEM(method_table));
    }
};

//end of file

注册JNI方法的函数要被调用才能生效, 修改同目录下的 onload.cpp 文件,首先在 namespace android 增加register_android_server_HelloService 函数声明,然后在函数extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)中调用register_android_server_HelloService(env);

至此,JNI层编写结束,编译:~/Android$ mmm  frameworks/base/services/jni

       重新打包镜像文件:~/Android$ make snod


四、编写Service

在 Android 系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序
提供服务。因此,调用这些硬件服务的应用程序与这些硬件服务之间的通信需要 通过代理来
进行。为此,我们要先定义好通信接口。

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

//IHelloService.aidl

package  android.os;

interface  IHelloService  
{
         void  setVal_test(int  val);
         int  getVal_test();
         void close();
}

2、返回到 frameworks/base 目录,打开 Android.mk 文件,修改 LOCAL_SRC_FILES 变量的值:

LOCAL_SRC_FILES += /

..........................

core/java/android/os/IHelloService.aidl \

.......................

3、 编译 IHelloService.aidl 接口:

~/Android$ mmm  frameworks/base

如果编译不过需要重新全编译一下,因为编译aidl的过程中依赖一些中间文件,这些中间文件在之前的编译中被删除了,所以需要重新编译一下来得到这些中间文件。以上纯属猜测。


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

//HelloService.java

package  com.android.server;
import  android.content.Context;
import  android.os.IHelloService;
import  android.util.Slog;

 public  class  HelloService  extends  IHelloService.Stub  
 {
        private  static  final  String  TAG  =  "HelloService";
        static
        {
            System.load("/system/lib/libandroid_servers.so");//在上一节编写的JNI接口,最后编译出来会变成一个动态库,名字为libandroid_servers.so

                                                                                                     //具体名字需要到路径android\frameworks\base\services\jni下查看Android.mk文件中的LOCAL_MODULE

                                                                                                     //注意:执行make snod镜像文件打包后,该库就会被打包到目标机的/system/lib目录
            
        }
        public HelloService()  
        {
            init_native();
        }
        public  void  setVal_test(int  val)  
        {
            setVal_native(val);
        }
        public  int  getVal_test()  
        {
            return  getVal_native();
        }
        public void close()
        {
            close_native();
        }
        private  static  native  boolean  init_native();
        private  static  native  void  setVal_native(int  val);
        private  static  native  int  getVal_native();
        private  static  native  void close_native();
};

//end of file

5、编译:~/Android$ mmm  frameworks/base/services/java

        打包镜像文件::~/Android$ make snod

至此,service层就编译好了,下一步就是要编写apk测试程序。问题来了,上面所述的kernel驱动、hal驱动、jni驱动以及service都是在android源码环境下完成的,而按照一般的习惯,apk测试程序一般都是在集成开发环境下完成的,比如大家用得很火的eclipse,那么直接在eclipse里边new一个HelloService,能识别吗?不能。有两种方式可以解决问题,第一种是把当前的android源码生成一个SDK,然后把这个SDK加载进eclipse里边就行了。第二种是,直接找到service对应的jar包,然后拷贝到eclipse的android工程目录下的lib目录,在通过右键--->Properties--->Java Bulid Path---->Libraries---->Add External JARs把jar包加到工程来就可以了。其中,service对应的jar包在android\out\target\common\obj\JAVA_LIBRARIES\services_intermediates\classes.jar


五、编写apk程序

       这里就不对android工程的布局等进行描述,编写apk的前提是把上一节生成service对应jar包加载进来。代码如下:

package com.hello_driver_test;



import com.android.server.HelloService;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;


public class HelloDriverActivity extends Activity implements  OnClickListener {
    

    int i = 0 ;
    private  final  static  String  LOG_TAG  =  "syb.Hello";
    private  HelloService  helloService  =  null;
    private Button bt_open,bt_close,bt_ioctr,bt_read,bt_write;
    
    public native void Open();
    public native void Close();
    public native void IoCtr();
    public native void Read();
    public native void Write();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        bt_open = (Button) this.findViewById(R.id.button_open);
        bt_close = (Button) this.findViewById(R.id.button_close);
        bt_ioctr = (Button) this.findViewById(R.id.button_ioctr);
        bt_read = (Button) this.findViewById(R.id.button_read);
        bt_write = (Button) this.findViewById(R.id.button_write);
        
        bt_open.setOnClickListener(this);
        bt_close.setOnClickListener(this);
        bt_ioctr.setOnClickListener(this);
        bt_read.setOnClickListener(this);
        bt_write.setOnClickListener(this);
        
        
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.hello_driver, menu);
        return true;
    }
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.button_open:
            helloService  =  new HelloService();
            break;

        case R.id.button_close:
            helloService.close();

            break;
        case R.id.button_ioctr:

            break;
        case R.id.button_read:
            helloService.getVal_test();
            break;
        case R.id.button_write:
            helloService.setVal_test(1);

            break;
            }
        }

}

到此,从apk到service再到JNI再到HAL,最后到kernel驱动的通路就打通了。这个实验的完成参考了《老罗的Android之旅》,感谢老罗的无私共享。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值