Chinaunix首页 | 论坛 | 博客
  • 博客访问: 465687
  • 博文数量: 85
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 32
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-13 13:49
文章分类

全部博文(85)

文章存档

2018年(1)

2014年(40)

2013年(44)

分类: Android平台

2013-06-13 21:19:22

原文地址:Android HAL 分析 作者:myfaxmail

1 HAL简介
      Android 的 HAL(Hardware Abstract Layer硬件抽象层)是Google因应厂商「希望不公开源码」的要求下,所推出的新观念,其架构如下图。虽然 HAL 现在的「抽象程度」还不足,现阶段实作还不是全面符合 HAL的架构规划,不过也确实给了我们很好的思考空间。

Android HAL 分析 - Once - 许兴旺的博客
                                        图1:Android HAL 架构规划


       这是 Patrick Brady (Google) 在2008 Google I/O 所发表的演讲「Anatomy & Physiology of an Android」中,所提出的 Android HAL 架构图。从这张架构图我们知道,HAL 的目的是为了把 Android framework 与 Linux kernel 完整「隔开」。让 Android 不至过度依赖 Linux kernel,有点像是「kernel independent」的意思,让 Android framework 的开发能在不考虑驱动程序的前提下进行发展。

在 Android 原始码里,HAL 主要的实作储存于以下目录:

    1. libhardware_legacy/ - 过去的实作、采取链接库模块的观念进行
    2. libhardware/ - 新版的实作、调整为 HAL stub 的观念
    3. ril/ - Radio Interface Layer

     在 HAL 的架构实作成熟前(即图1的规划),我们先就目前 HAL 现况做一个简单的分析。另外,目前 Android 的 HAL实作,仍旧散布在不同的地方,例如 Camera、WiFi 等,因此上述的目录并不包含所有的 HAL 程序代码。

2 HAL 的过去

Android HAL 分析 - Once - 
许兴旺的博客

图2:Android HAL / libhardware_legacy

       过去的 libhardware_legacy 作法,比较是传统的「module」方式,也就是将 *.so 档案当做「shared library」来使用,在runtime(JNI 部份)以 direct function call 使用 HAL module。透过直接函数呼叫的方式,来操作驱动程序。当然,应用程序也可以不需要透过 JNI 的方式进行,直接以加载 *.so 檔(dlopen)的做法呼叫*.so 里的符号(symbol)也是一种方式。总而言之是没有经过封装,上层可以直接操作硬件。

3 HAL 的现况

Android HAL 分析 - Once - 
许兴旺的博客

图3:Android HAL / libhardware

       现 在的 libhardware 作法,就有「stub」的味道了。HAL stub 是一种代理人(proxy)的概念,stub 虽然仍是以 *.so檔的形式存在,但 HAL 已经将 *.so 档隐藏起来了。Stub 向 HAL「提供」操作函数(operations),而 runtime 则是向 HAL 取得特定模块(stub)的 operations,再 callback 这些操作函数。这种以 indirect function call 的实作架构,让HAL stub 变成是一种「包含」关系,即 HAL 里包含了许许多多的 stub(代理人)。Runtime 只要说明「类型」,即 module ID,就可以取得操作函数。对于目前的HAL,可以认为Android定义了HAL层结构框架,通过几个接口访问硬件从而统一了调用方式。

4 HAL_legacy和HAL的对比
         HAL_legacy:旧式的HAL是一个模块,采用 共享库形式,在编译时会调用到。由于采用function
call形式调用,因此可被多个进程使用,但会被mapping到多个进程空间中,造 成浪费,同时需要考虑代码能否安全重入的问题(thread safe)。
HAL:新式的HAL采用HAL module和HAL stub结合形式,HAL stub不是一个share library,编译时上层只拥有访问HAL stub的函数指针,并不需要HAL stub。上层通过HAL module提供的统一接口获取并操作HAL stub,so文件只会被mapping到一个进程,也不存在重复mapping和重入问题。
5 HAL module架构
HAL moudle主要分为三个结构:
struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;
他 们的继承关系如下图:

Android HAL 分析 - Once - 许兴旺的博客

图4:Android HAL结构继承关系

6 HAL使用方法

(1)Native code通过hw_get_module调 用获取HAL stub:
hw_get_module (LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module)

(2)通过继承hw_module_methods_t的callback来 open设备:
module->methods->open(module,
            LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);

(3)通过继承 hw_device_t的callback来控制设备:
sLedDevice->set_on(sLedDevice, led);
sLedDevice->set_off(sLedDevice, led);


7 HAL stub编写方法

(1)定义自己的HAL结构体,编写头文件led.h, hardware/hardware.h
struct led_module_t {
   struct hw_module_t common;
};

struct led_control_device_t {
   struct hw_device_t common;

   int fd;             /* file descriptor of LED device */

   /* supporting control APIs go here */
   int (*set_on)(struct led_control_device_t *dev, int32_t led);
   int (*set_off)(struct led_control_device_t *dev, int32_t led);
};

继 承关系如下图:

Android HAL 分析 - Once - 许兴旺的博客

图5:HAL stub与HAL module继承关系


(2) 设计led.c 完成功能实现和HAL stub注册

(2.1)led_module_methods继承 hw_module_methods_t,实现open的callback
struct hw_module_methods_t led_module_methods = {
    open: led_device_open
};

(2.2)用 HAL_MODULE_INFO_SYM实例led_module_t,这个名称不可修改
tag:需要制定为 HARDWARE_MODULE_TAG
id:指定为 HAL Stub 的 module ID
methods:struct hw_module_methods_t,为 HAL 所定义的「method」
const struct led_module_t HAL_MODULE_INFO_SYM = {
    common: {
        tag: HARDWARE_MODULE_TAG,
        version_major: 1,
        version_minor: 0,
        id: LED_HARDWARE_MODULE_ID,
        name: "Sample LED Stub",
        author: "The Mokoid Open Source Project",
        methods: &led_module_methods,
    }

    /* supporting APIs go here. */
};

(2.3)open是一个必须实现的callback API,负责申请结构体空间,填充信息,注册具体操作API接口,打开Linux驱动。
       由于存在多重继承关系,只需对子结构体hw_device_t对象申请空间即可。
int led_device_open(const struct hw_module_t* module, const char* name,
        struct hw_device_t** device)
{
       struct led_control_device_t *dev;
       dev = (struct led_control_device_t *)malloc(sizeof(*dev));
       memset(dev, 0, sizeof(*dev));
       dev->common.tag = HARDWARE_DEVICE_TAG;
       dev->common.version = 0;
       dev->common.module = module;
       dev->common.close = led_device_close;
       dev->set_on = led_on;
       dev->set_off = led_off;
       *device = &dev->common;
       /*
         * Initialize Led hardware here.
         */
        dev->fd = open(LED_DEVICE, O_RDONLY);
       if (dev->fd < 0)
           return -1;

       led_off(dev, LED_C608);
       led_off(dev, LED_C609);
success:
       return 0;
}

(2.4)填充具体API操作代码
int led_on(struct led_control_device_t *dev, int32_t led)
{
       int fd;
       LOGI("LED Stub: set %d on.", led);
       fd = dev->fd;
       switch (led) {
              case LED_C608:
                     ioctl(fd, 1, &led);
                     break;
              case LED_C609:
                     ioctl(fd, 1, &led);
                     break;
              default:
                     return -1;
       }
return 0;
}

int led_off(struct led_control_device_t *dev, int32_t led)
{
       int fd;
       LOGI("LED Stub: set %d off.", led);
       fd = dev->fd;
       switch (led) {
              case LED_C608:
                     ioctl(fd, 2, &led);
                     break;
              case LED_C609:
                     ioctl(fd, 2, &led);
                     break;
              default:
                     return -1;
       }
       return 0;
}

 

 

 

 

Android HAL 是如何被调用的

Android 对硬件的调用, google 推荐使用 HAL 的方式进行调用,对于 Andriod HAL 的写法,可以参考 android 源码里的 hardware 目录下几个模块的模版。

在看 HAL 的编写方法的过程中,会发现整个模块貌似没有一个入口。一般说来模块都要有个入口,比如应用程序有 main 函数,可以为加载器进行加载执行, dll 文件有 dllmain ,而对于我们自己写的动态链接库,我们可以对库中导出的任何符号进行调用。

问题来了, Android 中的 HAL 是比较具有通用性的,需要上层的函数对其进行加载调用, Android HAL 加载器是如何实现对不同的 Hardware Module 进行通用性的调用的呢?

带着这个疑问查看 Android 源码,会发现 Android 中实现调用 HAL 是通过 hw_get_module 实现的。

int hw_get_module(const char *id, const struct hw_module_t **module);

这是其函数原型, id 会指定 Hardware id ,这是一个字符串,比如 sensor id

#define SENSORS_HARDWARE_MODULE_ID "sensors" ,如果找到了对应的 hw_module_t 结构体,会将其指针放入 *module 中。看看它的实现。。。。

    /* Loop through the configuration variants looking for a module */

    for (i=0 ; i

        if (i < HAL_VARIANT_KEYS_COUNT) {

             // 获取 ro.hardware/ro.product.board/ro.board.platform/ro.arch key 的值。

            if (property_get(variant_keys[i], prop, NULL) == 0) {

                continue;

            }

            snprintf(path, sizeof(path), "%s/%s.%s.so",

                    HAL_LIBRARY_PATH, id, prop);

              // 如果开发板叫做 mmdroid, 那么这里的 path 就是 system/lib/hw/sensor.mmdroid.so

        } else {

            snprintf(path, sizeof(path), "%s/%s.default.so",

                    HAL_LIBRARY_PATH, id);// 默认会加载 /system/lib/hw/sensor.default.so

 

         }

        if (access(path, R_OK)) {

            continue;

        }

        /* we found a library matching this id/variant */

        break;

    }

    status = -ENOENT;

    if (i < HAL_VARIANT_KEYS_COUNT+1) {

        /* load the module, if this fails, we're doomed, and we should not try

         * to load a different variant. */

        status = load(id, path, module);// 调用 load 函数打开动态链接库

    }

 

 

获取了动态链接库的路径之后,就会调用 load 函数打开它,下面会打开它。

 

奥秘在 load

static int load(const char *id,

        const char *path,

        const struct hw_module_t **pHmi)

{

    int status;

    void *handle;

    struct hw_module_t *hmi;

 

    /*

     * load the symbols resolving undefined symbols before

     * dlopen returns. Since RTLD_GLOBAL is not or'd in with

     * RTLD_NOW the external symbols will not be global

     */

    handle = dlopen(path, RTLD_NOW);// 打开动态库

    if (handle == NULL) {

        char const *err_str = dlerror();

        LOGE("load: module=%s/n%s", path, err_str?err_str:"unknown");

        status = -EINVAL;

        goto done;

    }

 

    /* Get the address of the struct hal_module_info. */

    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;// 被定义为了“ HMI

    hmi = (struct hw_module_t *)dlsym(handle, sym);// 查找“ HMI ”这个导出符号,并获取其地址

    if (hmi == NULL) {

        LOGE("load: couldn't find symbol %s", sym);

        status = -EINVAL;

        goto done;

    }

 

/* Check that the id matches */

// 找到了 hw_module_t 结构!!!

    if (strcmp(id, hmi->id) != 0) {

        LOGE("load: id=%s != hmi->id=%s", id, hmi->id);

        status = -EINVAL;

        goto done;

    }

 

    hmi->dso = handle;

 

    /* success */

    status = 0;

 

    done:

    if (status != 0) {

        hmi = NULL;

        if (handle != NULL) {

            dlclose(handle);

            handle = NULL;

        }

    } else {

        LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",

                id, path, *pHmi, handle);

    }

  // 凯旋而归

    *pHmi = hmi;

 

     return status;

}

从上面的代码中,会发现一个很奇怪的宏 HAL_MODULE_INFO_SYM_AS_STR ,它直接被定义为了 #define HAL_MODULE_INFO_SYM_AS_STR  "HMI" ,为何根据它就能从动态链接库中找到这个 hw_module_t 结构体呢?我们查看一下我们用到的 hal 对应的 so 就可以了,在 linux 中可以使用 readelf XX.so –s 查看。

Symbol table '.dynsym' contains 28 entries:

   Num:     Value  Size Type    Bind   Vis      Ndx Name

     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 00000594     0 SECTION LOCAL  DEFAULT    7

     2: 00001104     0 SECTION LOCAL  DEFAULT   13

     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND ioctl

     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND strerror

     5: 00000b84     0 NOTYPE  GLOBAL DEFAULT  ABS __exidx_end

     6: 00000000     0 OBJECT  GLOBAL DEFAULT  UND __stack_chk_guard

     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0

     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND __errno

     9: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS _bss_end__

    10: 00000000     0 FUNC    GLOBAL DEFAULT  UND malloc

    11: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start__

    12: 00000000     0 FUNC    GLOBAL DEFAULT  UND __android_log_print

    13: 00000b3a     0 NOTYPE  GLOBAL DEFAULT  ABS __exidx_start

    14: 00000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail

    15: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_end__

    16: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start

    17: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset

    18: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_uidiv

    19: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS __end__

    20: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS _edata

    21: 00001188     0 NOTYPE  GLOBAL DEFAULT  ABS _end

    22: 00000000     0 FUNC    GLOBAL DEFAULT  UND open

    23: 00080000     0 NOTYPE  GLOBAL DEFAULT  ABS _stack

    24: 00001104   128 OBJECT  GLOBAL DEFAULT   13 HMI

    25: 00001104     0 NOTYPE  GLOBAL DEFAULT   13 __data_start

    26: 00000000     0 FUNC    GLOBAL DEFAULT  UND close

    27: 00000000     0 FUNC    GLOBAL DEFAULT  UND free

从上面中,第 24 个符号,名字就是“ HMI ”,对应于 hw_module_t 结构体。再去对照一下 HAL 的代码。

/*

  * The COPYBIT Module

  */

struct copybit_module_t HAL_MODULE_INFO_SYM = {

    common: {

        tag: HARDWARE_MODULE_TAG,

        version_major: 1,

        version_minor: 0,

        id: COPYBIT_HARDWARE_MODULE_ID,

        name: "QCT MSM7K COPYBIT Module",

        author: "Google, Inc.",

        methods: ©bit_module_methods

    }

};

这里定义了一个名为 HAL_MODULE_INFO_SYM copybit_module_t 的结构体, common 成员为 hw_module_t 类型。注意这里的 HAL_MODULE_INFO_SYM 变量必须为这个名字,这样编译器才会将这个结构体的导出符号变为“ HMI ”,这样这个结构体才能被 dlsym 函数找到!

综上,我们知道了 andriod HAL 模块也有一个通用的入口地址,这个入口地址就是 HAL_MODULE_INFO_SYM 变量,通过它,我们可以访问到 HAL 模块中的所有想要外部访问到的方法。

阅读(1456) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~