Android HAL层开发

come from : https://www.jianshu.com/p/fa36e5faea67

一直想深入Android底层开发,首先就从写一个完整的HAL层开发demo开始吧,步骤确实有很多,对我们这种不熟悉c/c++开发的人来说,确实是很痛苦,我看这简单的demo都要理解半天。下面我就一步步的来实现HAL层开发,附代码。

我这里简单的归纳了下,一共8大步骤

  1. linux驱动实现
  2. 驱动测试
  3. hal层实现
  4. aidl实现
  5. service java实现
  6. service jni 实现
  7. 注册service和jni方法
  8. android app调用测试

下面我一步步实现。

开发环境:Ubuntu 14.04
        Android源码 2.3.1
        内核 2.6.9

linux驱动实现

下面我就简单实现一个字符驱动

//
// Created by javalong on 17/3/24.
//

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
struct test2_cdev{
    char val;
    struct cdev cv;
};

dev_t devNo2;
struct test2_cdev *test2_c;

int test2_open (struct inode *node, struct file *filp){
    //获取到当前自定义结构体变量,保存至file中,以便其他函数方便访问
    struct test2_cdev *n_cdev = container_of(node->i_cdev,struct test2_cdev,cv);
    filp->private_data = n_cdev;
    return 0;
}

int test2_release (struct inode *node, struct file *filp){
    filp->private_data = NULL;
    return 0;
}

ssize_t test2_read (struct file *filp, char __user *buf, size_t len, loff_t *pos){
    struct test2_cdev *n_cdev = (struct test2_cdev *)filp->private_data;
    int err;
    printk("test2 read %c  count : %d",n_cdev->val, sizeof(n_cdev->val));
    if(copy_to_user(buf,&n_cdev->val, sizeof(n_cdev->val))){
        err = -EFAULT;
        return err;
    }
    return sizeof(n_cdev->val);

}

ssize_t test2_write (struct file *filp, const char __user *buf, size_t len, loff_t *pos){
    struct test2_cdev *n_cdev = (struct test2_cdev *)filp->private_data;
    int err;
    printk("test2 write %s  count : %d",(*buf),len);
    if(copy_from_user(&n_cdev->val,buf,len)){
        err = -EFAULT;
        return err;
    }
    return sizeof(n_cdev->val);
}

struct file_operations test2_op = {
    .owner = THIS_MODULE,
    .open  = test2_open,
    .release = test2_release,
    .write = test2_write,
    .read = test2_read
};




static int test2_init(void){
    //申请设备号,自动分配 fs.h
    int err = alloc_chrdev_region(&devNo2,0,1,"test2");
    if(err){
        printk("获取设备号失败~ %d",err);
        return err;
    }
    //申请自定义结构体内存 slab.h
    test2_c = kmalloc(sizeof(struct test2_cdev),GFP_KERNEL);
    //初始化 字符设备
    cdev_init(&test2_c->cv,&test2_op);
    //添加字符设备
    err = cdev_add(&test2_c->cv,devNo2,1);
    if(err){
        printk("添加设备失败~ %d",err);
        return err;
    }

    test2_c->val = '8';
    //在/dev中创建节点
    struct class * cls = class_create(THIS_MODULE,"test2");
    device_create(cls,NULL,devNo2,NULL,"test2");

    return 0;
}

static void test2_exit(void){
    //释放设备号
    unregister_chrdev_region(devNo2,1);
    //释放自定义结构体的内存
    kfree(test2_c);
    //移除字符设备
    cdev_del(&test2_c->cv);
}


module_init(test2_init);
module_exit(test2_exit);

代码编译

在kernel/goldfish/drivers/下创建驱动文件夹,这里我创建test2,
然后编写对应的Kconfig/Makefile文件。这2个文件比较简单。
Kconfig:

config TEST2
    tristate 'TEST2 Driver'
    default n

Makefile:

obj-$(CONFIG_TEST2) += test2.o

把这2个文件都放在test2目录下。

当然仅仅这样还不够,因为你并没有让内核编译关联到我们新创建的test2驱动。

在kernel/goldfish/drivers/Makefile中添加

4FC0D48C-4556-475A-BF46-4D11F38EFB7C.png

在kernel/goldfish/arch/arm/Kconfig中添加

154A0CCE-5B28-4F5F-AE05-61FB90370921.png

然后在kernel/goldfish下执行命令make menuconfig

3.png

然后找到对应的驱动,按键按Y,启动该驱动,然后exit退出保存。

4.png

最后执行,make.

5.png

成功生成zImage文件

查看驱动

启动android虚拟机,并指定内核为当前生成的内核。

emulator -kernel kernel/goldfish/arch/arm/boot/zImage

然后执行adb shell进入android系统控制台

执行 cat /proc/devices

6.png

可以看到test2驱动已经注册了。

但是,我们还希望能在/dev下有test2驱动,那么还需要在test_init最后面再添加2行代码。

struct class *cls = class_create(THIS_MODULE,"test2");
device_create(cls,NULL,devNo2,NULL,"test2");

然后替换掉原来的test2.c文件,重新make生成zImage文件,然后再调用
emulator -kernel kernel/goldfish/arch/arm/boot/zImage 重新启动模拟器
就会发现,/dev下出现了test2驱动。

7.png

注意:如果调用emulator启动android模拟器的时候发现一直黑屏,无法启动,就说明可能是你的驱动编写出错了,导致android系统无法正常启动,你就需要认真检查下代码了。

驱动测试

既然驱动已经编写完毕,那么我们需要编写一个可执行文件,去访问一下驱动是否能正常运行,因为在我们后面的代码中,其实我们是直接在app中通过Service去访问Hal层,然后Hal层再调用底层驱动,所以我们必须保证驱动是正常的,否者到了整个过程都完成后,发现没有正常运行,会很难调试的。

#include <fcntl.h>
#include <stdio.h>
int main(void){
    char val = '1';
    int fd = open("/dev/test2",O_RDWR);
    read(fd,&val,1);
    printf("test04 read %c\n",val);
    val = val+1;
    write(fd,&val,1);
    printf("test04 write %c\n",val);
    return 0;
}

代码编译

需要编译成可执行文件,然后编译成system.img,然后启动虚拟机。
Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := test_test2.c
LOCAL_MODULE := test_test2
include $(BUILD_EXECUTABLE)

然后在external文件夹下新建test2,把Android.mk和test_test2.c这2个文件放入,然后使用mmm编译成可执行模块。

屏幕快照 2017-03-27 14.57.54.png

编译成功后,如图,会安装至out/target/product/generic/system/bin

然后使用make snod生成新的system.img,然后再启动android模拟器。

然后控制台输入adb shell进入Android控制台。然后执行我们刚才生成的test_test2可执行文件。

效果就是把驱动里面保存的char读出来,然后+1再写入,如果驱动没有错误的话,则如下运行

屏幕快照 2017-03-27 15.04.49.png

HAL层实现

test2.h

//
// Created by javalong on 17/3/25.
//



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


struct test2_module_t {
    struct hw_module_t common;
};

struct test2_device_t {
    struct hw_device_t common;
    int fd;
    int (*get_val)(struct test2_device_t *dev,int *val);
    int (*set_val)(struct test2_device_t *dev,int val);
};

int test2_dri_open(const struct hw_module_t* module, const char* id,
              struct hw_device_t** device);

#endif //ANDROIDDRIVER_TEST2_H

test2.c

//
// Created by javalong on 17/3/25.
//
#include <hardware/hardware.h>
#include <stdlib.h>
#include <fcntl.h>
#include <hardware/test2.h>
#include <cutils/log.h>

struct hw_module_methods_t test2_method = {
        open : test2_dri_open
};

struct test2_module_t HAL_MODULE_INFO_SYM = {
        common : {
                tag : HARDWARE_MODULE_TAG,
                version_major : 1,
                version_minor : 0,
                id : "test2",
                name : "test2",
                author : "javalong",
                methods : &test2_method
        }
};


int test2_get_val(struct test2_device_t *dev,int *val){
    read(dev->fd,val, sizeof(val));
    LOGI("test2_get_val  %d",val);
    return val;
}

int test2_set_val(struct test2_device_t *dev,int val){
    write(dev->fd,&val, sizeof(val));
    LOGI("test2_set_val  %d",val);
    return 0;
}

int test2_close(struct hw_device_t* device){
  //关闭文件,释放内存
    struct test2_device_t *dev = (struct test2_device_t *)device;
    close(dev->fd);
    free(dev);
    return 0;
}

int test2_dri_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device){

    struct test2_device_t *dev = malloc(sizeof(struct test2_device_t));
    memset(dev,0, sizeof(struct test2_device_t));
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = module;
    dev->common.close = test2_close;

    dev->get_val = test2_get_val;
    dev->set_val = test2_set_val;
    *device = dev;

    if((dev->fd = open("/dev/test2",O_RDWR))== -1){
        LOGI("fail open /dev/test2");
    }

    return 0;
}

将test2.h 放入hardware/libhardware/inlcude下,然后再在hardware/libhardware/modules创建test2. 创建Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := test2.c
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_MODULE := test.default
LOCAL_PRELINK_MODULE := false
LOCAL_SHARED_LIBRARIES := liblog
include $(BUILD_SHARED_LIBRARY)

然后将Android.mk和test2.c放入刚才新建的test2文件夹下。然后执行mmm ./hardware/libhardware/modules/test2/

屏幕快照 2017-03-27 15.56.48.png

这样就代表HAL层已经编译成功。

aidl实现

我们最终其实要实现的效果是实现一个SystemService,然后可以在自己的app中调用这个SystemService,而这个SystemService是跟我们的app处于不同的进程的,所以就必须要使用AIDL.

package android.os;

interface ITest2Service{
    void setVal(int val);
    int getVal();
}

这一步同样需要编译,把ITest2Service.aidl放入 framework/base/core/java/android/os

然后在framework/base下的Android.mk添加一行,让编译的时候把这个aidl也一起编译进去。

LOCAL_SRC_FILES += \
  ...
  ...
core/java/android/os/ITest2Service.aidl

也是使用 mmm,我就不过多介绍了。

mmm ./frameworks/base/

service java实现

package com.android.server;

import android.os.ITest2Service;
/**
 * Created by javalong on 17/3/25.
 */

public class Test2Service extends ITest2Service.Stub {

    private int ptr = 0;
    Test2Service(){
        ptr = init_test();
    }


    public int getVal(){
        return getVal_native(ptr);
    }

    public void setVal(int val){
        setVal_native(ptr,val);
    }

    public native int init_test();
    public native int getVal_native(int ptr);
    public native void setVal_native(int ptr,int val);

}

service jni 实现

//
// Created by javalong on 17/3/27.
//
#include <jni.h>
#include "JNIHelp.h"
#include <hardware/hardware.h>
#include <android_runtime/AndroidRuntime.h>
#include <hardware/test2.h>
#include <utils/Log.h>
namespace android{

    static jint getVal(JNIEnv *env,jobject obj,jint ptr){
        struct test2_device_t *dev = (struct test2_device_t *)ptr;
        int val = 0;
        dev->get_val(dev,&val);
        return val;
    }

   static void setVal(JNIEnv *env,jobject obj,jint ptr,jint val){
        struct test2_device_t *dev = (struct test2_device_t *)ptr;
        dev->set_val(dev,val);


    }

   static jint test_init(JNIEnv *env,jobject obj){
        struct test2_module_t *module;
        struct test2_device_t *device;

        hw_get_module("test2",(const struct hw_module_t **)&module);
        module->common.methods->open(&(module->common),"test2",(struct hw_device_t **)&device);
        return (jint)device;
    }

    static const JNINativeMethod method_table[] = {
            {"init_test","()I",(void *)test_init},
            {"getVal_native","(I)I",(void *)getVal},
            {"setVal_native","(II)V",(void *)setVal}
    };

    int register_Test2_Service(JNIEnv *env){
        jniRegisterNativeMethods(env,"com/android/server/Test2Service",method_table,NELEM(method_table));
        return 0;
    }
}

注册service和jni方法

上面的service和jni代码写好后,需要在对应的文件中注册。
Test2Service需要在framework/base/services/java/com/android/server/SystemServer.java注册

 @Override
    public void run() {
    ...
    Slog.i(TAG, "Telephony Registry");
    ServiceManager.addService("telephony.registry", new TelephonyRegistry(context));
    ServiceManager.addService("test2", new Test2Service()); 
    ...

Test2Service.cpp 需要注册在framework/base/services/jni/onload.cpp


extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
   ...
   ...
  register_Test2_Service(env);
}

还需要在当前目录的Android.mk添加Test2Service.cpp

LOCAL_SRC_FILES:= \
...
...
Test2Service.cpp \
...

编译,生成新的system.img

android app调用测试

最后一步,写一个简单的app,获取到Test2Service,然后调用其方法。

package com.example.javalong.myapplication;

package com.example.javalong.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.os.ITest2Service;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ITest2Service testService = ITest2Service.Stub.asInterface(android.os.ServiceManager.getService("test2"));
        final EditText et_set = (EditText) findViewById(R.id.et_set);
        final TextView tv_get = (TextView) findViewById(R.id.tv_test);
        findViewById(R.id.bt_getval)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            Toast.makeText(MainActivity.this, "c:"+testService.getVal(), Toast.LENGTH_SHORT).show();
                            tv_get.setText(testService.getVal()+"");
                        } catch (Throwable e) {
                            Toast.makeText(MainActivity.this, "b:"+e, Toast.LENGTH_SHORT).show();
                            e.printStackTrace();
                        }
                    }
                });

        findViewById(R.id.bt_setval).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    testService.setVal(Integer.parseInt(et_set.getText().toString()));
                    Toast.makeText(MainActivity.this, "d:"+Integer.parseInt(et_set.getText().toString()), Toast.LENGTH_SHORT).show();
                } catch (Throwable e) {
                    Toast.makeText(MainActivity.this, "e:"+e, Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                }
            }
        });


    }
}

这个app放在packages/experimental/下,然后也是使用
mmm编译
mmm ./packages/experimental/app/

然后使用make snod,生成system.img,最后启动模拟器。

最终终于把整个流程走完了,看下运行效果。

屏幕快照 2017-03-27 19.13.04.png

最终的效果应该是setVal 一个4 getVal也应该是一个4,但是当前不对,这是因为/dev/test2驱动

屏幕快照 2017-03-27 19.15.27.png

是不允许外部直接访问的,所以需要重新给该驱动设置权限。

由于篇幅太大了,我给个链接参考下
http://www.cnblogs.com/LoongEmbedded/p/5298388.html

最终重新启动模拟器就可以达到目标的效果了。


总结:

1. linux驱动实现, 并2.驱动测试  /

3. hal层实现

   主要实现struct hw_module_t  和 struct hw_device_t 两个结构体

/*定义模块ID*/  
#define XXX_HARDWARE_MODULE_ID "XXX"   
  
/*硬件模块结构体*/  
//见hardware.h中的hw_module_t定义的说明,
//xxx_module_t的第一个成员必须是hw_module_t类型,其次才是模块的一此相关信息,当然也可以不定义,  
//这里就没有定义模块相关信息   
struct xxx_module_t {  
    struct hw_module_t common;  
};  
  
/*硬件接口结构体*/  
//见hardware.h中的hw_device_t的说明,
//要求自定义xxx_device_t的第一个成员必须是hw_device_t类型,其次才是其它的一些接口信息.   
struct xxx_device_t {  
    struct hw_device_t common;  
    //以下成员是HAL对上层提供的接口或一些属性  
    int fd;  
    int (*set_val)(struct xxx_device_t* dev, int val);  
    int (*get_val)(struct xxx_device_t* dev, int* val);  
};  

例如:
#ifndef ANDROIDDRIVER_TEST2_H
#define ANDROIDDRIVER_TEST2_H
#include <hardware/hardware.h>
 
 
struct test2_module_t {
    struct hw_module_t common;
};
 
struct test2_device_t {
    struct hw_device_t common;
    int fd;
    int (*get_val)(struct test2_device_t *dev,int *val);
    int (*set_val)(struct test2_device_t *dev,int val);
};
 
int test2_dri_open(const struct hw_module_t* module, const char* id,
              struct hw_device_t** device);
 
#endif //ANDROIDDRIVER_TEST2_H

 

/*模块实例变量*/  
struct xxx_module_t HAL_MODULE_INFO_SYM = {    
//变量名必须为HAL_MODULE_INFO_SYM,这是强制要求的,你要写Android的HAL就得遵循这个游戏规则,  
//见hardware.h中的hw_module_t的类型信息说明.  它是一个宏定义:#define HAL_MODULE_INFO_SYM         HMI
        common: {  
        tag: HARDWARE_MODULE_TAG,  
        version_major: 1,  
        version_minor: 0,   
        id: XXX_HARDWARE_MODULE_ID,    //头文件中有定义  /*定义模块ID*/  
        name: MODULE_NAME,  
        author: MODULE_AUTHOR,  
        methods: &xxx_module_methods,  //模块方法列表,在本地定义  
    }  
}; 

例如:

#include <hardware/hardware.h>
#include <stdlib.h>
#include <fcntl.h>
#include <hardware/test2.h>
#include <cutils/log.h>
 
struct hw_module_methods_t test2_method = {
        open : test2_dri_open
};
 
struct test2_module_t HAL_MODULE_INFO_SYM = {
        common : {
                tag : HARDWARE_MODULE_TAG,
                version_major : 1,
                version_minor : 0,
                id : "test2",     // 根据id进行区分的
                name : "test2",
                author : "javalong",
                methods : &test2_method
        }
};
 
 
int test2_get_val(struct test2_device_t *dev,int *val){
    read(dev->fd,val, sizeof(val));
    LOGI("test2_get_val  %d",val);
    return val;
}
 
int test2_set_val(struct test2_device_t *dev,int val){
    write(dev->fd,&val, sizeof(val));
    LOGI("test2_set_val  %d",val);
    return 0;
}
 
int test2_close(struct hw_device_t* device){
  //关闭文件,释放内存
    struct test2_device_t *dev = (struct test2_device_t *)device;
    close(dev->fd);
    free(dev);
    return 0;
}
 
int test2_dri_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device){
 
    struct test2_device_t *dev = malloc(sizeof(struct test2_device_t));
    memset(dev,0, sizeof(struct test2_device_t));
    dev->common.tag = HARDWARE_DEVICE_TAG;   // 结构体赋值
    dev->common.version = 0;
    dev->common.module = module;
    dev->common.close = test2_close;  // 必须实现
 
    dev->get_val = test2_get_val;   // 操作赋值
    dev->set_val = test2_set_val;   // 操作赋值
    *device = dev;
 
    if((dev->fd = open("/dev/test2",O_RDWR))== -1){
        LOGI("fail open /dev/test2");
    }
 
    return 0;
}

4.aidl实现


package android.os;
 
interface ITest2Service{
    void setVal(int val);
    int getVal();
}
这一步同样需要编译,把ITest2Service.aidl放入 framework/base/core/java/android/os下

然后在framework/base下的Android.mk添加一行,让编译的时候把这个aidl也一起编译进去。

LOCAL_SRC_FILES += \
  ...
  ...
core/java/android/os/ITest2Service.aidl
也是使用 mmm,我就不过多介绍了。

mmm ./frameworks/base/

5.service java 实现  ??? 没有很好的明白  是不是应该看 生成的aidl文件?

package com.android.server;
 
import android.os.ITest2Service;
/**
 * Created by javalong on 17/3/25.
 */
 
public class Test2Service extends ITest2Service.Stub {   // 这个很关键 看上面aidl
 
    private int ptr = 0;
    Test2Service(){
        ptr = init_test();
    }
 
 
    public int getVal(){
        return getVal_native(ptr);
    }
 
    public void setVal(int val){
        setVal_native(ptr,val);
    }
 
    public native int init_test();   // 这个三个是Java端的代码
    public native int getVal_native(int ptr);
    public native void setVal_native(int ptr,int val);
 
}
 

6.service jni 实现   //  这个还是调用hal中的代码

//
// Created by javalong on 17/3/27.
//
#include <jni.h>
#include "JNIHelp.h"
#include <hardware/hardware.h>
#include <android_runtime/AndroidRuntime.h>
#include <hardware/test2.h>
#include <utils/Log.h>
namespace android{
 
    static jint getVal(JNIEnv *env,jobject obj,jint ptr){
        struct test2_device_t *dev = (struct test2_device_t *)ptr;
        int val = 0;
        dev->get_val(dev,&val);
        return val;
    }
 
   static void setVal(JNIEnv *env,jobject obj,jint ptr,jint val){
        struct test2_device_t *dev = (struct test2_device_t *)ptr;
        dev->set_val(dev,val); 
    }
 
   static jint test_init(JNIEnv *env,jobject obj){
        struct test2_module_t *module;
        struct test2_device_t *device;
 
        hw_get_module("test2",(const struct hw_module_t **)&module);
        module->common.methods->open(&(module->common),"test2",(struct hw_device_t **)&device);
        return (jint)device;
    }
 
    static const JNINativeMethod method_table[] = {  //这个很重要 就是 翻译 java与C 一一对应
            {"init_test","()I",(void *)test_init},
            {"getVal_native","(I)I",(void *)getVal},
            {"setVal_native","(II)V",(void *)setVal}
    };
 
    int register_Test2_Service(JNIEnv *env){
        jniRegisterNativeMethods(env,"com/android/server/Test2Service",method_table,NELEM(method_table));
        return 0;
    }
}

7.注册service 和JNI方法

Test2Service需要在framework/base/services/java/com/android/server/SystemServer.java注册

Test2Service.cpp 需要注册在framework/base/services/jni/onload.cpp

8.android app调用测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值