zoukankan      html  css  js  c++  java
  • Android驱动学习-app调用内核驱动过程(驱动框架回顾)

    考研已经过去了,android驱动的学习也断了半年多了,现在重新捡起来学习,回顾一下Android驱动的大体框架。

    Android系统的核心是java,其有一个David虚拟机。Android-app操作硬件也相当于是java操作硬件。

    在Linux系统上操作硬件是通过open read write等来实现,也就是操作C库。如果java能直接调用C库中的函数,也就解决了app操作硬件的问题。

    下面的文章是java调用C/C++库的方法。

    链接:JAVA程序通过JNI调用C/C++库

    1.方法1——jni调用底层驱动

    在android框架中写入c/c++直接调用底层linux驱动,并向上提供jni接口给应用程序:

    优点:简单易行;

    缺点:主要在于驱动程序,由于在linux中需要遵循GPL协议,需要开源,而许多厂商的一些代码不希望开源。

    而且像屏幕等设备可能需要多个app同时操作,这样的话将会导致各种各样的问题。

    2.方法2——增加硬件抽象层

    将驱动程序一分为二,一部分开源在内核中,一部分不开源在android框架中:

    二、举例led android驱动:

    从这里我们将看到整个应用框架层到底层驱动的走向。首先,无论是哪种方法,我们都需要实现一个linux驱动以供上层访问led资源。

     同样也是通过jni来加载C库,从而通过调用open等来实现对硬件的操作。Android为了实现多个APP能操作同一个硬件,硬件不由app来直接操作,而是有SystemServer来操作。app需要把请求通过serviceManager发给SystemServer,由SystemServer最终完成对硬件的操作。

    这里我们反过来思考一个app操作硬件的过程:

    1、app需要申请服务和获得服务getservice。

    而这个服务是有接口完成的。所以第一步我们需要建立一个aidl文件,来生成接口类。

    frameworks/base/core/java/android/os/ILedService.aidl

    同时修改 frameworks/base/Android.mk, 加入新建的aidl文件。

    现在以实现一个利用app来通过串口对外发送数据的案例;

    1. 首先创建文件  frameworks/base/core/java/android/os/IMySerialService.aidl

    package android.os;
    
    /** {@hide} */
    interface IMySerialService
    {
        int serialOpen( String filename, int flags );
        int serialSetOpt( int fd,int nSpeed,int nBits,char nEvent,int nStop );
        String serialRead( int fd, int len );
        int serialWrite( int fd, String txData );
        void serialClose( int fd );
    }

      同时需要修改Android.mk文件添加我们的aidl文件

    2、自动生成ILedService.java

    mmm frameworks/base/

    编译自动生成

    out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java

    ILedService.java文件获得了,现在就需要新建与之对应的java文件,实现java下的对硬件操作的函数。

      2.运行:mmm frameworks/base/  进行编译,将会生成 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/IMySerialService.java 文件。

        下一步就是要实现 ISerialService.java 中被自动创建的接口函数了

    3、创建 LedService.java 实现接口函数

    创建完LedService.java后将其放到frameworks/base/services/core/java/com/android/server/中,其上层Android.mk会自动包含此java文件。

      3.  新建文件  frameworks/base/services/core/java/com/android/server/SerialService.java

      

    package com.android.server;
    
    import android.os.IMySerialService;
    import android.util.Slog;
    
    public class MySerialService extends IMySerialService.Stub {
        private static final String TAG = "MySerialService";
    
        public native static int nativeSerialOpen(String filename, int flags ) ;
        public native static int nativeSerialSetOpt(int fd, int nSpeed, int nBits, char nEvent, int nStop) ;
        public native static java.lang.String nativeSerialRead(int fd, int len ) ;
        public native static int nativeSerialWrite( int fd, java.lang.String txData ) ;
        public native static void nativeSerialClose(int fd );
        
    
        public int serialOpen(java.lang.String filename, int flags) throws android.os.RemoteException {
            return nativeSerialOpen(filename,flags);
        }
        public int serialSetOpt(int fd, int nSpeed, int nBits, char nEvent, int nStop) throws android.os.RemoteException {
            return nativeSerialSetOpt( fd, nSpeed, nBits, nEvent, nStop );
        }
        public java.lang.String serialRead(int fd, int len) throws android.os.RemoteException {
            return nativeSerialRead( fd, len );
        }
        public int serialWrite(int fd, java.lang.String txData) throws android.os.RemoteException {
            return nativeSerialWrite( fd, txData);
        }
        public void serialClose(int fd) throws android.os.RemoteException {
            nativeSerialClose(fd);
        }
      
    }

        public int serialOpen(java.lang.String filename, int flags) throws android.os.RemoteException 

        这些函数的定义在自动生成的IMySerialService.java的最下面,直接拷贝过来实现即可。

    4、将服务注册到Service Manager当中

    修改frameworks/base/services/java/com/android/server/SystemServer.java

    这样的话,app就能获得服务,从而实现对SystemServer的通信,从而实现对硬件的操作。现在就是需要对SystemServer进行修改了。

    SystemServer对硬件的操作也是jni,所以其也需要加载C/C++库。在Android系统中已经把所有对硬件操作的jni文件打包为一个.so库,所以我们需要做的是在so库中添加我们对硬件的支持。

      4. 将下面的代码加入到 SystemServer.java 中,这样的话就把服务加入到了ServiceManager

                Slog.i(TAG, "Serial Service");
                ServiceManager.addService("serial", new SerialService());

    5、实现com_android_server_LedService.cpp   供 LedService.java使用

    将jni文件放到frameworks/base/services/core/jni/目录中。还要注册native接口,在frameworks/base/services/core/jni/onload.cpp中修改。

    并修改Android.mk文件,加入com_android_server_LedService.cpp的编译。

    同时还要修改onload.cpp,添加 register_android_server_MySerialService(env);  和 函数声明。

    #define LOG_TAG "MySerialService"
    
    #include "jni.h"
    #include "JNIHelp.h"
    #include "android_runtime/AndroidRuntime.h"
    
    #include <utils/misc.h>
    #include <utils/Log.h>
    
    #include <stdio.h>
    
    namespace android
    {
    #if 1
    static jstring charTojstring(JNIEnv* env, const char* pat) {
        //定义java String类 strClass
        jclass strClass = (env)->FindClass("Ljava/lang/String;");
        //获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
        jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
        //建立byte数组
        jbyteArray bytes = (env)->NewByteArray(strlen(pat));
        //将char* 转换为byte数组
        (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat);
        // 设置String, 保存语言类型,用于byte数组转换至String时的参数
        jstring encoding = (env)->NewStringUTF("GB2312");
        //将byte数组转换为java String,并输出
        return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding);
    }
    
    char* jstringToChar(JNIEnv* env, jstring jstr) {
        char* rtn = NULL;
        jclass clsstring = env->FindClass("java/lang/String");
        jstring strencode = env->NewStringUTF("GB2312");
        jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
        jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
        jsize alen = env->GetArrayLength(barr);
        jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
        if (alen > 0) {
            rtn = (char*) malloc(alen + 1);
            memcpy(rtn, ba, alen);
            rtn[alen] = 0;
        }
        env->ReleaseByteArrayElements(barr, ba, 0);
        return rtn;
    }
    #endif
    
    jint jniSerialOpen( JNIEnv *env, jobject clazz, jstring filename, jint flags ) 
    {
        char *arrChars = jstringToChar(env, filename );
        ALOGI("jniSerialOpen:%s,%d
    ",arrChars,flags);
    
        return flags+1;
    }
    jint jniSerialSetOpt( JNIEnv *env, jobject clazz, jint fd, jint nSpeed, jint nBits, char nEvent, jint nStop) 
    {
        ALOGI("jniSerialSetOpt:%d,%c
    ",fd,nEvent);
        return fd+1;
    }
    jstring jniSerialRead( JNIEnv *env, jobject clazz, jint fd, jint len )
    {
        char arrTx[] = "This is a chars";
        ALOGI("jniSerialRead:%d,%c
    ",fd,len);
        /* CStr2Jstring */
    
        return charTojstring(env, arrTx);
    }
    jint jniSerialWrite( JNIEnv *env, jobject clazz, jint fd, jstring txData ) 
    {
        char *arrChars = jstringToChar(env, txData );
        ALOGI("jniSerialWrite:%d,%s
    ",fd,arrChars);
        
        return 0;
    }
    void jniSerialClose( JNIEnv *env, jobject clazz, jint fd )
    {
        ALOGI("jniSerialClose:%d
    ",fd);
    }
    
    static JNINativeMethod method_table[] = {
        { "nativeSerialOpen", "(Ljava/lang/String;I)I", (void*)jniSerialOpen },
        { "nativeSerialSetOpt", "(IIICI)I", (void*)jniSerialSetOpt },
        { "nativeSerialRead", "(II)Ljava/lang/String;", (void*)jniSerialRead },
        { "nativeSerialWrite", "(ILjava/lang/String;)I", (void*)jniSerialWrite },
        { "nativeSerialClose", "(I)V", (void*)jniSerialClose },
    
         
    };
    
    int register_android_server_MySerialService(JNIEnv *env)
    {
        return jniRegisterNativeMethods(env, "com/android/server/MySerialService",
                method_table, NELEM(method_table));
    }
    
    };

    这里其实已经差不多实现了对硬件的操作,因为我们实现了对cpp文件的调用并打包进系统,cpp文件就能直接加载C库,调用open等函数实现对硬件的操作。

    但是这样的话有一个问题,就是如果驱动有点儿问题,我们就需要修改com_android_server_LedService.cpp,并重新编译系统、烧写系统。这样的话不是很方便。我们可以再引入硬件抽象层(HAL),这样更有利于搭建整个系统。

    注意:编译的时候使用:

    mmm frameworks/base/services/
    mmm frameworks/base/
    make snod

    只执行  mmm frameworks/base/  将不会 frameworks/base/services/ 下修改的文件进行编译。 这个要注意。

     JNI 向上提供本地函数,向下加载HAL文件并调用HAL的函数

    HAL负责访问驱动程序执行硬件操作(dlopen)。

    这里编译系统,运行app  即可打印出信息(logcat | grep "MySerialSerice")

    APP代码: 需要添加jar包。在 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar

    package com.lmissw.serialdevicetest;
    
    import android.os.RemoteException;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.os.IMySerialService;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    
    import static android.os.ServiceManager.getService;
    
    public class MainActivity extends AppCompatActivity {
        Button iButton=null;
        private IMySerialService iSerialService = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            iButton = findViewById(R.id.button);
            iButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    iSerialService = IMySerialService.Stub.asInterface(getService("myserial"));
                    try {
                        int fd = iSerialService.serialOpen("/dev/s3c2410_serial2",0);
                        iSerialService.serialSetOpt(2,3,4,'t',6);
                        Log.i("serialTest","App serialSetOpt"+fd);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            });
    
        }
    }

     6、实现Hal层程序

    JNI 向上提供本地函数,向下加载HAL文件并调用HAL的函数。

    HAL负责访问驱动程序并执行硬件操作。而HAL层代码被编程一个.so动态库。

    所以JNI加载HAL就是加载动态库,而在linux系统中加载动态库使用的是dlopen。

    在Android系统中,不直接使用dlopen,是因为Android对dlopen做了一层封装,使用的是hw_get_module("模块名");

    而hw_get_module最终还是调用dlopen,那么就需用从模块名拿到文件名了。

      模块名 >> 文件名

        在模块名转化为名时,使用的是hw_module_by_class(模块名,NULL),在其中将name=“模块名”;

          然后使用property_get 获得属性值(价值对),先查询ro.hardware.模块名、ro.hardware、ro.product.board、ro.board.platform、ro.arch。上面的每个属性都对应一个subname。

          然后使用hw_module_exists 在 HAL_LIBRARY_PATH 环境变量、/vendor/lib/hw、/system/lib/hw 三个文件夹中 判断 "name"."subname".so 文件是否存在。如果都没有则查找 "name".default.so。

          如果上述的模块存在,则调用load 加载,在load中使用了dlopen。再使用dlsym("HMI")从so获得名为hw_module_t结构体。然后判断结构体中的名字和name是否一致,一致则表明找到了模块。

    JNI怎么使用HAL

      使用hw_get_module获得一个hw_module_t结构体。调用module中的open函数来获得一个hw_device_t结构体。并且把hw_device_t结构体转换为设备自定义的结构体。自定义的结构体第一个成员是 hw_device_t结构体。

    在 hardware/libhardware/include/hardware/ 下创建hal层的头文件,在hardware/libhardware/modules/ 目录下创建一个新的目录用来 存放hal的C文件,并创建一个Android.mk文件。

    编译的话用 mmm  hardware/libhardware/modules/+创建的目录名。

    /* filename: Android.mk */
    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := myserial.default
    
    LOCAL_MODULE_RELATIVE_PATH := hw
    LOCAL_C_INCLUDES := hardware/libhardware
    LOCAL_SRC_FILES := myserial.c
    LOCAL_SHARED_LIBRARIES := liblog
    LOCAL_MODULE_TAGS := eng
    
    include $(BUILD_SHARED_LIBRARY)

    myserial.c

    #define LOG_TAG "MySerialHal"
    
    #include <hardware/myserial.h>
    #include <hardware/hardware.h>
    #include <cutils/log.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    
    int halSerialOpen( char* filename, int flags ) 
    {
        ALOGI("halSerialOpen:%s,%d
    ",filename,flags);
        return 0;
    }
    int halSerialSetOpt( int fd, int nSpeed, int nBits, char nEvent, int nStop) 
    {
        ALOGI("halSerialSetOpt:%d,%c
    ",fd,nEvent);
        return 0;
    }
    char* halSerialRead( int fd, int len )
    {
        ALOGI("halSerialRead:%d,%c
    ",fd,len);
        return 0;
    }
    int halSerialWrite( int fd, char* txData ) 
    {
        ALOGI("halSerialWrite:%d,%s
    ",fd,txData);
        return 0;
    }
    void halSerialClose( int fd )
    {
        ALOGI("halSerialClose:%d
    ",fd);
    }
    
    struct myserial_device_t myserial_device = {
        .common = {
            .tag = HARDWARE_DEVICE_TAG,
        },
        .serial_open = halSerialOpen,
        .serial_setOpt = halSerialSetOpt,
        .serial_read = halSerialRead,
        .serial_write = halSerialWrite,
        .serial_close = halSerialClose,
    };
    
    int serial_open(const struct hw_module_t* module, const char* id,
                struct hw_device_t** device) {
        *device = &myserial_device;
         return 0;
     }
    
    static struct hw_module_methods_t myserial_module_methods = {
        .open = serial_open,
    };
    
    struct hw_module_t HAL_MODULE_INFO_SYM = {
        .tag = HARDWARE_MODULE_TAG,    
        .name = "myserial Device HAL",   
        .id = "myserial",        //必填
        .methods = &myserial_module_methods, //必填
    };

    myserial.h

    #ifndef _HARDWARE_MYSERIAL_H
    #define _HARDWARE_MYSERIAL_H
    
    #include <hardware/hardware.h>
    
    __BEGIN_DECLS
    
    struct myserial_device_t {
        struct hw_device_t common;
        int (*serial_open)( char* filename, int flags ) ;
        int (*serial_setOpt)( int fd, int nSpeed, int nBits, char nEvent, int nStop) ;
        char* (*serial_read)( int fd, int len );
        int (*serial_write)( int fd, char* txData ) ;
        void (*serial_close)( int fd );
    } ;
    
    __END_DECLS
    
    #endif  // _HARDWARE_MYSERIAL_H
    /* filename com_android_server_MySerialService.cpp */
    #define
    LOG_TAG "MySerialService" #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include <utils/misc.h> #include <utils/Log.h> #include <hardware/myserial.h> #include <stdio.h> namespace android { struct hw_module_t *module; struct hw_device_t* device; struct myserial_device_t* serial_dev; #if 1 static jstring charTojstring(JNIEnv* env, const char* pat) { //定义java String类 strClass jclass strClass = (env)->FindClass("Ljava/lang/String;"); //获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V"); //建立byte数组 jbyteArray bytes = (env)->NewByteArray(strlen(pat)); //将char* 转换为byte数组 (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat); // 设置String, 保存语言类型,用于byte数组转换至String时的参数 jstring encoding = (env)->NewStringUTF("GB2312"); //将byte数组转换为java String,并输出 return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding); } static char* jstringToChar(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("GB2312"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; } #endif jint jniSerialOpen( JNIEnv *env, jobject clazz, jstring filename, jint flags ) { jint err; char *arrChars = jstringToChar(env, filename ); err = hw_get_module("myserial",(hw_module_t const**)&module); if (err==0) { err = module->methods->open( module, NULL, &device ); if (err==0) { serial_dev = (struct myserial_device_t*) device; ALOGI("jniSerialOpen:%s,%d ",arrChars,flags); return serial_dev->serial_open(arrChars,flags); } else { ALOGI("open:error "); } } else { ALOGI("hw_get_module:error "); } return -1; } jint jniSerialSetOpt( JNIEnv *env, jobject clazz, jint fd, jint nSpeed, jint nBits, char nEvent, jint nStop) { ALOGI("jniSerialSetOpt:%d,%c ",fd,nEvent); return serial_dev->serial_setOpt(fd,nSpeed,nBits,nEvent,nStop); } jstring jniSerialRead( JNIEnv *env, jobject clazz, jint fd, jint len ) { char* arrTx= serial_dev->serial_read(fd,len); ALOGI("jniSerialRead:%d,%c ",fd,len); return charTojstring(env, arrTx); } jint jniSerialWrite( JNIEnv *env, jobject clazz, jint fd, jstring txData ) { char *arrChars = jstringToChar(env, txData ); ALOGI("jniSerialWrite:%d,%s ",fd,arrChars); return serial_dev->serial_write(fd,arrChars); } void jniSerialClose( JNIEnv *env, jobject clazz, jint fd ) { ALOGI("jniSerialClose:%d ",fd); serial_dev->serial_close(fd);; } static JNINativeMethod method_table[] = { { "nativeSerialOpen", "(Ljava/lang/String;I)I", (void*)jniSerialOpen }, { "nativeSerialSetOpt", "(IIICI)I", (void*)jniSerialSetOpt }, { "nativeSerialRead", "(II)Ljava/lang/String;", (void*)jniSerialRead }, { "nativeSerialWrite", "(ILjava/lang/String;)I", (void*)jniSerialWrite }, { "nativeSerialClose", "(I)V", (void*)jniSerialClose }, }; int register_android_server_MySerialService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/MySerialService", method_table, NELEM(method_table)); } };

    首先 实现一个 hw_module_t结构体,实现一个methodsidmethods中实现open函数,open函数根据传入的名字返回设备自定义的结构体。实现一个设备自定义的结构体,并在设备自定义的结构体中加入需要的函数指针,并在.c文件中实现这些函数。

    编译:

    mmm frameworks/base/services/
    mmm  hardware/libhardware/modules/myserial/
    make snod
  • 相关阅读:
    点赞功能实现 $(tag).css('属性', '样式')
    ul li的添加 删除 复制 clone()
    Java 面向对象之构造函数和 this 关键字
    Java 面向对象编程介绍
    Java 语言基础之数组应用
    Java 语言基础之数组常见操作
    Java 语言基础之数组(一)
    Java 语言基础之函数
    Java 语言基础之语句
    Java 语言基础之运算符
  • 原文地址:https://www.cnblogs.com/ynxf/p/8214773.html
Copyright © 2011-2022 走看看