1. 程式人生 > >Android驅動學習-app調用內核驅動過程(驅動框架回顧)

Android驅動學習-app調用內核驅動過程(驅動框架回顧)

() tag tst 發送數據 ive cat .so ace bar

考研已經過去了,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\n",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\n",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\n",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\n",fd,arrChars);
    
    return 0;
}
void jniSerialClose( JNIEnv *env, jobject clazz, jint fd )
{
    ALOGI("jniSerialClose:%d\n",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\n",filename,flags);
    return 0;
}
int halSerialSetOpt( int fd, int nSpeed, int nBits, char nEvent, int nStop) 
{
    ALOGI("halSerialSetOpt:%d,%c\n",fd,nEvent);
    return 0;
}
char* halSerialRead( int fd, int len )
{
    ALOGI("halSerialRead:%d,%c\n",fd,len);
    return 0;
}
int halSerialWrite( int fd, char* txData ) 
{
    ALOGI("halSerialWrite:%d,%s\n",fd,txData);
    return 0;
}
void halSerialClose( int fd )
{
    ALOGI("halSerialClose:%d\n",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\n",arrChars,flags); return serial_dev->serial_open(arrChars,flags); } else { ALOGI("open:error\n"); } } else { ALOGI("hw_get_module:error\n"); } return -1; } jint jniSerialSetOpt( JNIEnv *env, jobject clazz, jint fd, jint nSpeed, jint nBits, char nEvent, jint nStop) { ALOGI("jniSerialSetOpt:%d,%c\n",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\n",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\n",fd,arrChars); return serial_dev->serial_write(fd,arrChars); } void jniSerialClose( JNIEnv *env, jobject clazz, jint fd ) { ALOGI("jniSerialClose:%d\n",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結構體,實現一個methods和id,methods中實現open函數,open函數根據傳入的名字返回設備自定義的結構體。實現一個設備自定義的結構體,並在設備自定義的結構體中加入需要的函數指針,並在.c文件中實現這些函數。

編譯:

mmm frameworks/base/services/
mmm hardware/libhardware/modules/myserial/
make snod

Android驅動學習-app調用內核驅動過程(驅動框架回顧)