本文是阅读《Android源代码情景分析》中 “第二章 硬件抽象层”后的感想。
本章首先介绍了android硬件抽象层的必要性,有利于我对android分层模型的清晰认识。其次,使用了一个实际例子,说明了硬件从kernel到硬件抽象层,再到android framework层,再到application层的实现、使用。对于我理解整个android架构,有很大帮助。
一、硬件抽象层在android系统中的位置
android对硬件的支持划分为两部分,一部分在Linux内核中实现,也就是硬件驱动,另一部分则是硬件抽象层。至于分成两层实现的原因,就需要从android系统的生态环境说起。android使用了Linux内核,而Linux是开源的,这就要求Linux中的硬件驱动也得开源,若android对硬件的支持全部包含在Linux内核,定会损害硬件提供商的利益。另一方面,若完全不开源,将硬件的支持都在用户空间中实现,这又做不到。同时有一个利好条件是,android系统源代码中允许设备厂商添加/修改android源代码,又可以不公开。这刚好解决了硬件厂商的难题——将对硬件的支持中划分到用户空间和内核空间,其中,内核空间实现硬件驱动,完成简单的硬件访问通道;用户空间以硬件抽象层模块的形式,封装硬件的实现细节和参数。
用官方的话来说,硬件抽象层(Hardware Abstract Layer, HAL)运行在用户空间中,向下屏蔽硬件驱动模块的实现细节,向上提供硬件访问服务。它在android分层结构中的位置,如下图所示。
(图片参考http://www.cnblogs.com/muhuacat/articles/5279293.html)
Linux kernel层属于内核空间,其以上则是用户空间。可见,用户空间包括硬件抽象层、第三方库和android运行时、应用框架层、应用层。这样一来,android分层的概念,就不再模糊了。
二、对书中例子的感想
书中的例子,是对一个虚拟寄存器硬件展开,Linux内核层实现驱动、硬件抽象层实现模块接口、android framework层实现硬件访问服务、application层调用硬件访问服务,对硬件进行读写。
1.Linux内核层的初步认识
由于Linux内核相关的知识还是比较缺乏,这一部分读起来很吃力。
大体可以理解到,硬件的驱动程序中,需要向上层用户空间提供硬件设备的访问接口,有三类接口:proc文件系统接口、传统的设备文件接口、devfs文件系统接口。(这部分后续补充知识)
a. 在~android/kernel/goldfish/drivers下新建文件夹,添加驱动程序,驱动程序模块自身需要有Kconfig和Makefile文件,其中,kconfig文件定义了该驱动的编译选项,编译时可以通过make menuconfig命令设置为不同的方式:直接内建在kernel中、编译成内核模块、不编译到内核中;makefile文件是该驱动的编译脚本文件。
b. 硬件驱动实现后,需要将该驱动的kconfig文件加入到Linux kernel的根kconfig文件中,使得make menuconfig命令,可以找到它们。Linux kernel的根kconfig文件在~android/kernel/goldfish/arch/$(ARCH)目录中,android中,$ARCH=arm,于是需要修改的是~android/kernel/goldfish/arch/arm/Kconfig文件。加入的方法是:
在menu "Device Drivers"节点中加入source "drivers/freg/Kconfig"(这是新增的驱动Kconfig文件的路径)
c. 还需要将该驱动模块的编译文件makefile加入Linux kernel drivers的根makefile中(~android/kernel/goldfish/drivers/makefile)。
d. 编译内核
先执行命令make menuconfig修改驱动的编译方式,再执行make命令。编译结束后,会有提示,更新了原来的arch/arm/boot/zImage.img文件。
检查驱动是否加入kernel的方法:
使用该zImage启动android模拟器,在设备/dev目录中会有设备文件freg,说明freg驱动程序已经将设备freg注册到设备文件系统中了;
a. 验证proc文件系统接口
使用cat、echo命令对/proc/freg文件进行读写
/proc# cat freg
/proc# echo '5' > freg
b. 验证devfs文件系统接口
进入/sys/class/freg/freg中,使用cat、echo命令对val进行读写。
/sys/class/freg/freg# cat val
/sys/class/freg/freg# echo '0' > val
c. 验证普通文件系统接口/dev/freg可用
需要c语言编写程序,获取文件句柄fd,之后使用read、write函数,对句柄进行操作。
对测试程序编译后,更新system.img,使用它启动android模拟器后,在/system/bin中会有二进制可执行文件freg。使用./freg命令即可运行。
2.硬件抽象层模块
虚拟寄存器硬件freg在硬件抽象层模块的实现文件包括:
~/android/hardware/libhardware/include/hardward/freg.h
~/android/hardware/libhardware/modules/freg(文件夹中包含cpp程序和android.mk文件)
虚拟硬件freg的硬件抽象层模块中,实现了freg_device_open、freg_device_close对/dev/freg设备进行开关,调用read/write函数实现了读写虚拟硬件设备freg寄存器val的内容——freg_get_val、freg_set_val方法。
硬件抽象层模块中android.mk文件内容如下,表示要将该硬件抽象层模块编译成一个动态链接库文件,名称为freg.default,并且保存在out/target/product/generic/system/lib/hw目录下:
这样通过mmm ./hardware/libhardware/freg 和make snod命令,更新system.img,使用android模拟器运行后,system/lib/hw下,有freg.default.so文件。当我们要加载该模块时只需要指定它的ID值(文中设置为freg),系统就会根据一定的规则找到并加载freg.default.so文件。
a. 硬件抽象层模块文件的命名规范
硬件抽象层模块文件的命名为<MODULE_iD>.variant.so(动态链接库),其中<MODULE_iD>表示模块的ID,variant表示系统属性,有ro.hardware、ro.product.board、ro.board.platform、ro.arch。一个硬件抽象层模块文件,四个属性具备其一。
ro.hardware属性值确定方法:系统启动过程中,/proc/cmdline文件中若有androidboot.hardware属性,把它的值作为ro.hardware的值;否则,就将/proc/cpuinfo文件读取并解析,找到Hardware字段的值,就是ro.hardware的值。
ro.product.board、ro.board.platform、ro.arch属性值的确定方法:是从/system/build.prop文件读取出来的。而/system/build.prop是由编译系统中的编译脚本build/core/Makefile和shell脚本build/tools/buildinfo.sh生成的。
b.硬件抽象层模块加载的过程
硬件抽象层模块加载调用的是hw_get_module函数,输入id,即要加载的硬件抽象层模块ID;输出module,如果加载成功,它指向一个自定义的硬件抽象层模块结构体。若加载成功,函数返回值为0.
int hw_get_module(const char *id, const struct hw_module_t **module)
寻找、加载的流程:
一个循环,从/system/lib/hw(系统抽象层模块接口文件的位置)和/vendor/lib/hw(设备厂商放硬件抽象层模块接口文件的位置)中,分别找<MODULE_iD>.variant.so文件,找到一种则退出循环,进行加载。
以找freg.default.so为例:
确定ro.hardware的值,在android 模拟器中,该值为goldfish,于是就在以上两个文件中找freg.goldfish.so,没有找到,继续
确定ro.product.board的值,然后找对应的freg.XXX.so,没有找到,继续
确定ro.board.platform的值,然后找对应的freg.XXX.so,没有找到,继续
确定ro.arch的值,然后找对应的freg.XXX.so,没有找到。
四种都没有,就找下freg.default.so,找到了。
加载。
c. 硬件设备访问权限
硬件设备(/dev/freg)读写,需要root用户才可进行。一般程序(硬件抽象层模块的程序)没有权限,于是需要给硬件设备设置other组权限。
在system/core/rootdir中的配置文件ueventd.rc中设置:
/dev/freg 0666 root root
修改该文件后,需要重新编译整个android源代码才可以,修改的是ramdisk.img文件。不过有方法可以避免:
把ramdisk.img转为ramdisk.img.zip并解压,得到cpio格式的归档文件;
使用cipo命令对其解除归档,还原ramdisk.img,
修改/android/ramdisk目录中的ueventd.rc文件,
前两步逆向,把ramdisk.img还原。
3.android硬件访问服务--application framework层
硬件访问服务通过硬件抽象层模块为上层应用程序提供硬件读写操作。
硬件访问服务使用Java开发,而硬件抽象层模块使用C++开发,所以硬件访问服务必须通过JNI(Java本地接口,Java native interface)来调用硬件抽象层模块的接口。
android的硬件访问服务通常运行在系统进程System中,使用这些服务的应用程序通常在另外的进程,这就需要使用进程间通信机制来完成二者的通信任务。android提供的进程间通信机制是binder进程间通信机制。
在binder进程间通信机制中,提供服务的一方必须实现一个具有跨进程访问能力的服务接口,使用服务的一方通过该接口访问服务。所以,在实现硬件服务的同时,还应该定义好服务接口。
书中以freg为例,开发了它的硬件访问服务FregService,定义了其硬件访问服务接口IFregService。
a. 定义硬件访问服务接口
使用android接口描述语言(android interface definition language,AIDL),接口定义文件以.aidl为后缀,通常在/frameworks/base/core/java/android/os目录中。
IFregService接口编译后会包含在framework.jar文件中,继承了android.os.IInterface接口,并在IFregService接口内部,定义了一个binder本地对象类Stub,在IFregService.Stub类内部,定义了一个binder代理对象类Proxy。
在使用binder进程间通信时,server进程使用stub来等待client进程发送进程间通信的请求,而client在发送请求之前,需要先获得service接口的proxy对象,然后通过binder代理对象接口向server发送进程间通信请求。
b. 实现硬件访问服务
硬件访问服务FregService则实现了硬件访问服务接口IFregService中定义的两个函数,其实现的细节是通过JNI调用,最终调用到硬件抽象层模块中定义的方法。
c.硬件访问服务的启动
android硬件访问服务通常是在系统进程system中启动的,系统启动时启动,即开机启动。
system启动时,会创建serverThread线程来启动系统的关键服务,其中就包括硬件访问服务。在serverThread线程中,会创建FregService实例,并注册到Service Manager中。就OK了。
硬件访问服务在system.img中。
4.开发android应用程序使用硬件访问服务
使用方法一目了然,30行通过service manager获取名为freg服务的binder代理对象接口(这个服务就是硬件访问服务FregService),29行将binder代理对象接口转换为FregService代理对象接口。