Android系统HAL层原理及编程注意事项

来源:互联网 发布:什么叫网络故障诊断 编辑:程序博客网 时间:2024/06/05 14:13
一,HAL层次结构

闲话不扯,直入主题,Android的HAL层其实不是一定要有的,只是为了系统的完整性,模块化,或者说是为了满足硬件厂商的保密需求,于是有了这个HAL层,HAL层起到承上启下的作用,承上就是指被JNI调用,启下就是指通过一般的系统调用open/read/write等访问硬件driver,那么先来看一个典型的android系统的层次关系:

APK  ( Activity/Service )                                                    ----第一层:Applications
  |
  | 
  |
XXXManager   [ 这个Manager不是必须的 ]                   ----第二层:Framework
  |  [对aidl和service的封装而已]
AIDL    ( *.aidl )                   
  |   [ IXXX.Stub.asInterface(ServiceManager.getService()) / Context.getSystemService() ]
service  ( XXXService.java )
  |   [ native method ]
  |
  |
jni   ( com_android_server_*.cpp )                               ----第三层:Android Runtime/Dalvik 
   |  [ hw_get_module(*_HARDWARE_MODULE_ID) ,使用dlopen/dlsym加载HAL]
  | 
  |
HAL  ( *.default.so / *.ATM705.so )                                ----第四层:HAL
  |  [ 通过系统调用 open/read/write/ioctl来访问驱动,从而达到操作硬件的目的 ]     
  |
  |  
kernel driver  ( *.so )                                                         ----第五层:Linux Kernel
  

可以说,jni层,也就是虚拟机层,起到了承上启下的作用,非常重要。说啥呢,不是说HAL层吗,对,说HAL,可以说,没有HAL层,android系统照样可以搭建起来,直接通过系统调用访问驱动就好了。


二,HAL实现原理


HAL的实现原理有人总结为321条款,即3个结构体2个宏定义1个方法,的确很简单,网上一搜一大把的介绍,我就不重复了,主要就是看一下这2个文件:

android/hardware/libhardware/include/hardware/hardware.h

android/hardware/libhardware/hardware.c

其中,最重要的就是使用dlopen/dlsym进行HMI的映射,所以,没有dl库,HAL层是玩不转的。


三,HAL重要问题研究


先看一下hw_module_methods_t 中的open类型:

typedef struct hw_module_methods_t { 

     int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device);

} hw_module_methods_t;

open函数指针的参数类型是固定的,必须如此!(其实如果不是这样还能是什么样呢,HAL又不是神仙,怎么会知道你自定义的结构体类型是什么呢)。这个基本原则导致了HAL层编程有一个潜规则,这个潜规则不得不写一下,就是我们在写HAL程序时,自定义的结构体中会包括hw_module_t或者hw_device_t,潜规则规定hw_module_t或hw_device_t必须是我们自定义结构体的第一个成员变量,这是强制性的,没得商量,否则HAL层也玩不转了,详细情况看下文:


这是我在工作中的HAL层自定义的结构体:

"device/actions/common/hardware/include/display.h"

161 struct owldisp_device_t {

163     struct hw_device_t common;  //必须是第一个

167     int (*get_disp_num)(struct owldisp_device_t *dev);

169     int (*get_disp_info)(struct owldisp_device_t *dev,int disp,int * info );

185     int (*set_hdmi_size)(struct owldisp_device_t *dev, int xres , int yres);

191     int (*get_hdmi_fitscreen)(struct owldisp_device_t *dev);

193 };


这是在JNI中访问HAL层的代码:

"./device/actions/common/frameworks/services/jni/com_actions_server_DisplayService.cpp"

static struct owldisp_device_t * mDisplayManager = NULL;

static jboolean actions_server_DisplayService_init(JNIEnv *env, jclass clazz) {

         owldisp_module_t * module;

         if (hw_get_module(DM_HARDWARE_MODULE_ID, (const hw_module_t**) &module) == 0) {

                 module->methods->open(module, DM_HARDWARE_MODULE_ID, (struct hw_device_t**) &mDisplayManager);

         }

}

上面说了,open的第一个参数必须是struct hw_module_t,第三个参数必须是struct hw_device_t,而参数module是owldisp_module_t类型,mDisplayManager是struct owldisp_device_t类型,很明显二者都不符合参数要求,于是只能做强制类型转换,那神奇的根源就是强制类型转换这一点,继续往下:


上面module->methods->open()其实调用的是这个open_display_manager函数:

"./device/actions/common/hardware/libdisplay/display.cpp"

 39 struct disp_manager_context_t {

 41     struct owldisp_device_t device;

 43     int mDispFd;

 45     int mDispNum;

 47     struct owlfb_disp_device * mDisplays[MAX_DISPLAY_NUMBER];

 48 };

static int open_display_manager(const struct hw_module_t* module, const char* name, struct hw_device_t** device) {

372     int status = -EINVAL;

373     struct disp_manager_context_t *ctx = static_cast<struct disp_manager_context_t *>(malloc(sizeof(struct disp_manager_context_t)));

376     memset(ctx, 0, sizeof(*ctx));

380     ctx->device.common.tag = HARDWARE_DEVICE_TAG;

381     ctx->device.common.version = 0;

382     ctx->device.common.module = const_cast<struct hw_module_t *>(module);

383     ctx->device.common.close = close_display_manager;

385     ctx->device.get_disp_info = owldisp_get_disp_info;

395     ctx->device.get_hdmi_cable_state =owldisp_get_hdmi_cable_state;

396     ctx->device.set_hdmi_fitscreen = owldisp_set_hdmi_fitscreen;

397     ctx->device.get_hdmi_fitscreen = owldisp_get_hdmi_fitscreen;

399     ctx->mDispFd = open(DM_HARDWARE_DEVICE, O_RDWR, 0);

401     init_display_device(ctx);

403     if (ctx->mDispFd >= 0) {

405         *device = &ctx->device.common;  

这里的ctx->device.common其实就是hw_device_t,也就是说,返回的仅仅是我们定义的大结构体owldisp_device_t中的成员hw_device_t,而hw_device_t是owldisp_device_t的第一个成员,当强制类型转换发生时,外面接收owldisp_device_t这个大结构体的指针变量会将指针指到HAL层里面的有真正内容的owldisp_device_t这个大结构体中的hw_device_t的起始位置,其实也就是整个大结构体的起始位置,那这样的话,排在hw_device_t之后的所有成员就顺其自然的被外面接收owldisp_device_t这个大结构体的指针变量接纳了,所以看似只是返回了某个成员,而通过简单的一个强制类型转换就拿到了我们在HAL中自定义结构体owldisp_device_t的全部数据,当然包括了HAL提供出来的函数指针,神奇啊!


如果hw_device_t不是结构体owldisp_device_t的第一个成员,那么强制类型转换发生时,必然导致大结构体owldisp_device_t里面的数据的错乱甚至数据的丢失,这样的转换就没有意义了,HAL提供的接口也就彻底看不到了,那还要HAL层干嘛呢!


同理,hw_module_t 也必须是owldisp_module_t 的第一个成员,

 62     owldisp_module_t * module;

 69     if (hw_get_module(DM_HARDWARE_MODULE_ID, (const hw_module_t**) &module) == 0)

你看,传进去的module是owldisp_module_t 类型的,而hw_get_module(const char *id, const struct hw_module_t **module)的参数是固定的,必须是hw_module_t 

所以hw_module_t 也必须是owldisp_module_t的第一个成员,这样返回后的强制类型转换才能正确指向owldisp_module_t类型的全部数据。

 33 struct owldisp_module_t {

 34    struct hw_module_t common;

 35 };


四,问题验证


为了验证这个强制转换的正确性,特意写了一个实验程序,以验证上面的正确性:

 #include <stdio.h>

#include <stdlib.h>

struct person {

    int sno;

    char sex;

    int age;

};

struct student {

    struct person per;

    int val1;

    int val2;

};

void test(struct person **per) {

    struct student *stu = (struct student *)malloc(sizeof(struct student));

    if (stu == NULL) {

        printf("malloc failed!");

        return;

    }

    stu->per.sno = 100201;

    stu->per.sex = 'm';

    stu->per.age = 32;

    stu->val1 = 11;

    stu->val2 = 22;

    *per = &stu->per;

}

int main(int argc, char **argv) {

    struct student *stu = NULL;

    test((struct person **)&stu);

    printf("student.val1: %d, student.per.sno: %d, student.per.age: %d \n", stu->val1, stu->per.sno, stu->per.age);

    return 0;

}

当struct person是struct student的第一个成员时,如果这种强制转换是正确的,那么预期的输出应该是:

student.val1: 11, student.per.sno: 100201, student.per.age: 32

实际输出的确如此,所以结构体的第一个成员的位置在类似这种强制类型转换时的重要作用,得到了验证。其实说到底是因为结构体数据的内存连续性,因为这种连续性导致只要定位到了第一个成员的起始位置,也就等于获得了整个大结构体​的全部数据;如果结构体在内存不是连续的,那HAL这个机制是玩不转的。​


如果存在疑惑,马上可以再做如下实验:

将struct person作为struct student的第二个成员,其他代码保持不变:

 struct student {

      int val1;

      struct person per;

      int val2;

};

编译运行后的输出结果为:student.val1: 100201, student.per.sno: 109, student.per.age: 22,错乱了


将struct person作为struct student的第三个成员,其他代码保持不变:

 struct student {

      int val1;

      int val2;

      struct person per;

};

编译运行后的输出结果为:student.val1: 100201, student.per.sno: 32, student.per.age: 135137,错乱了。


0 0
原创粉丝点击