最近有Android的项目,学习下在Android上调用Linux驱动的开发流程,找到两个不错的资源。
推荐一篇文章和一个视频:
文章:Android硬件抽象层(HAL)概要介绍和学习计划
视频:Android HAL编程实战
都讲得很好,浅显易懂。
然后讲下自己的理解:
HAL层
Android 的Hal层是为了给不开放源码的厂商准备,编译Hal代码时会将其编译为.so文件使用。在Hal层中有几个主要的结构,分别是struct hw_module_t、struct hw_module_methods_t 以及struct hw_device_t,这三个结构的主要关系如下图:
这三个结构体在使用时候是以被包含的形式存在,并且包含它的结构体必须把其放在第一个成员的位置上,这样可以进行相关结构的强制转换。例如上述文章链接中Hal的代码:
/*硬件模块结构体*/
struct hello_module_t {
struct hw_module_t common;
};
/*硬件接口结构体*/
struct hello_device_t {
struct hw_device_t common;
int fd;
int (*set_val)(struct hello_device_t* dev, int val);
int (*get_val)(struct hello_device_t* dev, int* val);
};
通常,我们会在hardware/libhardware/include/hardware下定义头文件,在hardware/libhardware/modules/下创建新的文件夹用于存放源文件。
源文件中会对上述三个结构体进行实例化,并且实现相关功能函数,包括open、close、自定义功能函数等。
完成Hal的源文件代码编写后,在源文件的同目录下增加Android.mk文件,将Hal代码加入Android系统中。可以使用mmm命令对其单独编译,编译后生成.so的库文件。
得到库文件后,上层可以使用hw_get_module() 函数,获取对应的struct hw_module_t结构,hw_get_module() 函数的调用关系如下图:
HAL涉及的文件(参考上述文章):
文件名 | hello.h | hello.c | uevent.rc | android.mk | hello.default.so |
---|---|---|---|---|---|
所在目录 | hardware/libhardware/include/hardware | hardware/libhardware/modules/hello | system/core/rootdir | hardware/libhardware/modules/hello | out/target/product/generic/system/lib/hw |
作用 | 定义设备hal相关结构 | hal层访问linux内核驱动的源码 | 改变设备节点权限,让Hal可以调用 | 编译加载 | 编译完成后输出的库文件 |
JNI层
JNI(Java Native Interface),用于提供接口给Java Framework Server,代码路径为:frameworks/base/services/jni。
JNI的接口代码中使用hw_get_module() 函数获得struct hw_module_t结构,利用struct hw_module_t结构去调用open、close等相关功能函数,代码如下:
/*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/
static jboolean hello_init(JNIEnv* env, jclass clazz) {
hello_module_t* module;
LOGI("Hello JNI: initializing......");
if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
LOGI("Hello JNI: hello Stub found.");
if(hello_device_open(&(module->common), &hello_device) == 0) {
LOGI("Hello JNI: hello device is open.");
return 0;
}
LOGE("Hello JNI: failed to open hello device.");
return -1;
}
LOGE("Hello JNI: failed to get hello stub module.");
return -1;
}
完成这些接口代码后,使用jniRegisterNativeMethods() 函数进行JNI方法注册。注册代码如下:
/*JNI方法表*/
static const JNINativeMethod method_table[] = {
{"init_native", "()Z", (void*)hello_init},
{"setVal_native", "(I)V", (void*)hello_setVal},
{"getVal_native", "()I", (void*)hello_getVal},
};
/*注册JNI方法*/
int register_android_server_HelloService(JNIEnv *env) {
return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));
}
完成JNI方法注册后,需要通过修改onload.cpp 文件将JNI的源文件加载到Android系统中,并进行相关编译生成.so文件,具体可以参考上述文章链接中的JNI部分。
JNI涉及的文件:
文件名 | com_android_server_HelloService.cpp | onload.cpp | Android.mk |
---|---|---|---|
所在目录 | frameworks/base/services/jni | frameworks/base/services/jni | frameworks/base/services/jni |
作用 | jni接口源码 | 编译jni | 编译jni接口源码 |
Framework Service
Framework Service需要编写aidl文件和相关服务的java文件。
aidl(Android interface definition language),是为了实现Android进程间通信而设计的语言。aidl文件的代码内容如下:
package android.os;
interface IHelloService {
void setVal(int val);
int getVal();
}
Android系统编译时会根据aidl文件生成相应的stub接口,而java文件中定义的类会继承stub接口,我们只需在java文件中利用JNI提供的接口去实现setVal和getVal两个功能函数即可,具体代码如下:
package com.android.server;
import android.content.Context;
import android.os.IHelloService;
import android.util.Slog;
public class HelloService extends IHelloService.Stub {
private static final String TAG = "HelloService";
//构造函数
HelloService() {
init_native();
}
public void setVal(int val) {
setVal_native(val);
}
public int getVal() {
return getVal_native();
}
//JNI的接口函数声明
private static native boolean init_native();
private static native void setVal_native(int val);
private static native int getVal_native();
};
Framework Service涉及的文件
文件名 | IHelloService.aidl | Android.mk | HelloService.java | SystemServer.java |
---|---|---|---|---|
所在目录 | frameworks/base/core/java/android/os | frameworks/base | frameworks/base/services/java/com/android/server | frameworks/base/services/java/com/android/server |
作用 | 与硬件服务进程通讯,增加操作接口 | 配置需要编译的源文件 | 调用jni方法,为frameworks提供硬件服务接口 | 系统服务 |
APP
APP不会写。看资料是根据stub函数提供的接口,转换为aidl的接口,再用aidl提供的两个setVal和getVal函数进行相关操作。