0.Android架构图
一上来就是一张框架图,发明了框架图的天才真是个天才!
简单点评一下:
1.对于安卓应用开发来说:App层以下都是操作系统,反正我只关心Android Studio(其实是SDK)给我什么包,包里有哪些API,直接调库!
2.对于linux驱动开发者来说:设备节点以上都是应用层,我只要把xxx_open,xxx_write,xxx_read,xxx_ioctrl这些fops做好,给应用层设备节点就算完事!
3.对于安卓驱动开发来说:苦逼,不仅!要给app层写服务层接口、理解服务层蕴含的C/S模型,还要用JNI映射表来映射java接口和C语言接口,还要在hal层对设备节点的open/write/read/ioctrl封装!来写保密、复杂设备的驱动也就是camera、wifi等驱动!java、c++、c语言都要会你敢信??而且还有一堆编译脚本要处理,而且编译android系统还贼慢,而且!电脑内存根本不够用!everyone,全体起立给安卓驱动开发工程师致敬!
简单解释一下上面的框图:
1.APP层:没什么好说的,调库画界面,和我们嵌入式关系不大;
2.Framework&&Lib&&Runtime:这个是功能框图,大概点出了层次以及作用,中文名就是应用程序框架层(先简单理解,就是提供服务)、系统运行时库层(两种虚拟机,简单理解就是让java程序能跑在C/C++构建的环境上)、C库层(库更好理解嘛,一堆别人写好的东西等你去调!);但是对于我们具体要动手编程的还是有点太抽象了,现在我可以明确的讲,按 框架层(系统服务层+具体硬件服务层)---->>JNI层---->>HAL层 来划分“安卓应用--linux驱动"之间的这些东西,后面会比较清晰~暂且叫这坨东西叫安卓驱动好了。
3.linux kernel:这个就不用讲了吧,linux驱动呐,描述硬件信息的设备树,标准化的字符设备驱动、spi/i2c/input/v4l2等等各种驱动框架,最终就是要暴露出设备节点给到应用层,应用层(相对于Linux驱动来说安卓驱动也是应用层)都是操作设备节点来操作硬件,在安卓驱动开发暂且就不用关心linux驱动了,只需要知道open/write/read/ioctrl这些系统调用就够了。
1.以ledctrl为例子,介绍实际代码的层次结构
1.0 层次概况:
层次 | 概述 |
APP层 | 在Android Studio进行开发的人,调Java包的API,就是调API |
框架层 | Java API的提供者,它提供了被称为“服务”的东西, 就是应用层你调某个API,我帮你做操作并给你返回值 |
思考 | 这里需要思考一个问题,例如硬件只有一个,那如果多个应用程序都要控制它,怎么办?这就要提到“服务”这个东西,就是安卓系统做主,它去建立一个进程访问硬件(上服务),然后应用层的进程要控制硬件,都给系统服务(下服务)发请求,“系统服务”会建立队列让它们排队使用这个硬件服务(上服务)。这里重点是分清楚上服务和下服务,上服务指的是LED服务、串口服务等等根据具体硬件的功能提供接口的进程,每个硬件都有一个服务;下服务指的就是安卓系统建立的那个系统服务进程,它是帮忙运行维护LED服务/串口服务这些一系列服务的。 |
JNI层 | 框架层的“服务”进程有些操作要控制硬件,我们知道驱动都是C语言写的, JNI就是把做了个表,把java操作硬件的函数和C操作硬件的函数映射起来。比如java层有个函数叫native_ioctrl(),C语言层有个函数叫ioctrl(),JNI的功能就是提供映射表,将native_ioctrl()和ioctrl()映射起来。 |
思考 | 可能会有疑问,有JNI层了还要HAL层干嘛?JNI层不也可以用open/write/read/ ioctrl这些系统调用去通过设备节点来控制硬件嘛?其实多了HAL层最大的好处就是可以把公司保密的代码写在HAL层,然后编译成库给到JNI层使用。原理如下:我们知道hal层是C语言写的,可以编译成so库文件,然后JNI层是C++写的,可以链接so库文件从而调用HAL层的函数。 |
HAL层 | 把JNI层的xxx_open/xxx_w/xxx_r/xxx_ioctrl等函数给具体实现出来,这里就是真正的使用了open/w/r/ioctrl这些系统调用来操控硬件 |
总结 | 再下层就是linux驱动了,可以看到安卓驱动确实是利用了linux驱动暴露出的设备节点,但是由于linux的系统调用是标准化的,所以我们也不需要太多的Linux驱动的知识也能做安卓驱动 |
linux驱动层 | 利用设备树描述硬件信息,利用字符设备驱动、SPI/I2C/INPUT/V4L2等驱动框架开发 linux驱动,最终暴露出设备节点/dev/xxx给到应用层调用。 |
下面就从下层到上层来介绍一下,linux驱动、安卓驱动、安卓应用层怎么从无到有,造出一个led控制函数。也就是说最终的效果是安卓应用层,有一个api可以控制开发板上的led灯。
1.1 linux驱动层的ledctrl
最简单的字符设备驱动:
"
入:模块入口
号:获得主、次设备号
注:cdev_init,cdev_add把这个字符设备注册进系统,包括fops
节:创建类,创建设备节点/dev/led
硬:硬件相关的操作,比如led具体是哪个GPIO控制寄存器,一些寄存器的初始化和配置
操:实现fops,比如xxx_open,xxx_write,xxx_ioctrl,这里就是控制寄存器让led亮灭
"
linux驱动层的效果:
生成了/dev/led这个设备节点,然后应用层可以通过ioctrl(fd,on/off,which)来控制这个led灯的亮灭。
1.2 HAL层的ledctrl
这里就简单了,就是调用ioctrl来控制Led嘛,如下:
static int led_ctrl(struct led_device_t* dev, int which, int status)
{
int ret = ioctl(fd, status, which);
ALOGI("led_ctrl : %d, %d, %d", which, status, ret);//打印信息
return ret;
}
hal层对led的控制就是这样,但是hal层本身也有一些规范要遵守,其实就是硬件操作的一个组织规范,看下面就知道了:
hal层规范1:struct hw_device_t
把硬件操作都用结构体 struct led_device_t 来维护:
static struct led_device_t led_dev = {
.common = {
.tag = HARDWARE_DEVICE_TAG,
.close = led_close,
},
.led_open = led_open,
.led_ctrl = led_ctrl,
};
这个结构体其实是我们自己定义的,只是对第一个成员有要求,看注释:
struct led_device_t {
struct hw_device_t common;//第一个成员必须是struct hw_device_t,其他随意
int (*led_open)(struct led_device_t* dev);
int (*led_ctrl)(struct led_device_t* dev, int which, int status);
};
hal层规范2:struct hw_module_t
要把上面的包含了硬件操作的结构体led_dev,给放到这个结构体的一个成员中,也就是放到hw_module_t->methods->open这个成员里面,其实都是一些固定的东西,照抄就行了:
先是hw_module_t->methods:
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.id = "led",//这个id要记下来,是JNI层乃至框架层链接到HAL层的关键
.methods = &led_module_methods,//这里,有个成员要包含硬件操作的结构体
};
然后是methods->open:
static struct hw_module_methods_t led_module_methods = {
.open = led_device_open,
};
static int led_device_open(const struct hw_module_t* module, const char* id,
struct hw_device_t** device)
{
*device = &led_dev;//看,操作硬件的结构体在这里
return 0;
}
这个规范不用过多纠结,抄就完事了
hal层的编译:
因为hal层是要编译出库给JNI层调用嘛,所以还是讲一讲hal层的编译:
hal_led.c和hal_led.h放在这里:
hardware/libhardware/include/hardware/led_hal.h
hardware/libhardware/modules/led/led_hal.c
hardware/libhardware/modules/led/Android.mk //这个makefile文件去参考参考别的hal文件就行了
编译指令:
$ mmm hardware/libhardware/modules/led
这样hal_led.c和.h就会被编译成动态链接库,例如led.default.so,生成的位置是hardware/libhardware,这个库会被安卓自动包含,不用我们操心的。
hal层的效果:
JNI层链接了led.default.so这个库,就可以调用hal层封装好的函数,led_ctrl去控制led灯的亮灭了。
1.3 JNI层的ledctrl
JNI层的文件名是com_android_server_LedService.cpp,很明显是C++写的哈,也叫native层,叫法不一样无所谓。但是需要注意的是这个文件名,里面有LedService的字样,我们前面说到JNI层的上一层,其实就是框架层了,框架层的核心就是“服务”,即下服务(系统服务层)和上服务(具体硬件服务层),现在我们这个文件其实就是上服务的基础,之后会有一个LedService.java(具体硬件服务之Led服务)会来调用我们这个JNI文件的函数,并且会有一个SystemServer.java(系统服务层),来管理LedService.java(Led服务)。
还是一样,先看看JNI层怎么调用hal层的函数来实现led的控制,再来介绍JNI层的一些比较固定的东西,也就是规范之类的。
JNI层调用流程:
首先,引入hal_led.h的这个头文件:
#include <hardware/led_hal.h>
然后在这里也造了一个自定义的操作硬件结构体,和hal层的一毛一样哈:
下面注释里我标注了注意1和注意2的地方,其实就是对hal层规范的解析,通过hw_get_module这个接口就可以同步JNI层和HAL层的操作硬件结构体led_device_t的内容,使得JNI层可以去调用hal层已经实现了的函数。
static led_device_t* led_device;
jint ledOpen(JNIEnv *env, jobject cls)
{
jint err;
hw_module_t* module;
hw_device_t* device;
ALOGI("native ledOpen ...");
/* 1. hw_get_module */
err = hw_get_module("led", (hw_module_t const**)&module);//注意这里1
if (err == 0) {
/* 2. get device : module->methods->open */
err = module->methods->open(module, NULL, &device);//注意这里2
if (err == 0) {
/* 3. call led_open */
led_device = (led_device_t *)device;
return led_device->led_open(led_device);
} else {
return -1;
}
}
return -1;
}
接着就能愉快地调用led_ctrl来控制led的亮灭了!
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
ALOGI("native ledCtrl %d, %d", which, status);
return led_device->led_ctrl(led_device, which, status);//调用hal层的led控制函数
}
接下来是一些JNI的规范,同时捏,也说明了java程序怎么去调用c语言编写的函数
JNI规范1:java接口和c语言接口的映射表
static const JNINativeMethod methods[] = {
{"native_ledOpen", "()I", (void *)ledOpen},
{"native_ledClose", "()V", (void *)ledClose},
{"native_ledCtrl", "(II)I", (void *)ledCtrl},
};
举一个例子哈,例如第三行:
native_ledCtrl这个就是java层实现的函数名
ledCtrl这个就是我们在JNI层实现的函数名
"(II)I",里面的II指明函数是有两个参数的,后面的I指明函数的返回类型是int型!
JNI规范2:注册这个JNI接口到安卓系统中
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/LedService",
methods, NELEM(methods));
}
从这个com/android/server/LedService我们能获得很多信息:
把斜杠换成下划线,com_android_server_LedService.cpp就变成了我们JNI文件名
把com去掉,android/server/LedService.java就指明了框架层的上服务(led服务)文件所在路径,接下来我们要看框架层,首先要看的就是这个上服务。
JNI层的效果:
JNI层用C++,构造了一个表格,将ledCtrl(JNI层)和native_ledCtrl(java层)映射了起来,JNI层的主要作用就是让java语言能够调用C程序编写的接口,接下来就都是用java写的了,也就是框架层(上服务和下服务),以及APP层。
1.4 框架层的ledctrl
接下来要讲的调用关系虽然不复杂,但是文件比较多,所以先看看下面的框图:
我们刚刚是讲到了JNI层,现在要讲的就是下服务,也就是系统服务层。
系统服务层(下服务):
从小了来看,对于LED来说,JNI层主要就是要执行com_android_server_LedService.cpp文件里的函数register_android_server_LedService,从而建立java接口和c语言接口的映射。
但是我们的板子,不止有LED,还有light,还有串口等外设,所以会有很多硬件的register_android_server_xxxService,这些映射表的注册操作,被集中到onload.cpp文件里的JNI_OnLoad函数中。所以从大了来说,JNI层的核心任务就是提供JNI_OnLoad函数,给上层调用。
JNI的上层是系统服务层,它会执行这条语句:
//file :SystemServer.java
System.loadLibrary(“android_servers”)
上面的android_servers实际上是一个C语言动态库,load这个库之后就会自动执行JNI_OnLoad函数,来注册所有硬件的java和c函数的映射表。
//file :onload.cpp
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_LightsService(env);
register_android_server_VibratorService(env);
register_android_server_LedService(env); //我们的led服务
register_android_server_SystemServer(env);
//... ...
}
具体硬件服务层(上服务):
那么JNI层的ledctrl被映射到java层的native_ledCtrl函数后,是谁调用呢?答案就是java版的native_ledCtrl接口会被上服务(具体硬件服务)即LED服务调用(LedService.java),如下代码所示:
//file :LedService.java
public class LedService extends ILedService.Stub {
private static final String TAG = "LedService";
/* call native c function to access hardware */
public int ledCtrl(int which, int status) throws android.os.RemoteException
{
return native_ledCtrl(which, status);//是不是很熟悉,就是java版的ledCtrl接口
//封装native_ledCtrl来实现LedService->ledCtrl()
}
public LedService() {
native_ledOpen();
}
public static native int native_ledOpen();
public static native void native_ledClose();
public static native int native_ledCtrl(int which, int status);
}
LedService实际是继承于ILedService,这个I是interface即接口的意思,是结合了Binder通信机制的一种产物,这里先mark一下ILedService,等一会就讲这个。
上、下服务与APP层调用关系:
这里值得重点说一下app层和框架层交互的一个流程,这样我们就能够理解系统服务层、具体硬件服务层、app层之间的一个关系。
①app层:getService("led"),向系统服务层请求,我要用led辣。
②系统服务层:收到请求,查看现在led有没有别的app进程在用。没有就把ILedService类实例给到请求led服务的app进程。可以理解为Led服务是系统服务的小弟,系统服务安排Led服务(具体硬件服务层)去响应app进程的操作。
③app层:调用mService.led_ctrl() 控制led,实际就是调用了LedService->ledCtrl(),即Led服务在响应app进程的调用 。
ILedService.java和LedService.java
ILedService.java可以理解为也是上服务(具体硬件服务)的一部分,但是不是由我们编写的,是可以生成的,我们前面说I的内涵其实就是结合Binder机制,生成的代码的过程其实就是在构建Binder机制相关的代码。简单理解即可,主要是掌握怎么生成ILedService.java,步骤如下:
1.新建一个文件ILedService.aidl,放在/frameworks/base/core/java/android/os目录里。
只需要把需要的硬件操作接口声明出来就行。
//file :ILedService.aidl
package android.os;
/** {@hide} */
interface ILedService
{
int ledCtrl(int which, int status);
}
2. 修改/frameworks/base的Android.mk,模仿别的把这个的编译项加进去:
core/java/android/os/ILedservice.aidl
3.编译
执行mmm .就会生成ILedService.java,目录路径是在
out/target/common/ob/JAV_LIBRARIES/framework intermediates/src/core/iava/android/os
ILedService.java里面都是生成的代码,拉到最下面会发现我们刚刚声明的led控制函数:
static final int TRANSACTION_ledCtrl = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int ledCtrl(int which, int status) throws android.os.RemoteException;
}
ILedService.java中的ledCtrl函数,实际是在具体硬件服务层LedService.java中实现的,具体是通过调用java版的JNI接口来操控硬件。
框架层的效果:
1.实现多个app进程访问同一个硬件的解决机制(即系统服务+具体硬件服务,系统服务使用等待队列管理app进程使用具体硬件服务的请求)
2.实现了server和client的模型(就是app层进程是client,负责发请求,框架层作server负责响应请求)
1.5 APP层的ledctrl
永远都不要忘记最初的目标,无论是linux驱动也好,安卓驱动也好,作为驱动工程师最终的目的都是为了让应用层能够控制led灯的亮灭,有时候我们觉得我们掌握的技术很复杂,仿佛在技术上有优越感,但实际在生活中,掌握技术本身往往不是目的,实现产品功能才是老板想要的,尽管最后一步通常是简单的,容易让人忽略前边驱动的努力和工作量......
框架层的最顶部就是具体硬件服务层(上服务),它控制led的最顶层就是ILedService.java里面生成的接口ledCtrl,参考上面“上、下服务与APP层调用关系”,我们知道app层是这样控制led的:
APP层编程流程:
①引入包:import android.os
②app层:getService("led"),向系统服务层请求,我要用led辣。
③app层:框架层返回了ILedService类实例,现在我们可以调用ILedService.ledCtrl() 控制led了。
APP层的效果:
led亮灭的控制命令,经过安卓框架(框架层---->>JNI层---->>HAL层)、linux驱动框架等多个层次,一层一层往下,最终改变了GPIO的寄存器,让led闪烁起来。
1.6 总结
到这里整条控制链就打通了,这就是所谓的安卓驱动...看到这里,我们再回过头来看这个框图,是不是能透过表面的文字,看到更多深层次的逻辑呢~
(完~)