Android系统HAL层驱动开发实战

基于Android HAL层驱动框架,开发者很容易将自己的驱动实现在用户空间,实现驱动细节的隐藏,而不必在内核中进行修改,避免因修改内核,公开源码进而损害自己的商业利益。本章节,我们就对HAL层驱动框架进行实现,由底层驱动到中间层framework,继而进入上层应用端,彻底完成上层应用到底层驱动的一一实现。

HAL层驱动实现

HAL层驱动框架的介绍在上篇章中已经详细的说明了。本节就在上节篇章中进行补充和说明,在框架基础上,代码实现自定义的驱动程序。本篇章中实现的驱动原型为led灯,包括设备初始化,打开,关闭以及设备移除等。

HAL头文件

HAL驱动的头文件声明,可供实体文件导入,在JNI实现代码中同样需要导入,具体代码如下:

路径:hardware/libhardware/include/hardware/led.h

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H

#include <hardware/hardware.h>

__BEGIN_DECLS

#define LED_HARDWARE_MODULE_ID "led"

#define LED_HARDWARE_DEVICE_ID "led"

typedef struct led_module_t {
    struct hw_module_t common;
} led_module_t;

typedef struct led_device_t {
    struct hw_device_t common;
    int (*set_on)(struct led_device_t* dev);
    int (*set_off)(struct led_device_t* dev);
    int (*getCount)(struct led_device_t* dev);
} led_device_t;

static inline int led_device_open(const struct hw_module_t* module,
    led_device_t** dev){
    return module->methods->open(module,LED_HARDWARE_DEVICE_ID,
        (struct hw_device_t**) dev);
}

static inline int led_device_close(led_device_t* dev){
    return dev->common.close(&dev->common);
}

__END_DECLS

#endif

代码相关说明:

  1. 声明模块ID,设备ID均为“led”
  2. 定义结构体 led_module_t 及 led_device_t 时便进行全局 typedef ,方便导包直接使用
  3. 设备声明三个函数指针,set_on开灯,set_off关灯,getCount灯数目
  4. 两个静态内联函数 led_device_open打开模块设备,led_device_close关闭模块设备

注意:文件路径的include同级目录下,应该有hardware.h 文件,此文件源码框架本身已经提供,不必自己实现

HAL实体文件

本体文件代码如下:

代码路径:hardware/libhardware/modules/led/led.c

#define LOG_TAG "LedHALStub"

#include <hardware/hardware.h>
#include <hardware/led.h>
#include <cutils/log.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>
#include <memory.h>

#define MODULE_NAME "Led"
#define MODULE_AUTHOR "891904833@qq.com"

static int led_set_on(led_device_t* dev) {
    if(!dev) {
        ALOGI("Null dev pointer.");
        return -EFAULT;
    }
    ALOGI("led_set_on function call!");
    return 0;
}

static int led_set_off(led_device_t* dev) {
    if(!dev) {
        ALOGI("Null dev pointer.");
        return -EFAULT;
    }
    ALOGI("led_set_off function call!");
    return 0;
}

static int led_getCount(led_device_t* device) {
    ALOGI("led_getCount function call,return led_getCount is 4!");
    return 4;
}

static int led_close(hw_device_t* device) {
    if(device){
        free(device);
    }
    ALOGI("led_device close successfully!");
    return 0;
}

static int led_open(const hw_module_t* module, const char* id,
    hw_device_t** device) {

        // dev = (struct led_device_t*)malloc(sizeof(struct led_device_t));
        led_device_t *dev = (led_device_t *)calloc(1, sizeof(led_device_t));

        if(!dev) {
            ALOGE("Failed to alloc space for led_device_t.");
            return -EFAULT;
        }

        memset(dev, 0, sizeof(struct led_device_t));

        dev->common.tag = HARDWARE_DEVICE_TAG;
        dev->common.version = 0;
        dev->common.module = (struct hw_module_t*)module;
        dev->common.close = led_close;

        dev->set_on = led_set_on;
        dev->set_off = led_set_off;
        dev->getCount = led_getCount;

        *device = &dev->common;

        ALOGI("Open led_device successfully!");

        return 0;
}

static struct hw_module_methods_t my_methods = {
    .open = led_open,
};

struct led_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .version_major = 1,
        .version_minor = 0,
        .id = LED_HARDWARE_MODULE_ID,
        .name = MODULE_NAME,
        .author = MODULE_AUTHOR,
        .methods = &my_methods,
    },
};

代码相关说明:

  1. 前面几个静态函数是函数指针方法的具体实现,下面在open设备时实现方法的一一映射
  2. led_open 方法实现设备的初始化,包括内核分配内存,led_device_t 内相关指针函数方法的映射等
  3. my_methods 方法为框架中结构体 hw_module_t 内 open 方法的具体映射
  4. HAL_MODULE_INFO_SYM 为HAL框架的必要说明,方便加载动态库时识别为驱动模块,并依据ID找到对应的模块驱动。同时完成 hw_module_t 必要方法 methods 的映射
  5. 为了方便调试,HAL驱动的具体实现中仅进行Log打印,没有协助内核进行相关操作,之后在上层调用中如果查看到 “LedHALStub” 日志,即代表驱动调用ok

编译脚本

编译文件 Android.mk 将源代码编译成动态链接库,存放在设备的 /system/lib/hw/led.default.so 和 /system/lib64/hw/led.default.so,代码如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_SHARED_LIBRARIES := \
    liblog \
    libutils
LOCAL_SRC_FILES := led.cpp
LOCAL_MODULE := led.default
include $(BUILD_SHARED_LIBRARY)

framework层实现

HAL层驱动框架实现完成,下面就进行framework层的实现。大概调用流程如下:首先系统提供一个全局的LedService服务,系统设备启动中将其注册到ServiceManager中,其后上层应用通过跨进程aidl方式,远程调用服务内方法,最后在服务内通过jni方式再调用到底层驱动程序,实现上层到中间层继而底层驱动的全程跨度。

AIDL声明

AIDL的详细说明在之前的篇章中已经详细介绍过了,下面直接看服务的接口声明:

代码路径:frameworks/base/core/java/android/os/ILedService.aidl

package android.os;

/** {@hide} */
interface ILedService
{
    int LedInit();
    void LedSetOn();
    void LedSetOff();
    void LedClose();
    int LedGetCount();
}

代码说明:
声明五个接口,对应设备的初始化,打开,关闭,灯数目以及设备移除。

编译AIDL文件

声明完成AIDL文件,修改framework/base下的Android.mk文件,将ILedService.aidl加入到编译脚本aidl文件中去。

LOCAL_SRC_FILES += \
    core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \
    ...
    core/java/android/os/ILedService.aidl \

  ...

之后执行 mmm framework/base 局部编译,生成aidl对应的java文件,文件位于 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java

自定义服务实现

有了AIDL文件的接口方法,下面就进行自定义服务的实现。首先找到同级文件夹 frameworks/base/services/core/java/com/android/server/ 下,自定义 LedService 继承AIDL文件中间类 ILedService.Stub ,具体代码如下:

package com.android.server;

import android.os.ILedService;
import android.util.Slog;

public class LedService extends ILedService.Stub {
    private static final String TAG = "LedService";

    public int LedInit() throws android.os.RemoteException
    {
        return native_LedInit();
    }

    public void LedSetOn() throws android.os.RemoteException
    {
        Slog.d(TAG,"LedService native_LedSetOn");
        native_LedSetOn();
    }

    public void LedSetOff() throws android.os.RemoteException
    {
        Slog.d(TAG,"LedService native_LedSetOff");
        native_LedSetOff();
    }

    public int LedGetCount() throws android.os.RemoteException
    {
        Slog.d(TAG,"LedService native_LedGetCount");
        return native_LedGetCount();
    }

    public void LedClose() throws android.os.RemoteException{
        Slog.d(TAG,"LedService native_LedClose");
        native_LedClose();
    }

    public LedService()
    {
        Slog.d(TAG,"LedService started!");
    }

    native int native_LedInit();
    native void native_LedSetOn();
    native void native_LedSetOff();
    native int native_LedGetCount();
    native void native_LedClose();
}

代码相关说明:

  1. ILedService.Stub 类为aidl文件经过系统编译后生成的本地Binder类,实现跨进层通信的基础是基于安卓Binder框架,这里通过继承 ILedService.Stub 类,可以直接调用框架中实现好的Binder体系中重要的两个方法 transact(…) 和 onTransact(…)
  2. 对于接口中的每个方法,最终都归结实现在native层jni方法中

加入系统服务SM

完成自定义服务的编写,下面就需要将其加入到SM中,方便后续上层应用进行调用。由于系统开机会在SystemServer中进行相关服务的初始化登记动作,这里将LedService加入到SystemServer中去即可,具体代码如下:

代码路径:frameworks/base/services/core/java/com/android/server/SystemServer.java

...

Slog.i(TAG, "Vibrator Service");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);

// new code by allies
LedService led = null;
Slog.i(TAG, "Led Service");
led = new LedService();
ServiceManager.addService("led", led);

...

这里找到 VibratorService 震动服务,将 LedService 加入到此段代码下面即可,其他代码这里省略。

JNI方法实现

LedService中的native方法声明完成,下面就要对此jni方法进行实现。这里依据其他服务jni方法声明文件方式,同级目录下新建文档 com_android_server_LedService.cpp ,具体代码如下:

代码路径:frameworks/base/services/core/jni/com_android_server_LedService.cpp

#define LOG_TAG "LedServiceJNI"
#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/led.h>
#include <stdio.h>

namespace android{

    static led_device_t* mLedDevice = 0;

    static jint led_init(JNIEnv *env, jobject instance){
        led_module_t* module;
        ALOGI("led_init start ...");
        if (hw_get_module(LED_HARDWARE_MODULE_ID, (hw_module_t const**)&module) == 0) {  
            ALOGI("Device led found...");
            if (led_device_open(&(module->common), &mLedDevice ) == 0) {
                ALOGI("led_init success!!!");
                return 0;
            }
            ALOGE("led_init error!!!");
        }
        ALOGI("hw_get_module function call error!!!");
        return -1;
    }

    static jint led_getCount(JNIEnv *env, jobject thiz) {
        ALOGI("led_getCount 4");
        return mLedDevice->getCount(mLedDevice);;
    }

    static void led_setOn(JNIEnv *env, jobject thiz) {
        ALOGI("led_set_on");
        mLedDevice->set_on(mLedDevice);
    }

    static void led_setOff(JNIEnv *env, jobject thiz) {
        ALOGI("led_set_off");
        mLedDevice->set_off(mLedDevice);
    }

    static void led_close(JNIEnv *env, jobject thiz) {
        ALOGI("led_close");
        led_device_close(mLedDevice);
    }

    static const JNINativeMethod method_table[] = {
        {"native_LedInit", "()I", (void*)led_init},
        {"native_LedSetOn", "()V", (void*)led_setOn},
        {"native_LedSetOff", "()V", (void*)led_setOff},
        {"native_LedGetCount", "()I", (void*)led_getCount},
        {"native_LedClose", "()V", (void*)led_close},
    };

    int register_android_server_LedService(JNIEnv *env) {
            return jniRegisterNativeMethods(env, "com/android/server/LedService", method_table, NELEM(method_table));
    }
};

代码相关说明:

  1. led_init 为设备的初始化,主要有两个重要操作:其一,调用HAL框架公用方法 hw_get_module 依据传入的模块ID,加载对应的驱动模块文件,并完成框架结构体 hw_module_t 内 methods方法的映射;其二,调用 led.h 头文件内联函数 led_device_open 打开模块设备,完成 led_device_t 结构体内相关指针函数的映射,之后,将设备引用存放于 mLedDevice 中,方便后续具体方法的调用
  2. 几个静态方法的具体实现,实质上是调用 mLedDevice 变量设备的方法,及驱动中对应的方法
  3. method_table 静态常数组,其内存放了上层native方法声明和本c层实体方法的一一映射
  4. register_android_server_LedService jni方法的注册具体实现,此方法需要在系统启动时,在onload.cpp中指定

自定义方法JNI注册实现

修改同级目录下 frameworks/base/services/core/jni/onload.cpp 文件,将 com_android_server_LedService.cpp 文件注册到系统中去。

代码路径:frameworks/base/services/core/jni/onload.cpp

#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"

namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
...
int register_android_server_LedService(JNIEnv* env);
};

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");

    register_android_server_PowerManagerService(env);
    register_android_server_SerialService(env);
    ...
    register_android_server_LedService(env);

    return JNI_VERSION_1_4;
}

在对应的地方,依照其他代码方式,添加自己声明的jni方法注册实现即可。

编译脚本修改

到这里已经修改完成了framework层相关文件的编写,之后我们就需要修改此层文件中的编译脚本,将添加的文件加入编译系统中去。

代码路径:frameworks/base/services/core/jni/Android.mk

LOCAL_REL_DIR := core/jni

LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter

LOCAL_SRC_FILES += \
    $(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
    $(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \
    ...
    $(LOCAL_REL_DIR)/com_android_server_LedService.cpp \
    $(LOCAL_REL_DIR)/onload.cpp \

    ...

源码跟路径下,执行 mmm frameworks/base/services/ 命令,编译生成相关文件之后,make snod 将编译变动文件打包到system.img镜像中,升级系统。

应用层实现

到这里,HAL层驱动,framework层框架都以搭建完成,系统刷写新的rom后,下面我们就要对此进行上层应用的调用。

导入AIDL文件

由于需要跨进程进行系统服务的调用,这里需要将之前声明的AIDL文件拷贝到Android Studio中,锤子一下,生成相应的Stub文件,方便后续服务的获取。注意AIDL文件的包名要和系统中的相一致。

项目源码示例

对于Android Studio项目的全程源码,在这里就不贴了。主要的逻辑还是要看一下。如下:

package allies.showame.com.jni_hal_demo;

import android.os.Bundle;
import android.os.ILedService;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private static final String TAG = "Led_MainActivity";
private View btn_setOff;
private View btn_setOn;
private View btn_init;
private View btn_getCount;
private View btn_close;
static boolean ifInited = false;
private ILedService ledProxy = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initViews();
    setOnClick();
    enableBtn();
}

@Override
protected void onResume() {
    ledProxy = ILedService.Stub.asInterface(ServiceManager.getService("led"));
    if (ledProxy == null)
        Log.e(TAG, "led service is null!!!");
    else
        Log.e(TAG, "led service stub get success!!!");
    super.onResume();
}

private void enableBtn() {
    if (ifInited) {
        btn_setOff.setEnabled(true);
        btn_setOn.setEnabled(true);
        btn_getCount.setEnabled(true);
        btn_close.setEnabled(true);
    } else {
        btn_setOff.setEnabled(false);
        btn_setOn.setEnabled(false);
        btn_getCount.setEnabled(false);
        btn_close.setEnabled(false);
    }
}

private void initViews() {
    btn_init = findViewById(R.id.btn_init);
    btn_setOn = findViewById(R.id.btn_setOn);
    btn_setOff = findViewById(R.id.btn_setOff);
    btn_getCount = findViewById(R.id.btn_getCount);
    btn_close = findViewById(R.id.btn_close);
}

private void setOnClick() {

    btn_init.setOnClickListener(this);
    btn_setOn.setOnClickListener(this);
    btn_setOff.setOnClickListener(this);
    btn_getCount.setOnClickListener(this);
    btn_close.setOnClickListener(this);

}

@Override
public void onClick(View v) {

    int id = v.getId();
    int i = -1;
    switch (id) {
        case R.id.btn_init:
            try {
                i = ledProxy.LedInit();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "led init..." + String.valueOf(i));
            if (!ifInited) {
                ifInited = true;
                enableBtn();
            }
            break;

        case R.id.btn_setOn:
            try {
                ledProxy.LedSetOn();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "led setOn...");

            break;
        case R.id.btn_setOff:
            try {
                ledProxy.LedSetOff();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "led setOff...");

            break;

        case R.id.btn_getCount:
            try {
                i = ledProxy.LedGetCount();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "led getCount..." + String.valueOf(i));
            break;
        case R.id.btn_close:
            try {
                ledProxy.LedClose();

            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "led close...");
            if (ifInited) {
                ifInited = false;
                enableBtn();
            }
        default:
            break;
    }
}

}

代码相关说明:

  1. onCreate实现控件的获取绑定、点击监听以及状态切换
  2. onResume实现获取LedService的远程Proxy代理对象,方便跨进程方法调用
  3. onClick实现相关控件的点击实现,即调用Proxy代理类相关方法,其中还有控件状态使能控制

SM获取系统服务的说明

对于方法 ServiceManager.getService(“led”),此方法为系统api,直接调用在Android Studio无法实现,这里提供两个方法实现。

  • 方法一:本项目实现方法,导入系统编译中间framework.jar实现系统api无缝调用
  • 方法二:使用反射方式进行调用,具体方式如下:

    public ILedService getILedServiceProxy() {
        ILedService LedService = null;
        try {
            // 根据指定文件,反射获取类 ServiceManager
            Class<?> aClass = Class.forName("android.os.ServiceManager");
            // 获取类中定义的具体方法 getService
            Method getService = aClass.getDeclaredMethod("getService", String.class);
            // 反射方法到类,即 method + invoke + 类
            Object ledIBinder = getService.invoke(aClass, "led");
            // 将 IBinder 对象转成远程代理 Proxy
            LedService = ILedService.Stub.asInterface((IBinder) ledIBinder);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return LedService;
    }
    

安装调试

将之前打包好的rom刷写到设备中,重启开机后,Android Studio中终端adb连接到设备中去,开启软件进行调试。

Log日志

终端输入 logcat | grep “Led”,查看最终的log日志如下:

  1. led_init

    3673 I LedServiceJNI: led_init start …
    3673 I LedServiceJNI: Device led found…
    3673 I LedHALStub: Open led_device successfully!
    3673 I LedServiceJNI: led_init success!!!
    3659 D Led_MainActivity: led init…0

  2. led_setOn

    2340 D LedService: LedService native_LedSetOn
    2340 I LedServiceJNI: led_set_on
    2340 I LedHALStub: led_set_on function call!
    3659 D Led_MainActivity: led setOn…

  3. led_setOff

    2384 D LedService: LedService native_LedSetOff
    2384 I LedServiceJNI: led_set_off
    2384 I LedHALStub: led_set_off function call!
    3659 D Led_MainActivity: led setOff…

  4. led_getCount

    3673 D LedService: LedService native_LedGetCount
    3673 I LedServiceJNI: led_getCount 4
    3673 I LedHALStub: led_getCount function call,return led_getCount is 4!
    3659 D Led_MainActivity: led getCount…4

  5. led_close

    2391 D LedService: LedService native_LedClose
    2391 I LedServiceJNI: led_close
    2391 I LedHALStub: led_device close successfully!
    3659 D Led_MainActivity: led close…

从log日志可以看出,从上层应用的调用到驱动,经过的流程 Led_MainActivity -> LedServic -> nativce_jni>HAL -> Led_MainActivity ,一次调用过程,完整的透过了安卓的整个框架。至此从安卓HAL层驱动到上层应用的实现就完整结束了。

总结

经过上面的代码实战,透过安卓的硬件抽象层框架,完整的实现了从上层应用到中间层framework,再到驱动的全程调用。这里总结一下整个流程。

  1. 基于安卓HAL框架,实现自定义的驱动,生成相关 *.so 动态库
  2. 对应驱动具体方法,定义framework层AIDL文件,添加到编译文件,并生成相应的Stub类文件
  3. 新建 Service.java 自定义服务,继承于AIDL的中间编译文件 Service.Stub
  4. 将自定义服务添加到SysterServer中,便于登记注册到SM中
  5. 新建 com_android_server_*Service.cpp,完成对应native层代码实现,主要包括方法注册,模块的初始化和设备的打开
  6. 将新建c层jni文件添加到onload.cpp中,实现方法的注册和映射
  7. 修改对应的 Android.mk,将新建文件添加到编译系统中去
  8. 生成最新系统包进行刷写
  9. 提取AIDL文件到应用中,新建应用App通过SM获取LedService服务的远程代理对象Proxy,测试调用其方法,方法经过层层调用,最终指向驱动层实现代码

其他

相关参考

高焕堂安卓架构师系列视频

Android系统源代码情景分析

坚持原创技术分享,您的支持将鼓励我继续创作!