基于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
代码相关说明:
- 声明模块ID,设备ID均为“led”
- 定义结构体 led_module_t 及 led_device_t 时便进行全局 typedef ,方便导包直接使用
- 设备声明三个函数指针,set_on开灯,set_off关灯,getCount灯数目
- 两个静态内联函数 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,
},
};
代码相关说明:
- 前面几个静态函数是函数指针方法的具体实现,下面在open设备时实现方法的一一映射
- led_open 方法实现设备的初始化,包括内核分配内存,led_device_t 内相关指针函数方法的映射等
- my_methods 方法为框架中结构体 hw_module_t 内 open 方法的具体映射
- HAL_MODULE_INFO_SYM 为HAL框架的必要说明,方便加载动态库时识别为驱动模块,并依据ID找到对应的模块驱动。同时完成 hw_module_t 必要方法 methods 的映射
- 为了方便调试,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();
}
代码相关说明:
- ILedService.Stub 类为aidl文件经过系统编译后生成的本地Binder类,实现跨进层通信的基础是基于安卓Binder框架,这里通过继承 ILedService.Stub 类,可以直接调用框架中实现好的Binder体系中重要的两个方法 transact(…) 和 onTransact(…)
- 对于接口中的每个方法,最终都归结实现在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));
}
};
代码相关说明:
- led_init 为设备的初始化,主要有两个重要操作:其一,调用HAL框架公用方法 hw_get_module 依据传入的模块ID,加载对应的驱动模块文件,并完成框架结构体 hw_module_t 内 methods方法的映射;其二,调用 led.h 头文件内联函数 led_device_open 打开模块设备,完成 led_device_t 结构体内相关指针函数的映射,之后,将设备引用存放于 mLedDevice 中,方便后续具体方法的调用
- 几个静态方法的具体实现,实质上是调用 mLedDevice 变量设备的方法,及驱动中对应的方法
- method_table 静态常数组,其内存放了上层native方法声明和本c层实体方法的一一映射
- 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;
}
}
}
代码相关说明:
- onCreate实现控件的获取绑定、点击监听以及状态切换
- onResume实现获取LedService的远程Proxy代理对象,方便跨进程方法调用
- 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日志如下:
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…0led_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…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…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…4led_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,再到驱动的全程调用。这里总结一下整个流程。
- 基于安卓HAL框架,实现自定义的驱动,生成相关 *.so 动态库
- 对应驱动具体方法,定义framework层AIDL文件,添加到编译文件,并生成相应的Stub类文件
- 新建 Service.java 自定义服务,继承于AIDL的中间编译文件 Service.Stub
- 将自定义服务添加到SysterServer中,便于登记注册到SM中
- 新建 com_android_server_*Service.cpp,完成对应native层代码实现,主要包括方法注册,模块的初始化和设备的打开
- 将新建c层jni文件添加到onload.cpp中,实现方法的注册和映射
- 修改对应的 Android.mk,将新建文件添加到编译系统中去
- 生成最新系统包进行刷写
- 提取AIDL文件到应用中,新建应用App通过SM获取LedService服务的远程代理对象Proxy,测试调用其方法,方法经过层层调用,最终指向驱动层实现代码
其他
相关参考
高焕堂安卓架构师系列视频
Android系统源代码情景分析