从LED硬件控制流程认识Android架构!

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系统还贼慢,而且!电脑内存根本不够用!

简单解释一下上面的框图:

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 总结

        到这里整条控制链就打通了,这就是所谓的安卓驱动...看到这里,我们再回过头来看这个框图,是不是能透过表面的文字,看到更多深层次的逻辑呢~

(完~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值