Android系统的HAL层分析 (1)-- HAL的架构分析

说明

  1. 在Android系统中有一个很特殊的HAL层,它的作用是什么呢?
  2. HAL是Android底层开发绕不开的Android独有机制,是操作系统上层和硬件驱动沟通的桥梁,通过这一系列的文章,你将对HAL有深入的理解。
  3. 后续我会以我的一部Android 7.1.1 Nexus 6P手机分析:Vibrator震动器子系统、Lingts光子系统、Power电池子系统、Sensor传感器子系统、gps子系统、camera子系统、audio子系统等HAL相关子系统。

1. Android系统内为何会要有HAL ?

  想必在了解Android系统之前,都会听说Android系统是一个开放的操作系统,那么不禁要问,这“开放”和Linux的“开源”的区别是啥?
  HAL层又叫硬件抽象层。它在Android体系中有着重要的意义,因为Android究竟是完全开源的还是完全不开源的秘密就在这一层。Google 将硬件厂商的驱动程序放在这一层。正是因为这层的代码没有开源,所以Android才被Linux家族删除。
  在Android系统中,HAL层是为保护一些硬件提供商的知识产权而提出的,位于Linux内核层之上。HAL实现了硬件抽象化,将硬件平台的差异隐藏,为上层提供统一的硬件平台,从而加快开发人员在不同的硬件平台上进行代码移植。
  就驱动开发的角度而言,HAL将Android框架与Linux内核隔离。主要原因有两方面:由于Linux内核要遵循GPL,根据此许可证,如果对Linux内核源码进行改动就必须公开其源码。如果Android系统的硬件驱动都在Linux的驱动模块中实现,就要将驱动源码完全公开。但是硬件设备商并不愿意将具体的细节公开,此类做法有损其利益。因此,Android系统源码采用Apache License2许可,它允许各厂家对Android系统源码进行改动但不公开。另一方面是因为Android对某些硬件有特殊的要求并没有标准的 Linux接口。
  Android的HAL层考虑Linux系统的设计要求。由于Linux对硬件的支持实现不能完全在用户空间,只有内核空间才具有特权对硬件设备进行操作。基于上述因素,Android驱动将对硬件的支持放在内核空间与用户空间,内核空间使用Linux驱动模块,但只提供硬件访问通道,而用户空间则以HAL模块的方式,在HAL层对硬件细节与参数进行封装,既实现Linux系统对代码公开的要求,也保护移动设备厂家各自的利益。

在Android系统中可以分为如下 6 种HAL:

  • 上层软件。
  • 内部以太网。
  • 内部通信CLIENT。
  • 用户接入口。
  • 虚拟驱动,设置管理模块
  • 内部通信SERVER。

在Android系统中,定义硬件抽象层接口的代码具有以下5个特点:

  • 硬件抽象层具有与硬件密切的相关性。
  • 硬件抽象层具有与操作系统无关性。
  • 接口定义的功能应包含硬件或系统所需硬件支持的所有功能。
  • 接口定义简单明了,太多接口函数会增加软件模拟的复杂性。
  • 具有可测性的接口设计有利于系统的软硬件测试和集成。

在Android 源码中,HAL 主要被保存在源码hardware目录中:
在这里插入图片描述

  • libhardware_legacy:过去的目录,采取了链接库模块观念来架构。
  • libhardware:新版的目录,被调整为用HAL stub观念来架构。
  • ril:是Radio接口层。
  • 其余目录为一些厂商定制的硬件HAL。

2. HAL_legacy和HAL对比

从Android源码上分析,HAL层主要对应的源码目录如下:

  • libhardware_legacy:过去的目录,采取了链接库模块观念来架构。
  • libhardware:新版的目录,被调整为用HAL stub观念来架构。

  libhardware_legacy 架构中HAL层采用的是共享库的方式,也就是直接函数调用的方式使用HAL层的module,这种调用方式没有经过封装,上层直接操作底层的硬件设备,在编译时会调用到。但其明显的缺点是接口不统一,如果出现多个进程同时调用,由于需要对各进程空间进行映射会造成存储空间的浪费,同时存在代码的安全输人问题,容易造成系统崩溃。
  libhardware架构中HAL层使用的是HAL module和HAL stub结合形式,Stub以动态链接库的方式存在。就操作而言,HAL层实现对库的隐藏。Android系统的HAL层包含很多的Stub,采用统一的调用方式对硬件进行操作。上层在HAL层调用Stub的函数,然后再回调这些操作函数。通过这样的模式,实现接口统一,而且各Stub没有关系,上层若要对某个硬件设备进行操作只要提供模块ID,就能对相关设备进行操作。HAL stub不是一个共享库,在编译时上层只拥有访问 HAL stub 的函数指针,并不需要 HAL stub。在上层通过HAL module 提供的统一接口获取并操作 HAL stub,所以文件只会被映射到一个进程而不会存在重复映射和重入问题。
  从现在HAL层的结构可以看出,当前的HAL stub模式是一种代理人 (proxy)的概念,虽然stub仍以*.so文件的形式存在,但是HAL已经将*.so文件隐藏了。stub向HAL提供了功能强大的操作函数(Operations),而Runtime则从HAL获取特定模块(stub)的函数,然后再回调这些操作函数。这种以Indirect Function Call模式的架构,让HAL stub变成了一种“包含”关系也就是说在HAL里面包含了许多 stub(代理人)。Runtime只要说明module ID(类型)就可以取得操作函数。在当前的HAL模式中,Android 定义了HAL层结构框架,这样通过接口访问硬件时就形成了统一的调用方式。下图直观展示旧架构和新架构的不同之处:
在这里插入图片描述

  HAL stub不是一个共享库,在编译时上层只拥有访问 HAL stub 的函数指针,并不需要 HAL stub。在上层通过HAL module 提供的统一接口获取并操作 HAL stub,所以文件只会被映射到一个进程而不会存在重复映射和重入问题。

3. HAL module的架构分析

  Android 7的HAL采用HAL module和HAL stub结合的形式进行架构,HAL stub不是一个ShareLibrary(共享程序),在编译时上层只拥有访问HAL stub的函数指针并不需要HAL stub。上层通过HAL module提供的统一接口获取并操作HAL stub,so文件只会被mapping到一个进程,也不存在重复mapping和重入问题。

HAL module架构主要分为以下3个结构体:

  • struct hw_module_t
  • struct hw_module_methods_t
  • struct hw_device_t

  以上3个抽象概念在文件 hardware/libhardware/include/hardware/hardware.h 中进行了具体描述。对于不同的hardware的HAL,对应的lib命名规则是d.variant.so,例如:gralloc.msm7k.so 表示其id是gralloc,qcom是variant。variant的取值范围是在该文件中定义的variant keys对应的值。

3.1 hw_module_t

/**
 * Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
 * and the fields of this data structure must begin with hw_module_t
 * followed by module specific information.
 */
typedef struct hw_module_t {
    /** tag must be initialized to HARDWARE_MODULE_TAG */
    uint32_t tag;

    /**
     * The API version of the implemented module. The module owner is
     * responsible for updating the version when a module interface has
     * changed.
     *
     * The derived modules such as gralloc and audio own and manage this field.
     * The module user must interpret the version field to decide whether or
     * not to inter-operate with the supplied module implementation.
     * For example, SurfaceFlinger is responsible for making sure that
     * it knows how to manage different versions of the gralloc-module API,
     * and AudioFlinger must know how to do the same for audio-module API.
     *
     * The module API version should include a major and a minor component.
     * For example, version 1.0 could be represented as 0x0100. This format
     * implies that versions 0x0100-0x01ff are all API-compatible.
     *
     * In the future, libhardware will expose a hw_get_module_version()
     * (or equivalent) function that will take minimum/maximum supported
     * versions as arguments and would be able to reject modules with
     * versions outside of the supplied range.
     */
    uint16_t module_api_version;
#define version_major module_api_version
    /**
     * version_major/version_minor defines are supplied here for temporary
     * source code compatibility. They will be removed in the next version.
     * ALL clients must convert to the new version format.
     */

    /**
     * The API version of the HAL module interface. This is meant to
     * version the hw_module_t, hw_module_methods_t, and hw_device_t
     * structures and definitions.
     *
     * The HAL interface owns this field. Module users/implementations
     * must NOT rely on this value for version information.
     *
     * Presently, 0 is the only valid value.
     */
    uint16_t hal_api_version;
#define version_minor hal_api_version

    /** Identifier of module */
    const char *id;

    /** Name of this module */
    const char *name;

    /** Author/owner/implementor of the module */
    const char *author;

    /** Modules methods */
    struct hw_module_methods_t* methods;

    /** module's dso */
    void* dso;

#ifdef __LP64__
    uint64_t reserved[32-7];
#else
    /** padding to 128 bytes, reserved for future use */
    uint32_t reserved[32-7];
#endif

} hw_module_t;

  在结构体 hw_module_t中,需要注意如下5点:

  1. 在结构体hw_module_t 的定义前面有一段注释,意思是,硬件抽象层中的每一个模块都必须自定义一个硬件抽象层模块结构体,而且它的第一个成员变量的类型必须为 hw_module_t 。
  2. 硬件抽象层中的每一个模块都必须存在一个导出符号HAL_MODULE_IFNO_SYM,即HMI,它指向一个自定义的硬件抽象层模块结构体。后面在分析硬件抽象层模块的加载过程时,将会看到这个导出符号的意义。
  3. 结构体 hw_module_t 的成员变量 tag 的值必须设置为 HARDWARE_MODULE_TAG,即设置为一个常量值(‘H’<<24 | ‘W’<<16 | ‘M’<<8 | ‘T’),用来标志这是一个硬件抽层模块结构体。
  4. 结构体 hw_module_t 的成员量 dso 用来保存加硬抽象层模块后得到的句柄值。前面提到,每一个硬件抽象层模块都对应有一个动态链接库文件。加载硬件抽象层模块的过程实际上就是调用 dlopen() 函数来加载与其对应的动态链接库文件的过程。在调用 dlclose() 函数来卸载这个硬件抽象层模块时,要用到这个句柄值,因此,在加载时需要将其保存起来。
  5. 结构体 hw_module_t的成员变量 methods 定义了一个硬件抽象层模块的操作方法列表,其类型为hw_module_methods_t 。

3.2 hw_module_methods_t

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);

} hw_module_methods_t;

  在结构体 hw_module_methods_t 中只有一个成员,它是一个函数指针,用来打开硬件抽象层模块中的硬件设备。由于一个硬件抽象层模块可能会包含多个硬件设备,因此在调用结构体 hw_module_methods_t 的成员量open,打开一个硬件设备时需要指定其ID。其中:

  • 参数 module :表示要打开的硬件设备所在的模块。
  • 参数 id :表示要打开的硬件设备的ID。
  • 参数 device :是一个输出参数,用来描述一个已经打开的硬件设备。

3.3 hw_device_t

/**
 * Every device data structure must begin with hw_device_t
 * followed by module specific public methods and attributes.
 */
typedef struct hw_device_t {
    /** tag must be initialized to HARDWARE_DEVICE_TAG */
    uint32_t tag;

    /**
     * Version of the module-specific device API. This value is used by
     * the derived-module user to manage different device implementations.
     *
     * The module user is responsible for checking the module_api_version
     * and device version fields to ensure that the user is capable of
     * communicating with the specific module implementation.
     *
     * One module can support multiple devices with different versions. This
     * can be useful when a device interface changes in an incompatible way
     * but it is still necessary to support older implementations at the same
     * time. One such example is the Camera 2.0 API.
     *
     * This field is interpreted by the module user and is ignored by the
     * HAL interface itself.
     */
    uint32_t version;

    /** reference to the module this device belongs to */
    struct hw_module_t* module;

    /** padding reserved for future use */
#ifdef __LP64__
    uint64_t reserved[12];
#else
    uint32_t reserved[12];
#endif

    /** Close this device */
    int (*close)(struct hw_device_t* device);

} hw_device_t;

  在结构体hw_device_t中,需要注意如下3点:

  1. 硬件抽象层模块中的每一个硬件设备都必须自定义一个硬件设备结构体,而且它的第一个成员变量的类型必须为 hw_device_t 。
  2. 结构体 hw_device_t 的成员变量 tag 的值必须设置为HARDWARE_DEVICE_TAG,即设置为个常量值(‘H’<<24 | ‘W’<<16 | 'D<<8 | ‘T’),用来标志这是一个硬件抽象层中的硬件设备结构体。
  3. 结构体 hw_device_t 的成员变量 close 是一个函数指针,用来关闭一个硬件设备。

4. 分析hardware.c

可以在系统源码中查看 hardware/libhardware/hardware.c源码内容为:

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <hardware/hardware.h>

#include <cutils/properties.h>

#include <dlfcn.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <limits.h>

#define LOG_TAG "HAL"
#include <utils/Log.h>

/** Base path of the hal modules */
#if defined(__LP64__)
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib64/hw"
#else
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib/hw"
#endif

/**
 * There are a set of variant filename for modules. The form of the filename
 * is "<MODULE_ID>.variant.so" so for the led module the Dream variants 
 * of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
 *
 * led.trout.so
 * led.msm7k.so
 * led.ARMV6.so
 * led.default.so
 */

static const char *variant_keys[] = {
    "ro.hardware",  /* This goes first so that it can pick up a different
                       file on the emulator. */
    "ro.product.board",
    "ro.board.platform",
    "ro.arch"
};

static const int HAL_VARIANT_KEYS_COUNT =
    (sizeof(variant_keys)/sizeof(variant_keys[0]));

/**
 * Load the file defined by the variant and if successful
 * return the dlopen handle and the hmi.
 * @return 0 = success, !0 = failure.
 */
static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
{
    int status = -EINVAL;
    void *handle = NULL;
    struct hw_module_t *hmi = NULL;

    /*
     * 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();
        ALOGE("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 = (struct hw_module_t *)dlsym(handle, sym);
    if (hmi == NULL) {
        ALOGE("load: couldn't find symbol %s", sym);
        status = -EINVAL;
        goto done;
    }

    /* Check that the id matches */
    if (strcmp(id, hmi->id) != 0) {
        ALOGE("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 {
        ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
                id, path, *pHmi, handle);
    }

    *pHmi = hmi;

    return status;
}

/*
 * Check if a HAL with given name and subname exists, if so return 0, otherwise
 * otherwise return negative.  On success path will contain the path to the HAL.
 */
static int hw_module_exists(char *path, size_t path_len, const char *name,
                            const char *subname)
{
    snprintf(path, path_len, "%s/%s.%s.so",
             HAL_LIBRARY_PATH3, name, subname);
    if (access(path, R_OK) == 0)
        return 0;

    snprintf(path, path_len, "%s/%s.%s.so",
             HAL_LIBRARY_PATH2, name, subname);
    if (access(path, R_OK) == 0)
        return 0;

    snprintf(path, path_len, "%s/%s.%s.so",
             HAL_LIBRARY_PATH1, name, subname);
    if (access(path, R_OK) == 0)
        return 0;

    return -ENOENT;
}

int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t **module)
{
    int i = 0;
    char prop[PATH_MAX] = {0};
    char path[PATH_MAX] = {0};
    char name[PATH_MAX] = {0};
    char prop_name[PATH_MAX] = {0};


    if (inst)
        snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
    else
        strlcpy(name, class_id, PATH_MAX);

    /*
     * Here we rely on the fact that calling dlopen multiple times on
     * the same .so will simply increment a refcount (and not load
     * a new copy of the library).
     * We also assume that dlopen() is thread-safe.
     */

    /* First try a property specific to the class and possibly instance */
    snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
    if (property_get(prop_name, prop, NULL) > 0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

    /* Loop through the configuration variants looking for a module */
    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

    /* Nothing found, try the default */
    if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
        goto found;
    }

    return -ENOENT;

found:
    /* load the module, if this fails, we're doomed, and we should not try
     * to load a different variant. */
    return load(class_id, path, module);
}

int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}

4.1 查找动态链接库的地址

  函数hw_get_module()能够根据模块 ID 寻找硬件模块动态链接的地址,然后调用函数load()打开动态链接库,并从中获取硬件模块结构体地址。执行后首先得到是根据固定的符号 HAL_MODULE_INFO_SYM寻找到结构体 hw_module_t,然后是用 hw_moule_t 中hw_module_methods_t 结构体成员函数提供的结构 open 打开相应的模块,并同时进行初始化操作。因为用户在调用 open()时通常都会传入个指向 hw_device_t 指针的指针这样函数 open()将对模块的操作函数结构保存到结构体 hw_device_t 中,用户通过它可以和模块进行交互。

4.2 数组variant_keys

  在函数hw_get_module()中需要用到数组 variant_keys,因为HAL_VARIANT_KEYS_COUNT表示数组variant_keys的大小。定义数组variant keys 的代码如下所示:

static const char *variant_keys[] = {
    "ro.hardware",  /* This goes first so that it can pick up a different
                       file on the emulator. */
    "ro.product.board",
    "ro.board.platform",
    "ro.arch"
};

通过此数组,使用如下代码得到操作权限。

    /* Loop through the configuration variants looking for a module */
    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
        if (property_get(variant_keys[i], prop, NULL) == 0) {
            continue;
        }
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

再通过如下代码将路径和文件名保存到path,把HAL_LIBRARY_PATH/id.xxxso保存到path中其中“xxx”就是上面variant_keys中各个元素所对应的值。

    /* Nothing found, try the default */
    if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
        goto found;
    }

4.3 载入相应的库

载入相应的库,并把它们的HMI保存到module中。具体代码如下所示:

......
    return -ENOENT;

found:
    /* load the module, if this fails, we're doomed, and we should not try
     * to load a different variant. */
    return load(class_id, path, module);
}

4.4 获得hw_module_t结构体

通过函数load0打开相应的库并获得 hw_module t结构体,具体实现代码如下所示。

/**
 * Load the file defined by the variant and if successful
 * return the dlopen handle and the hmi.
 * @return 0 = success, !0 = failure.
 */
static int load(const char *id,
        const char *path,
        const struct hw_module_t **pHmi)
{
    int status = -EINVAL;
    void *handle = NULL;
    struct hw_module_t *hmi = NULL;

    /*
     * 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();
        ALOGE("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 = (struct hw_module_t *)dlsym(handle, sym); //获得hw_module_t结构体
    if (hmi == NULL) {
        ALOGE("load: couldn't find symbol %s", sym);
        status = -EINVAL;
        goto done;
    }

    /* Check that the id matches */
    if (strcmp(id, hmi->id) != 0) { //只是一个check
        ALOGE("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 {
        ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
                id, path, *pHmi, handle);
    }

    *pHmi = hmi; //得到hw_module_t结构体

    return status;
}

5. 分析HAL的加载过程

每一个硬件抽象层模块在内核中都对应一个驱动程序,硬件抽象层模块就是通过这些驱动程序来访问硬件设备的,它们是通过读写设备文件来进行通信的。硬件抽象层中的模块接口源文件一般保存在hardware/libhardware目录中,其目录结构如图所示:
在这里插入图片描述
  Android系统中的硬件抽象层模块是由系统统一加载的,当调用者需要加载这些模块时,只要指定它们的ID值就可以了。在Androd 硬件抽象层中负责加硬件抽象层模块的函数是 hw_get_module()。此函数有 id 和 module 两个参数。

  • id 是输入参数,表示要加的硬件抽象层模块 ID。
  • module 是输出参数,如果加成功,那么它指向一个自定义的硬件抽象层模块结构体。
  • 返回值是一个整数,如果=0,则表示加载成功;如果<0,则表示加载失败。
int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}

  其中数组 variant_keys 用来组装要加载的硬件抽象层模块的文件名称常量HAL_VARIANT_KEYS_COUNT表示数组大小。宏HAL_LIBRARY_PATH1、HAL_LIBRARY_PATH2和HAL_LIBRARY_PATH3 用来定义要加的硬件抽象层模块文件所在的目录。for 循环根据数组 variant_keys HAL_LIBRARY_PATH1/2/3目录中检查对应的硬件抽象层模块文件是否存在,如果存在则结束 for 循环调用 load()函数来执行加载硬件抽象层模块的操作。
  编译好的模块文件位于 out/target/product/generic/system/lib/hw 目录中,而这个目录经过打包后,就对应于设备上的/system/lib/hw目录。
  调用函数load()执行硬件象层模块的加载操作,调用函数 dlopen()将其加载到内存中。加载完成这个动态链接库文件之后调用函数 dlsym()来获得里面名称为 HAL_MODULE_INFO_SYM_AS_STR 的符号。这个HAL_MODULE_INFO_SYM_AS_STR 符号指向的是一个自定义的硬件抽象层模块结构体包含了对应的硬件抽象层模块的所有信息。HAL_MODULE_INFO_SYM_AS_STR 是一个宏,其值定义为HMI根据硬件抽象层模块的编写规范,每一个硬件抽象层模块都必须包含一个名称为HMI的符号,而且这个符号的第一个成员变量的类型必须定义为hw_module_t,因此可以安全地将模块中的HMI符号转换为一个 hw_module_t 结体指针。获得了这个hw_module_t 结体指针之后调用strcmp()函数来验证加载得到的硬件抽象层模块ID是否与所要求加的硬件抽象层模块ID一致。如果不一致,则说明出错了,函数返回一个错误值-EINVAL。最后,将成功加后得到的模块句柄值handle保存在 hw_module_t 结构体指针 hmi 的成员变量 dso 中,然后将其返回给调用者。

6. 分析硬件访问服务

  当开发好硬件抽象层模块之后,通常还需要在应用程序框架层中实现一个硬件访问服务。硬件访问服务通过硬件抽象层模块来为应用程序提供硬件读写操作。由于硬件抽象层模块是使用C++语言开发的,而应用程序框架层中的硬件访问服务是使用Java 语言开发的,因此,硬件访问服务必须通过 JNI来调用硬件抽象层模块的接口。

6.1 定义硬件访问服务接口

  Android 系统的硬件访问服务通常运行在 SystemServer 中,而使用这些硬件访问服务的应用程序运行在另外的进程中,即应用程序需要通过进程间通信机制来访问这些硬件访问服务。Android 系统提供了一种高效的进程间通信机制——Binder 进程间通信机制,应用程序就是通过它来访问运行在 SystemServer 中的硬件访问服务的。Binder 进程间通信机制要求提供服务的一方必须实现一个具有跨进程访问能力的服务接口,以便使用服务的一方可以通过这个服务接口来访问它。因此,在实现硬件访问服务之前,首先要定义它的服务接口。
  在Android 7系统中,提供了一种描述语言来定义具有跨进程访问能力的服务接口,这种描述语言称为Android接口描述语言 (Android Interface Definition Language,AIDL)。AIDL定义的服务接口文件是以 aidl 为后缀名的,在编译时,编译系统会将它们转换成一个 Java 文件,然后再对它们进行编译。本节将使用AIDL来定义硬件访问服务接口 ILedService 。在Android系统中,通常在frameworks/base/core/java/android/os目录中定义硬件访问服务接口,所以把定义了硬件访问服务接口 ILedService 的文件 ILedService.aidl 也保存在这个目录中,其具体代码如下所示:

package mokoid.hardware;

interface ILedService
{
    boolean setOn(int led);
    boolean setOff(int led);
}

  服务接口 ILedService 只定义了两个成员函数,分别是 setOn()和 setOff()。由于服务接口 ILedService 是使用AIDL语言描述的,因此需要将其添加到编译脚本文件中,这样编译系统才能将其转换为Java文件,然后再对它进行编译。进入到 frameworks/base 目录中打开里面的Android.mk 文件,修改LOCAL_SRC_FILES 变量的值:

LOCAL_SRC_FILES += \
	......
	core/java/android/os/ILedService.aidl \
	......

  修改这个编译脚本文件之后,编译系统镜像后得到的framework.jar 文件就包含有ILedService 接口,它继承了android.os.IInterface 接口。在ILedService 接口内部,定义了一个 Binder 本地对象类 Stub,它实现了 ILedService 接口,并且继承了android.os.Binder 类。此外,在ILedServiceStub 类内部,还定义了一个 Binder 代理对象类 Proxy;它同样也实现了 ILedService 接口。
  用 AIDL 定义的服务接口是用来进行进程间通信的,其中,提供服务的进程称为Server进程,而使用服务的进程称为Client进程。在Server进程中,每一个服务都对应有一个Binder 本地对象,它通过一个桩(Stub)来等待Client 进程发送进程间通信请求。Client 进程在访问运行Server 进程中的服务之前,首先要获得它的一个Binder 代理对象接口(Proxy),然后通过这个Binder 代理对象接口向它发送进程间通信请求。

6.2 具体实现

  在Android 系统中,通常通过 framcworks/base/services/java/com/android/server 目录中的文件实现硬件访问服务。因此把实现了硬件访问服务 ILedService 的文件 LedService.java也保存在这个目录中,其具体内容如下所示:

package com.mokoid.server;

import android.util.Config;
import android.util.Log;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.IBinder;
import mokoid.hardware.ILedService;

public final class LedService extends ILedService.Stub {    

    static {
        System.load("/system/lib/libmokoid_runtime.so");
    }

    public LedService() {
        Log.i("LedService", "Go to get LED Stub...");
	_init();
    }

    /*
     * Mokoid LED native methods.
     */
    public boolean setOn(int led) {
        Log.i("MokoidPlatform", "LED On");
	return _set_on(led);
    }

    public boolean setOff(int led) {
        Log.i("MokoidPlatform", "LED Off");
	return _set_off(led);
    }

    private static native boolean _init();
    private static native boolean _set_on(int led);
    private static native boolean _set_off(int led);
}

  在上述代码中,硬件访问服务 LedService 继承了类ILedService.Stub,并且实现了 ILedService 接口的成员函数 setOn()和 setOff() 。其中,成员函数 setOn()通过调用JNI方法 _set_on() 来操作硬件,而成员函数 setOff()调用JI 方法_set_off() 来操作硬件。在启动硬件访问服务 LedService 时,会通过调用JNI函数 _init()来初始化设备 led 。

7. 官方实例mokoid分析

  谷歌针对 HAL 提供了一个官方实例工程:mokoid,在此工程中提供了一个 LedTest 演示程序,此程序实例完整演示了Android 层次结构和 HAL架构编程的方法和流程。

7.1 获取实例工程源码

  工程源码可从这里下载:Android-HAL-Study

├── Android.mk
├── apps
│   ├── Android.mk
│   ├── LedClient  #直接调用Service控制硬件
│   │   ├── AndroidManifest.xml
│   │   ├── Android.mk
│   │   └── src
│   │       └── com
│   │           └── mokoid
│   │               └── LedClient
│   │                   └── LedClient.java
│   └── LedTest  #通过manager控制硬件
│       ├── AndroidManifest.xml
│       ├── Android.mk
│       └── src
│           └── com
│               └── mokoid
│                   └── LedTest
│                       ├── LedSystemServer.java
│                       └── LedTest.java
├── dma6410xp
│   ├── AndroidBoard.mk
│   ├── AndroidProducts.mk
│   ├── BoardConfig.mk
│   ├── dma6410xp.mk
│   ├── init.dma6410xp.rc
│   ├── init.goldfish.sh
│   └── init.rc
├── frameworks
│   ├── Android.mk
│   └── base
│       ├── Android.mk
│       ├── core
│       │   └── java
│       │       └── mokoid
│       │           └── hardware
│       │               ├── ILedService.aidl  #AIDL代码,提供LedService的接口
│       │               └── LedManager.java  #LedManager实现代码
│       └── service
│           ├── Android.mk
│           ├── com.mokoid.server.xml
│           ├── java
│           │   └── com
│           │       └── mokoid
│           │           └── server
│           │               └── LedService.java  #LedService的java实现代码
│           └── jni
│               ├── Android.mk
│               └── com_mokoid_server_LedService.cpp  #LedService的jni实现代码
├── hardware
│   ├── Android.mk
│   ├── libled
│   │   ├── Android.mk
│   │   └── libled.c
│   └── modules
│       ├── Android.mk
│       ├── include
│       │   └── mokoid
│       │       └── led.h
│       └── led
│           ├── Android.mk
│           └── led.c  #led实际控制硬件的代码
└── README.txt

  在Android 系统中需要通过JNI(Java Native Interface实现 HAL,因为JNI是Java 程序可以调用C/C++编写的动态链接库,所以可以使用 C/C++ 编写 HAL,这样做的好处是拥有更高的效率。在Android系统中有如下两种访问HAL的方式:

  1. Android APP 直接通过 service 调用.so 格式的JNI,虽然这种方式比较简单高效,但是不正规。
  2. 经过 Manager 用 Service,虽然此方式实现起来比较复杂,但是更符合目前的 Android 框架在此方法中,在进程 LegManager 和 LedService (Java)中需要通过进程通信的方式实现通信。

7.2 直接调用service()方法的实现代码

7.2.1 HAL层的实现代码

代码位置:mokoid/hardware/modules/led/led.c

#define LOG_TAG "MokoidLedStub"

#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <mokoid/led.h>
/*****************************************************************************/
int led_device_close(struct hw_device_t* device)
{
	struct led_control_device_t* ctx = (struct led_control_device_t*)device;
	if (ctx) {
		free(ctx);
	}
	return 0;
}

int led_on(struct led_control_device_t *dev, int32_t led)
{
	LOGI("LED Stub: set %d on.", led);

	return 0;
}

int led_off(struct led_control_device_t *dev, int32_t led)
{
	LOGI("LED Stub: set %d off.", led);

	return 0;
}

static 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;//将实例化的led_control_device_t 地址返回给JNI层。

success:
	return 0;
}

static struct hw_module_methods_t led_module_methods = {
    open: led_device_open
};

const struct led_module_t HAL_MODULE_INFO_SYM = {
	//定义此对象相当于向系统注册了一个ID为LED_HARDWARE_MODULE_ID的stub
    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, //实现一个open方法供JNI层调用
    }
    /* supporting APIs go here */
};

7.2.2 JNI层的实现代码

代码位置:mokoid/frameworks/base/service/jni/com_mokoid_server_LedService.cpp

#define LOG_TAG "MokoidPlatform"

#include "utils/Log.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

#include <jni.h>
#include <mokoid/led.h>
// ----------------------------------------------------------------------------
struct led_control_device_t *sLedDevice = NULL;

static jboolean mokoid_setOn(JNIEnv* env, jobject thiz, jint led) {

    LOGI("LedService JNI: mokoid_setOn() is invoked.");

    if (sLedDevice == NULL) {
        LOGI("LedService JNI: sLedDevice was not fetched correctly.");
        return -1;
    } else {
        return sLedDevice->set_on(sLedDevice, led);//调用HAL层的注册方法。
    }
}

static jboolean mokoid_setOff(JNIEnv* env, jobject thiz, jint led) {

    LOGI("LedService JNI: mokoid_setOff() is invoked.");

    if (sLedDevice == NULL) {
        LOGI("LedService JNI: sLedDevice was not fetched correctly.");
        return -1;
    } else {
        return sLedDevice->set_on(sLedDevice, led);//调用HAL层的注册方法。
    }
}

/** helper APIs JNI通过 LED_HARDWARE_MODULE_ID 找到对应的stub */
static inline int led_control_open(const struct hw_module_t* module,
        struct led_control_device_t** device) {
    return module->methods->open(module,
            LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}

static jboolean
mokoid_init(JNIEnv *env, jclass clazz)
{
    led_module_t* module;

    if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
        LOGI("LedService JNI: LED Stub found.");
        if (led_control_open(&module->common, &sLedDevice) == 0) {
    	    LOGI("LedService JNI: Got Stub operations.");
            return 0;
        }
    }

    LOGE("LedService JNI: Get Stub operations failed.");
    return -1;
}

// ----------------------------------------------------------------------------

/*
 * Array of methods.
 * JNINativeMethod 是JNI层的注册方法
 */
static const JNINativeMethod gMethods[] = {
    {"_init",	  	"()Z",
			(void*)mokoid_init},
    { "_set_on",          "(I)Z",
                        (void*)mokoid_setOn },
    { "_set_off",          "(I)Z",
                        (void*)mokoid_setOff },
};

static int registerMethods(JNIEnv* env) {
    static const char* const kClassName =
        "com/mokoid/server/LedService";
    jclass clazz;

    /* look up the class */
    clazz = env->FindClass(kClassName);
    if (clazz == NULL) {
        LOGE("Can't find class %s\n", kClassName);
        return -1;
    }

    /* register all the methods */
    if (env->RegisterNatives(clazz, gMethods,
            sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
    {
        LOGE("Failed registering methods for %s\n", kClassName);
        return -1;
    }

    /* fill out the rest of the ID cache */
    return 0;
}

// ----------------------------------------------------------------------------

/*
 * Framework层加载JNI库时调用
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (registerMethods(env) != 0) {
        LOGE("ERROR: PlatformLibrary native registration failed\n");
        goto bail;
    }

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

7.2.3 Service的实现代码

代码位置:mokoid/frameworks/base/service/java/com/mokoid/server/LedService.java

package com.mokoid.server;

import android.util.Config;
import android.util.Log;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.IBinder;
import mokoid.hardware.ILedService;

public final class LedService extends ILedService.Stub {    

    static {
        System.load("/system/lib/libmokoid_runtime.so"); //加载JNI动态库
    }

    public LedService() {
        Log.i("LedService", "Go to get LED Stub...");
	_init();
    }

    /*
     * Mokoid LED native methods.
     */
    public boolean setOn(int led) {
        Log.i("MokoidPlatform", "LED On");
	return _set_on(led);
    }

    public boolean setOff(int led) {
        Log.i("MokoidPlatform", "LED Off");
	return _set_off(led);
    }
	
	//声明JNI动态库可以提供的方法
    private static native boolean _init();
    private static native boolean _set_on(int led);
    private static native boolean _set_off(int led);
}

7.2.4 编写测试APP

代码位置:mokoid/apps/LedClient/src/com/mokoid/LedClient/LedClient.java

package com.mokoid.LedClient;
import com.mokoid.server.LedService;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class LedClient extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Call an API on the library.
	LedService ls = new LedService();
	ls.setOn(1);
        
        TextView tv = new TextView(this);
        tv.setText("LED 0 is on.");
        setContentView(tv);
    }
}

7.3 通过Manager调用service的实现代码

7.3.1 实现Manager

代码位置:mokoid/frameworks/base/core/java/mokoid/hardware/LedManager.java

package mokoid.hardware;

import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.util.Log;
import mokoid.hardware.ILedService;

/**
 * Class that lets you access the Mokoid LedService.
 */
public class LedManager
{
    private static final String TAG = "LedManager";
    private ILedService mLedService;

    public LedManager() {
	
        mLedService = ILedService.Stub.asInterface(
                             ServiceManager.getService("led"));

	if (mLedService != null) {
            Log.i(TAG, "The LedManager object is ready.");
	}
    }

    public boolean LedOn(int n) {
        boolean result = false;

        try {
            result = mLedService.setOn(n);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in LedManager.LedOn:", e);
        }
        return result;
    }

    public boolean LedOff(int n) {
        boolean result = false;

        try {
            result = mLedService.setOff(n);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in LedManager.LedOff:", e);
        }
        return result;
    }
}

因为LedService和LedManager分别属于不同的进程,所以在此需要考虑不同进程之间的通信问题。此时在 Manager 中可以增加一个AIDL 文件来描述通信接口,代码位置:mokoid/frameworks/base/core/java/mokoid/hardware/ILedService.aidl

package mokoid.hardware;

interface ILedService
{
    boolean setOn(int led);
    boolean setOff(int led);
}

7.3.2 实现SystemServer

SystemServer属于APP层,代码位置:mokoid/apps/LedTest/src/com/mokoid/LedTest/LedSystemServer.java

package com.mokoid.LedTest;

import com.mokoid.server.LedService;

import android.os.IBinder;
import android.os.ServiceManager;
import android.util.Log;
import android.app.Service;
import android.content.Context;
import android.content.Intent;

public class LedSystemServer extends Service {
	//代表一个后台进程
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void onStart(Intent intent, int startId) {
        Log.i("LedSystemServer", "Start LedService...");

	/* Please also see SystemServer.java for your interests. */
	LedService ls = new LedService();

        try {
            ServiceManager.addService("led", ls);
        } catch (RuntimeException e) {
            Log.e("LedSystemServer", "Start LedService failed.");
        }
    }
}

7.3.3 编写测试APP

代码位置:mokoid/apps/LedTest/src/com/mokoid/LedTest/LedTest.java

package com.mokoid.LedTest;
import mokoid.hardware.LedManager;
import com.mokoid.server.LedService;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Button;
import android.content.Intent;
import android.view.View;

public class LedTest extends Activity implements View.OnClickListener {
    private LedManager mLedManager = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Start LedService in a seperated process.
        startService(new Intent("com.mokoid.systemserver"));

	// Just for testing. !! PLEASE DON't DO THIS !!
	//LedService ls = new LedService();

        Button btn = new Button(this);
        btn.setText("Click to turn LED 1 On");
	btn.setOnClickListener(this);

        setContentView(btn);
    }

    public void onClick(View v) {

        // Get LedManager.
        if (mLedManager == null) {
	    Log.i("LedTest", "Creat a new LedManager object.");
	    mLedManager = new LedManager();
        }

        if (mLedManager != null) {
	    Log.i("LedTest", "Got LedManager object.");
	}

        /** Call methods in LedService via proxy object 
         * which is provided by LedManager. 
         */
        mLedManager.LedOn(1);

        TextView tv = new TextView(this);
        tv.setText("LED 1 is On.");
        setContentView(tv);
    }
}
  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小馬佩德罗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值