Android HAL机制的深入理解及在Linux上移植和运行的一个好玩的HAL小例子

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  Ubuntu 18.04.x

前言


  近一年来,虽然还是做的是AIOT相关的事情,但是某些事情却发生了一些变化。随着个人的阅历提升,现在的AI在边缘端部署已经不局限于传统的linux这样的形态,这一年来,我已经逐渐接触到android的边缘端盒子这样的概念了。

  对于Android来说,我之前有所了解,但是停留的非常表面,只知道其是一个Linux内核+Android Runtime+app的这样的形态。但是如果我们将Android Runtime和App看作普通的Linux app,那么我们会发现Android和传统Linux的差别没有那么大,我们甚至可以将Android当成一个Linux发行版来使用。但是实际在使用过程中,最大的差异在于Android引入了许多的Android特有的内容,例如binder,log,adb等等,其次和linux下面编程的最大区别还是在于他们的基础c库不一样,一个是bonic c,一个的glibc,这一点可以说是贯穿我在使用Android的整个过程中。

  在使用Android的过程中,我们会听见一个HAL的词,整个HAL可以说是Android能够成功商业化的一个重要因素,因为其可以保护各个厂商的利益,然后反过来,正是由于各个厂家的支持,导致了Android的生态是非常丰富的。那么我们来看看这个HAL到底是干嘛的。

  由于网上有许多介绍HAL的文章了,本文不会重复一些基础的内容,因此本文后续的阅读需要读者至少对Linux和Android HAL有一个基础的了解后,才建议阅读本文。

HAL 深入分析


  首先Android HAL分为大概分为两个版本,一个新的和旧的,本文重点分析新版HAL原理。其中两种版本架构大概简介如下:

  1. 旧的HAL架构(libhardware_legacy.so)每个app都会加载模块,有重入问题,由于每个app直接加载对应的so,也导致app和模块接口耦合较大,非常不方便维护。
  2. 新的HAL架构module/stub,app访问对应硬件对应的服务,然后对应硬件服务通过抽象api,module id,设备id,代理后访问硬件。

   上面对新的架构说的还是有些表面了,下面我们深入分析其中的重要的几个结构(在hardware.h中),然后最后通过一个实际例子来深入理解它。

struct 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代表一个硬件模块,但是一个硬件模块可能包含了很多的硬件设备,所以我们要操作一个实际的硬件设备,按照这套框架,第一件事获取模块,第二件事就是打开设备,第三操作设备,第四关闭设备。

   其实这里的注释说的很清楚,使用它,有两个注意事项,一是必须要在实际模块中定义一个HAL_MODULE_INFO_SYM的结构体变量,且此结构体必须是struct hw_module_t 作为第一个成员变量。这里的根本原因是因为c的结构体内存布局和暴露这个结构体的名字,后面会详细说这个事情。

struct 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;

  此结构体没啥可说的,就是通过实际的模块,然后传入一个硬件设备的id,然后打开实际的硬件设备。因此在每个实际的hw_module_t中,都包含了一个hw_module_methods_t,然后有打开设备的操作。这里也体现出来了一个模块可以有多个设备的这种概念。

struct 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;

  此结构体就是上文我们说的实际打开的设备结构体,一般情况我们会将此结构体暴露到对应hal的头文件中,因为这个包含了实际操作硬件的一些接口信息。注意这个hw_device_t包含了一个close接口,是每个设备的关闭接口。

  注意,我们这个时候没有去说hardware.c所做的事情,也就是如下两个接口到底做了什么,这个问题的解答,我们留到下一小节例子中去深入认识他。

/**
 * Get the module info associated with a module by id.
 *
 * @return: 0 == success, <0 == error and *module == NULL
 */
int hw_get_module(const char *id, const struct hw_module_t **module);

/**
 * Get the module info associated with a module instance by class 'class_id'
 * and instance 'inst'.
 *
 * Some modules types necessitate multiple instances. For example audio supports
 * multiple concurrent interfaces and thus 'audio' is the module class
 * and 'primary' or 'a2dp' are module interfaces. This implies that the files
 * providing these modules would be named audio.primary.<variant>.so and
 * audio.a2dp.<variant>.so
 *
 * @return: 0 == success, <0 == error and *module == NULL
 */
int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t **module);

一个MY_HW的硬件模块的HAL例子


   如上,我们已经介绍了hal里面的重要的3个结构体,但是如果就到此的话,其实我们对hal还是一知半解,这个时候,我们可以尝试自己虚拟一个硬件出来,然后设计HAL接口。这样可以实际体会HAL的工作原理。

   首先,我们定义我们的模块叫做MY_HW。

下面是自定义的hal模块源码

  my_hw_hal.h 源码

#ifndef __MY_HW_HAL_H__
#define __MY_HW_HAL_H__


#include "hardware.h"

#define MY_HW_MODULE_ID "MY_HW"


struct my_hw_device_t{

    struct hw_device_t base;

    int (*set_my_hw_op)(struct my_hw_device_t * dev, int op_type);
};

#endif //__MY_HW_HAL_H__

  my_hw_hal.c 源码

#include "my_hw_hal.h"

#include <stdio.h>
#include <string.h>
int my_hw_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
int set_my_hw_op_device_0 (struct my_hw_device_t * dev, int op_type);
int my_hw_close_device_0(struct hw_device_t* device);

//For hw_moudle_methods_t
static struct hw_module_methods_t my_hw_methods = {

    .open = my_hw_open,
};



//For hw_module_t
struct my_hw_module_t {

    struct hw_module_t base;
};




__attribute__((visibility("default")))  struct  my_hw_module_t  HAL_MODULE_INFO_SYM = {

    .base = {

        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = 0,
        .hal_api_version = 0,
        .id = MY_HW_MODULE_ID,
        .name = "MY HW MODULE",
        .author = "Sky",
        .methods = &my_hw_methods
    }
};

//For hw_device_t
static struct my_hw_device_t my_hw_device_0 = {
    .base = {
        .tag = HARDWARE_DEVICE_TAG,
        .version = 0,
        .module = (hw_module_t*)&HAL_MODULE_INFO_SYM,
        .close = my_hw_close_device_0
    },
    .set_my_hw_op = set_my_hw_op_device_0
};



//For hw_device_t
int set_my_hw_op_device_0 (struct my_hw_device_t * dev, int op_type)
{
    printf("set_my_hw_op_device_0() op_type = %d\n", op_type);
    return 0;
}

int my_hw_close_device_0(struct hw_device_t* device)
{
    printf("my_hw_close_device_0()\n");
    return 0;
}




//For hw_moudle_methods_t
int my_hw_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device)
{
    printf("my_hw_open() device id %s\n", id);
    if (strcmp(id, "0") != 0){
        printf("my_hw_open() failed\n");
        return -1;
    }
    *device = (struct hw_device_t*)&my_hw_device_0;

    return 0;
}

  从这里我们可以看出,新模块的实现就是对3个结构体的继承和实现。同时对一些成员变量进行赋值。

MY_HW模块源码的分析
__attribute__((visibility("default")))  struct  my_hw_module_t  HAL_MODULE_INFO_SYM = {

    .base = {

        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = 0,
        .hal_api_version = 0,
        .id = MY_HW_MODULE_ID,
        .name = "MY HW MODULE",
        .author = "Sky",
        .methods = &my_hw_methods
    }
};

   my_hw_module_t 的定义,就是定义一个HAL_MODULE_INFO_SYM(它是一个宏定义)变量,注意此宏定义会被替换为一个HMI的名字,此名字是所有HAL模块必须暴露的一个符号。且必须叫做这个名字,因为这是libhardware.so中读取它的约定。

  此外,hw_module_t必须在我定义的变量的开始位置,这样方便类型转换。

//For hw_moudle_methods_t
static struct hw_module_methods_t my_hw_methods = {

    .open = my_hw_open,
};

   hw_module_methods_t 的定义,实现一个真正的设备打开接口。

//For hw_device_t
static struct my_hw_device_t my_hw_device_0 = {
    .base = {
        .tag = HARDWARE_DEVICE_TAG,
        .version = 0,
        .module = (hw_module_t*)&HAL_MODULE_INFO_SYM,
        .close = my_hw_close_device_0
    },
    .set_my_hw_op = set_my_hw_op_device_0
};

   my_hw_device_t的定义,此设备就是我们这个模块定义的一个设备,此设备通过my_hw_module_t中的open接口打开,然后提供相关的接口给HAL相关的程序使用。

综合分析

  这里我们简单设计一个服务程序来调用我们封装的hal模块,其流程就是调用hw_get_module获取实际module地址,然后通过module打开对应设备,然后操作设备,最后关闭设备。

#include "my_hw_hal.h"

int main(int argc, char * argv[])
{
    hw_module_t * hwmodule = nullptr;
    my_hw_device_t * my_hw_device = nullptr;

    int _ret = hw_get_module(MY_HW_MODULE_ID, (const hw_module_t**)&hwmodule);

    #define MY_HW_DEVICE_ID "0"
    _ret = hwmodule->methods->open(hwmodule, MY_HW_DEVICE_ID, (hw_device_t**)&my_hw_device);

    #define MY_HW_DEVICE_ID_0_OP_TYPE_0 0
    my_hw_device->set_my_hw_op(my_hw_device, MY_HW_DEVICE_ID_0_OP_TYPE_0);

    my_hw_device->base.close((hw_device_t*)my_hw_device);
    return 0;
}

  然后通过如下编译脚本生成两个so和一个应用程序。

#!/bin/bash


# for hardware so
gcc -fPIC -c hardware.c -I . -fvisibility=hidden
gcc -shared hardware.o -o libhardware.so -ldl -fvisibility=hidden
strip libhardware.so



# for MY_HW.sky-sdk.so
gcc -fPIC -c my_hw_hal.c -I . -fvisibility=hidden
gcc -shared my_hw_hal.o -o MY_HW.sky-sdk.so -fvisibility=hidden
strip MY_HW.sky-sdk.so


# for my hw service 
g++ my_hw_service.cpp -o my_hw_service -L . -l hardware -I .  -fvisibility=hidden

  我们先来看看我们应用程序执行的结果如下:

rep_img

  我们可以看到,第一步通过hw_get_module获取到一个模块信息,这里其实在hardware.c里面定义的很清楚,直接通过dlopen/dlsym 一个HMI的符号得到了我们定义的my_hw_module_t的变量地址,由于c的内存布局的原因,本来这个地址存放的是my_hw_module_t变量,但是可以直接强转为hw_module_t变量。简单来说,这就是一种c里面实现类似c++继承的方法,由于内存布局是连续的,根据hw_module_t的大小,可以直接从my_hw_module_t前面部分转换为hw_module_t。这也是hw_module_t必须放在my_hw_module_t中开始的原因。

  我们也可知道,在hardware.c中,hw_get_module是根据id来在特定目录中去搜索相关的模块so,然后通过dlopen打开它并进行后续的操作。如我修改的hardware.c部分节选:

rep_img

  同理,到了这里,我们不用猜测,一定在MY_HW.sky-sdk.so暴露了一个HMI的符号。如图:

rep_img

  注意hardware.c和hardware.h直接从android源码中拿出来,简单做修改即可在linux里面编译。这里我们简单看看libhardware.so的符号暴露信息:

rep_img

  这里其实就暴露了上面提到的两个接口,hw_get_module和hw_get_module_by_class。

后记


  总的来说:

  • hw_module_methods_t 可以用来标识模块的公用方法,当前具备了一个open方法,注意一个module对应多个设备功能。
  • hw_get_module() 主要是使用传入的id,然后通过id和一些属性通过dlopen加载so。注意'HMI'这个符号,这个符号是存放的hw_module_t作为基类的地址。通过此地址可以打开这个模块中的特有设备,并提供特定操作。
  • hw_module_t 可以用来标识一个模块,首先通过hw_get_module()获取当前hw_module_t,然后通过当前hw_module_t的hw_module_methods_t中的open方法打开设备。
  • hw_device_t 可以用来标识模块下的一个设备,其中的close方法用来关闭本设备。

  注意三个结构体之间的关系:hw_get_module()获取hw_module_t,hw_module_t通过hw_module_methods_t获取hw_device_t,hw_device_t中携带了当前设备的各种操作方法,其实HAL的另一个重要部分是在hw_device_t中定义当前设备的通用接口。

  其实HAL的整个原理并不复杂,在Linux内核源码中,你会看到大量的类似的操作。归根到底,其实HAL的这种封装,就是一种应用技巧。

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。