非标准硬件控制之增加系统API

来源:互联网 发布:宁波cnc编程招聘信息 编辑:程序博客网 时间:2024/05/21 12:49

背景分析:

android主要设计目标之一就是要使应用程序和系统能独立于具体的计算机体系结构和硬件平台,表现在设备驱动程序设计上.

对于已有的Linux标准设备驱动程序可以直接继续使用,只需为其增加应用层JNI接口。

但对于Linux没有的非标准设备则提倡在Linux内核中驱动部分只做很少的接口工作,尽量把驱动程序的主要处理放在Android的上层架构中,即在应用层实现。

以硬件控制hello world(点led为案例):

1  Android系统驱动程序架构

  1.1 驱动程序分层体系结构

  Android是基于Linux的,它使用了Linux内核,但应用程序使用Java语言开发,所以应用程序在调用设备驱动时不能像一般的Linux应用程序那样直接使用系统调用,必须通过Java虚拟机的JNI的本地(Native)方法使用设备。另一方面,Android要成为一个通用性强的平台,必须加强它的可移植性。这也是在Android架构添加一个硬件抽象层(HAL)的原因,目的是为设备的调用提供一个更高级的封装图1所示为Android驱动程序架构。

Android驱动程序架构

图1 Android驱动程序架构

  HALStub是以Linux共享库(*.so)的形式存在,在整个驱动架构中,它是设备驱动程序运行在用户空间的一部分,它向上为Dalvik虚拟机提供硬件设备的抽象接口,向下通过系统调用与Linux内核中的驱动程序进行数据交互。在这个过程中HAL可以对驱动程序的数据进行处理,也就是说在Linux内核中的驱动程序部分只需要提供一个与硬件设备传输数据接口的功能,而其余具体的操作可以由HAL完成。

  1.2 Android的硬件抽象层

  Android的硬件抽象层HAL(HardwareAbstractLayer)在Android的架构中是在库这一层中,通过这一层,硬件厂商可以把部分设备的驱动源码封装在这一层而不公开源代码。

  对图1分析,设计HAL就是为了把应用框架和Linux内核分离出来,让Android使用Linux内核而又不完全依赖Linux内核。当然,驱动程序并不是完全从Linux内核中分离出来,一些基本的处理必须由内核来完成,HAL只是分担了Linux设备驱动的部分功能,至于这部分的功能占驱动程序功能的比例目前并没有一个标准。

  在Android系统发展过程中,HAL的实现也逐步有了一些变化,旧的HAL是一种模块化的思想,通过共享库的形式由Runtime在JNI时以函数调用方法调用,这种做法并没有通过封装,即上层应用可以直接调用硬件。另外,这种方法可被多个进程使用,映射到多个进程空间中浪费内存资源。

  现在HAL提出一种Stub的思想,HALStub是一种代理的概念,Stub同样是以共享库(*.so)格式存在,但上层应用并不像加载动态库那样调用Stub。这种HAL是由模块与Stub结合而成,Runtime通过模块提供的统一接口获取并操作Stub。Stub向HAL提供操作的回调函数,Runtime向HAL取得指定模块的操作函数后,调用这些回调函数。这是一种间接函数调用的方式,HAL里包含了多个Stub。图2为HALStib原理。

HALStub原理

图2 HALStub原理

  1.3 android的JNI实现原理

  JNI是JavaNativeInterface的缩写,是在Sun的Java平台中首先定义出来的,它允许Java代码与其他语言代码进行交互。Android中JNI的设计目的也是一样:

  (1)应用程序需要与硬件平台交互时,Java库中的类不可能支持;

  (2)本地已经使用其他语言编写的库允许Java程序访问;

  (3)某些功能用较低级的语言实现的执行效率较高,让Java程序调用这些函数。

  在Android应用层中的程序或组件都是用Java语言开发的,这些Java代码编译后变成Dex格式的字节码,由Dalvik虚拟机执行,在执行过程中需要调用本地库时,由虚拟机载入这些本地库,然后让Java函数调用库中的函数,虚拟机相当于一座桥梁,让Java与本地库能够透过标准的JNI界面互相沟通。

  应用程序在虚拟机里执行,通过函数System.loadLibrary()通知虚拟机载入指定的库,例如在Java代码中包含代码如:

  虚拟机就会在Android文件系统的“/system/lib/”目录中查找libsample_jni.so库文件,虚拟机载入libsample_jni.so后,Java代码就可以与库文件结合起来一起执行。

  这些用C语言编写的本地库必须遵循规范,当虚拟机执行System.loadLibrary()函数时,首先执行本地库里的JNI_OnLoad()函数,这个函数需要实现的功能是:返回给虚拟机此本地库使用的JNI版本;对库进行初始化。如果本地库里没有实现JNI_OnLoad()函数,虚拟机就会默认本地库使用最老的JNI1.1版本。

  JNI_OnUnload()函数与装入函数相对应,在虚拟机释放该本地库时,会调用JNI_OnUnload()函数进行资源回收动作。

  在应用层的Java代码通过虚拟机调用本地函数,一般要依赖于虚拟机查找库里的本地函数,如果需要调用比较频繁,每次都要寻找一遍,就会花费较多的时间影响效率,在这里可以通过registerNativeMethods()函数把gMethods[]表格所含的本地函数注册到虚拟机里。


2  Android硬件驱动程序设计

  Android是一个开放平台,在嵌入式移动设备领域里具有很好的应用前景,但在不同的设备上往往有不同的硬件支持,要在Android中添加这些硬件应用,不是单纯地在Linux内核中添加驱动模块,还必须在用户空间和应用框架中添加对应的支持。下面以给6410开发板添加一个LED显示控制驱动功能为例展示Android平台添加新硬件支持的过程。

  2.1 硬件驱动程序的框架

  LED控制功能通过应用程序来开关开发板上的LED灯。在应用层中LED控制程序调用LED控制服务(AndroidService),应用层中的LED控制服务通过JNI让虚拟机加载LED控制的本地库,然后向HAL获取LEDStub,由Stub调用在Linux内核中的LED驱动。图3为LED控制功能的架构设计。

  从LED控制功能的架构来分,整个功能可以分成五个模块:LED驱动模块、LEDStub模块、LED本地服务模块、LED服务管理模块和LED应用模块。

LED控制功能的架构设计

图3 LED控制功能的架构设计

  2.2 HAL中的Stub的设计与实现

  图4是LEDStub的实现过程。LEDStub是硬件抽象层中LED控制的代理,当LED控制的本地服务需要调用LEDStub时,通过函数hw_get_module()结合LEDStub的模块ID向HAL申请LEDStub,本地服务获得Stub对象后,可以把Stub看作一个抽象硬件进行操作。

LEDStub的实现过程

图4 LEDStub的实现过程

  下面是定义LEDStub的HAL结构体:

  将结构体led_module_t初始化一个实例名为HAL_MODULE_INFO_SYM,这个名称不能修改,实例里包含了Stub的模块信息,主要包括:

  tag:标记了结构体的类型,这里的值为HARDWARE_MODULE_TAG;id:LEDStub的模块ID,在本地服务向HAL获取Stub时调用的函数hw_get_module()中,通过这里的id查找LEDStub;methods:是结构体hw_module_methods_t的实例,为HAL定义回调函数open()。

  这里的open()函数是一个必须实现的回调函数接口,在本地服务获得Stub对象后调用,它负责申请结构体led_control_device_t的空间,填充信息,注册具体操作的回调函数接口并打开LED驱动。

  结构体led_control_device_t继承了hw_device_t,在open()函数调用时填充的主要信息包括:

  tag:结构体的类型,这里的值为HARDWARE_DEVICE_TAG;

  module:Stub的模块,也就是实例HAL_MODULE_INFO_SYM中的hw_module_t部分;

  close:释放LEDStub的回调函数;

  fd:打开设备驱动文件返回的文件描述符;

  ns_set_on:打开LED灯的回调函数指针;

  ns_set_off:关闭LED灯的回调函数指针。

  回调函数指针“*ns_set_on”和“*ns_set_off”分别指向实现函数hal_led_on()和hal_led_off(),在实现函数中通过系统调用ioctl()对LED灯进行开关控制。

2.3 硬件控制服务的JNI实现

  LED控制本地库编译后为“libled.so”保存在Android文件系统的“/sysem/lib/”目录下面,LED控制服务的Android进程运行后由虚拟机实例装入本地库,具体实现过程如图5所示。

LED控制服务的JNI实现过程

图5 LED控制服务的JNI实现过程

  控制服务调用System.load()函数,它的虚拟机实例就会装入LED控制本地库,虚拟机会首先调用JNI_OnLoad()函数完成:

  (1)把虚拟机环境信息保存到本地库的一个结构体“JNIEnv”的实例中;

  (2)建立一个应用层中的LED控制服务与本地库的JNI函数表;

  (3)返回虚拟机本地库使用的JNI版本。

  加载完后,应用层中的LED控制服务就可以通过虚拟机中的JNI函数表把运行的Java函数转换为本地函数执行。在LED控制服务类中定义有JNI函数的方式,例如下面的代码段:

第一步 为linux的led驱动提供android的HAL层


HAL是android的硬件逻辑层,在这里,主要是为framework层提供一层Led的操作接口。直接操作leds设备的函数在第一步已经指明了,就是ioctl函数,但是,如何将这个功能增加到android的框架里呢?
首先,增加hardware/libhardware/modules/目录下的mydevice目录,然后在这个目录下再新建Led目录,在led目录下,分别建立led.c和Android.mk文件在mydevice这个目录下,也需要建立Android.mk文件用来搜索mydevice目录下的每个驱动设备目录。在hardware/libhardware/include/hardware 下,建立hardware目录,在下层建立mydevice/led.h,该文件用来实现一些module_id之类的宏和结构体,这里比较重要的是DEVICE_NAME一定要写对,这个就是程序需要读取的设备,还有MODULE_NAME的取名要跟Android.mk里面的LOCAL_MODULE前缀一样,否则程序加载的时候会找不到。硬件结构体是继承hw_device_t这个结构的,在这个结构体里,可以定义leds设备的fd,和这个设备需要操作的一些结构函数,比如这边就增加了set_led,用来控制Led设备的。
最后还要修改hardware/libhardware/ Android.mk,将增加好的led结构添加进去。
include $(addsuffix /Android.mk, $(addprefix $(LOCAL_PATH)/, \
            modules/gralloc \
            modules/mydevice \ --》增加这行
            tests \
        ))
        
然后编译一下,out/target/product/mini6410/system/lib/hw目录下会生成led.default.so这个文件,编译就成功了。



第二步 增加jni底层对上层的应用接口


在这一步当中,封装了底层的.c的接口函数,使得上层Java可以调用, 做到这一步,java应用层应该也可以调用这一步生成的.so,友善提供的.so文件应该就是做到这一步生成的。
这一步当中,首先添加framework/base/services/jni目录下的com_android_server_DeviceService.cpp文件,这个文件用来动态加载jni的接口函数。为java上层提供帮助。Method_table这个表提供了底层到Java层的映射,
    static const JNINativeMethod method_table[]=
    {
        {"openLed", "()Z", (void*)led_init}, --》第一个是java层可以调用的函数名,第二个是函数签名,第三个是本层函数名
        {"set_Led", "(II)I", (void*)setLed},
    };
java层调用映射之后的字符就可以完成对底层的通讯了。全部写完之后,还要在frameworks/base/services/jni/onload.cpp这个文件当中将method_table这个表加载进系统里(JNI_OnLoad),然后在Android.mk下增加com_android_server_DeviceService.cpp文件就可以编译了。
LOCAL_SRC_FILES:= \
    com_android_server_AlarmManagerService.cpp \
    com_android_server_BatteryService.cpp \
    com_android_server_InputManager.cpp \
    com_android_server_LightsService.cpp \
    com_android_server_PowerManagerService.cpp \
    com_android_server_SystemServer.cpp \
    com_android_server_UsbService.cpp \
    com_android_server_VibratorService.cpp \
    com_android_server_location_GpsLocationProvider.cpp \
    com_android_server_DeviceService.cpp \  --》增加这行。
    onload.cpp


第三步  用aidl增加java services


Aidl是一个工具,他可以用来增加java的services,首先,在frameworks/base/core/java/android/os/路径下增加IDeviceService.aidl接口,然后,在 frameworks/base/Android.mk里面增加这个aidl文件,编译一下,这个时候就会生成stub,新增frameworks/base/services/java/com/android/server这个路径下的DeviceService.java,这个java文件里需要继承刚才用aidl工具生成出来的IDeviceService.Stub,之后对jni接口进行再一次封装。在frameworks/base/services/java/com/android/servier/SystemServer.java里面需要增加这个服务,之后重新编译程序就可以了。


第四步  在java应用层调用底层编译好的java service服务


用eclipse新建一个java项目,然后在这个新建的工程里面把操作的button做好,这个界面由于只是测试程序,所以就做了一个button,这个button的功能就是依次打开和关闭led灯。


具体实现参考 git://github.com/zbunix/hal_jni_led.git