分享

Android HAL 层原理分析

 astrotycoon 2018-04-26

Android HAL 简介

Android 系统硬件抽象层(Hardware Abstract Layer)运行在用户空间中,它向下屏蔽了硬件驱动的实现细节,向上提供了硬件访问的服务。通过 HAL 层,Android 系统分为两层来支持硬件设备,其中一层实现在用户空间,另外一层实现在内核空间中,它在 Android 系统框架中的位置如下图所示。传统的 Linux 系统则把对硬件的支持和管理全部放在内核空间中,即把对硬件的全部支持都放在硬件驱动模块当中。

20170324149036043728849.png

为什么要添加一个 HAL 层?

Android 在用户空间中新建一个的 HAL 层来支持硬件设备的主要原因还是因为 Android 使用的开源协议是 Apache License,这个协议比较宽松,它允许开发者获取并修改了源码之后,不用把源码公开出来。而 Linux 使用的开源协议 GPL,它的要求限制就比较多,它要求开发者添加或修改了源码之后,必须把添加或修改后的代码公开出来,所以我们在 Linux 内核中的所使用的设备驱动程序都是源码公开的,任何人都可以获取并修改它。

因此,如果 Android 系统像其他 Linux 系统一样,把对硬件的支持完全实现在 Linux 内核的驱动模块中,那么硬件厂商就必须将这些硬件驱动源码公开,这样就可能损害到移动厂商的利益,因为这相当于暴露了硬件的实现细节和参数。

所以,Android 就在用户空间搞了一个 HAL 层,将硬件的一些重要的操作都放在这一层中完成,这些操作都封装在厂商所提供的一个动态链接库中,从而达到了避免源码公开的目的,而底层 Linux 内核空间中的设备驱动模块,现在则只提供一些最基本的硬件设备寄存器操作的功能。

HAL 模块是如何实现的?

由于最近一直在做一个 Android-x86 系统相关的项目,其中就有一部分是跟音频 HAL 模块紧密相关,所以我就重新对 HAL 的相关知识进行了一个学习和整理,所以本文中将以音频系统对应的 audio HAL 模块( 它最终是以 audio.primary.x86.so 的动态链接库形式存在 )为例来介绍一个 HAL 模块的定义及实现过程。

HAL 层的三个重要结构体

Android 系统的 HAL 层其实并不复杂,只要你能理解清楚下面这 3 个结构体的含义:

  • hw_module_t:用来描述硬件模块
  • hw_device_t:用来描述硬件设备
  • hw_module_methods_t:用来打开硬件模块中包含硬件设备,获得指向硬件设备结构体的指针

Android 系统中 HAL 层是以模块的方式来管理各个硬件访问的接口,每一个硬件模块都对应一个动态链接库文件,而这些动态链接库文件需要符号一定的规范,而上述的这 3 种结构体就是用来建立这种规范。并且一个硬件模块可以管理多个硬件设备,例如 audio HAL 硬件模块中就管理了扬声器、麦克风等多个硬件设备。

注意:这里一定区分 hw_module_t 和 hw_device_t 它们所表示的含义

hw_module_t

结构体 hw_module_t 定义在 /hardware/libhardware/include/hardware/hardware.h 文件中,其定义如下所示:

/**
 * 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 结构体变量,而这结构体变量中的第一个成员必须是 hw_module_t 类型。也就是说,每个硬件模块都要自己实现一个结构体,但是这个结构体的第一个成员必须是 hw_module_t 结构体类型。

其实这里蕴含着一种面向对象中继承的思想,hw_module_t 就是一个基类,描述所有硬件模块都应该具有的一些属性,然后具体到某个特定的硬件模块实现时,都需要继承自 hw_module_t 结构体。也就是说 hw_module_t 是所有特定硬件模块的父类。

有关结构体 hw_module_t 中各个成员的具体含义,这里就不一一进行说明了,相信大家从它们的注释说明中就能了解到每一个成员所表示的含义,这里就挑一些重要的结构体成员来进一步说明。

  • id:这个成员用一个字符串来表示硬件模块的,用来区别于其他硬件模块。
  • methods:这个成员是一个 hw_module_methods_t 指针,它表示硬件模块所包含的方法集(其实里面就一个 open 函数指针,用来打开 hw_device_t 硬件设备,获得指向对应的硬件设备的结构体对象的指针)。
  • dso:我们前面提到,HAL 层中的硬件模块是用动态链接库表示的,所以 dso 指针就是系统使用 dlopen() 函数打开共享动态共享链接库之后获得的句柄。

hw_device_t

结构体 hw_device_t 定义在 /hardware/libhardware/include/hardware/hardware.h 文件中,其定义如下所示:

/**
 * 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_module_t 类似,hw_device_t 也可以看做是一个基类,它描述了所有硬件设备都应该具有的属性,然后具体到某个特定的硬件设备(例如,音频播放时需要的扬声器设备)实现时,都需要继承自 audio_device 结构体。所以,每个 HAL 层中硬件设备对应的结构体中的第一个成员必须是 hw_device_t。

下面还是简单提一下结构体 hw_device_t 比较关键的几个成员:

  • module:这个成员是一个 hw_module_t 指针,表示该结构体 hw_device_t 表示的硬件设备是由哪个 hw_module_t 表示的硬件模块进行管理的。(在这里所以一定要区分清楚==硬件设备==和==硬件模块==的区别!)
  • close:这是一个函数指针,表示如何关闭打开的硬件设备,通常==打开硬件设备的操作==在 hw_module_t 中包含的 hw_module_methods_t 函数列表中的 open() 函数中打开的。

hw_module_methods_t

结构体 hw_module_methods_t 定义在 /hardware/libhardware/include/hardware/hardware.h 文件中,其定义如下所示:

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 就比较简单了,它里面就只有一个 open 函数指针,用来打开 module 硬件模块==所管理==的硬件设备id值为 id 的硬件设备,最后将打开的==硬件设备(用 hw_device_t 结构体来描述)==通过 device 返回。

注意:这个 open 函数明确指出第三个参数的类型为 struct hw_device_t**,这主要是为了统一不同硬件设备向上层提供的硬件接口,然后在具体使用到某中硬件设备时,再转换成特定硬件设备的结构体类型。

Audio HAL 模块的实现

Step 1:定义 struct audio_module 模块

我们前面在结构体 hw_module_t 介绍时,有提到具体的硬件模块要定义一个新的结构体并且这个结构体的==第一个成员必须是 hw_module_t 类型==,所以根据这个规则,audio_module 的定义如下所示: 代码路径:/hardware/libhardware/include/hardware/audio.h

struct audio_module {
    struct hw_module_t common;
};

step 2:定义 struct audio_module 类型的 HAL_MODULE_INFO_SYM 变量

HAL_MODULE_INFO_SYM 其实是一个宏,它定义如下所示: 代码路径:/hardware/libhardware/include/hardware/hardware.h

/**
 * Name of the hal_module_info
 */
#define HAL_MODULE_INFO_SYM         HMI

/**
 * Name of the hal_module_info as a string
 */
#define HAL_MODULE_INFO_SYM_AS_STR  "HMI"

而 struct audio_module 类型名为 HAL_MODULE_INFO_SYM 变量的定义如下所示: 代码路径:<aosp>/generic/goldfish/audio/audio_hw.c

struct audio_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = AUDIO_HARDWARE_MODULE_ID,
        .name = "Generic audio HW HAL",
        .author = "The Android Open Source Project",
        .methods = &hal_module_methods,
    },
};```

**但是我们为什么要在每个硬件模块中都定义一个变量名为 HAL_MODULE_INFO_SYM 的变量呢?**

原因是**为了统一标准的接口**。这个硬件模块类型的变量主要是在 HAL 动态链接库加载时用到,它使得上层的 Framework 层打开所有的 HAL 动态链接库时都能找到名为 HAL\_MODULE\_INFO\_SYM(也就是 hmi)的硬件模块类型变量,然后通过这个变量再来打开它所管理的硬件设备,从而与之进行交互。这么做的好处就是 Framework 层中只需要用相同的 API,就能处理各个厂商所提供的不同的 HAL 动态链接库。

### step 3:定义 struct audio\_hw\_device 硬件设备结构体
每个硬件设备都需要通过一个结构体来表示,并且这个结构体的第一个成员必须是 hw\_device\_t 类型。而有关的对底层硬件设备的有关操作的函数指针接口,也是在这个结构体中定义。 struct audio\_hw\_device 硬件设备结构体的定义如下所示:
代码路径:`/hardware/libhardware/include/hardware/audio.h`

```c
struct audio_hw_device {
    /**
     * Common methods of the audio device.  This *must* be the first member of audio_hw_device
     * as users of this structure will cast a hw_device_t to audio_hw_device pointer in contexts
     * where it's known the hw_device_t references an audio_hw_device.
     */
    struct hw_device_t common;

    ......

    /* Returns audio input buffer size according to parameters passed or
     * 0 if one of the parameters is not supported.
     * See also get_buffer_size which is for a particular stream.
     */
    size_t (*get_input_buffer_size)(const struct audio_hw_device *dev,
                                    const struct audio_config *config);

    /** This method creates and opens the audio hardware output stream.
     * The "address" parameter qualifies the "devices" audio device type if needed.
     * The format format depends on the device type:
     * - Bluetooth devices use the MAC address of the device in the form "00:11:22:AA:BB:CC"
     * - USB devices use the ALSA card and device numbers in the form  "card=X;device=Y"
     * - Other devices may use a number or any other string.
     */

    int (*open_output_stream)(struct audio_hw_device *dev,
                              audio_io_handle_t handle,
                              audio_devices_t devices,
                              audio_output_flags_t flags,
                              struct audio_config *config,
                              struct audio_stream_out **stream_out,
                              const char *address);

    void (*close_output_stream)(struct audio_hw_device *dev,
                                struct audio_stream_out* stream_out);

    /** This method creates and opens the audio hardware input stream */
    int (*open_input_stream)(struct audio_hw_device *dev,
                             audio_io_handle_t handle,
                             audio_devices_t devices,
                             struct audio_config *config,
                             struct audio_stream_in **stream_in,
                             audio_input_flags_t flags,
                             const char *address,
                             audio_source_t source);

    void (*close_input_stream)(struct audio_hw_device *dev,
                               struct audio_stream_in *stream_in);

    ......

};

在上面音频硬件设备结构体 audio_hw_device 的定义中,第一个结构体成员就是 struct hw_device_t 类型的,而其他的结构体成员都是函数指针,例如 open_output_stream 函数就是用来打开播放音频的扬声器设备,open_input_stream 函数则是用来打开录音用的麦克风设备。

step 4:定义 struct hw_module_methods_t 函数列表变量

在前面介绍的结构体 hw_module_t 的定义中就有一个 hw_module_methods_t 函数指针类型的成员open,该函数的作用就是让硬件模块打开硬件设备,然后对特定硬件设备(例如上面的 struct audio_device_t结构体)中定义函数指针变量进行赋值绑定,所以在每个硬件模块都要实现这样一个变量。

struct audio_module 结构体中 struct hw_module_t 类型的成员 common 中的 hw_module_methods_t 函数指针成员的实现绑定如下所示: 代码路径:<aosp>/generic/goldfish/audio/audio_hw.c

static struct hw_module_methods_t hal_module_methods = {
    .open = adev_open,
};

可以看到,在 audio HAL 模块中使用了 adev_open 函数来初始化了 struct hw_module_methods_t 中的 open 函数指针成员。我们现在回过头来看 step 2struct audio_module HAL_MODULE_INFO_SYM 变量的定义中,audio_module 中的第一个成员 common中的结构体变量 methods 的指针就是被初始化指向了现在这里定义的 hal_module_methods 变量。

step 5:adev_open 函数的实现

最后,我们来看看 adev_open 函数的实现,看看它是如何通过 audio_module 硬件模块对象来打开 audio_hw_device 硬件设备对象。 代码路径:<aosp>/generic/goldfish/audio/audio_hw.c

static int adev_open(const hw_module_t* module, const char* name,
                     hw_device_t** device)
{
     struct generic_audio_device *adev;
    int fd;

    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0)
        return -EINVAL;

    fd = open(AUDIO_DEVICE_NAME, O_RDWR);
    if (fd < 0)
        return -ENOSYS;

    adev = calloc(1, sizeof(struct generic_audio_device));

    adev->fd = fd;

    adev->device.common.tag = HARDWARE_DEVICE_TAG;
    adev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
    adev->device.common.module = (struct hw_module_t *) module;
    adev->device.common.close = adev_close;

    adev->device.init_check = adev_init_check;
    adev->device.set_voice_volume = adev_set_voice_volume;
    adev->device.set_master_volume = adev_set_master_volume;
    adev->device.get_master_volume = adev_get_master_volume;
    adev->device.set_master_mute = adev_set_master_mute;
    adev->device.get_master_mute = adev_get_master_mute;
    adev->device.set_mode = adev_set_mode;
    adev->device.set_mic_mute = adev_set_mic_mute;
    adev->device.get_mic_mute = adev_get_mic_mute;
    adev->device.set_parameters = adev_set_parameters;
    adev->device.get_parameters = adev_get_parameters;
    adev->device.get_input_buffer_size = adev_get_input_buffer_size;
    adev->device.open_output_stream = adev_open_output_stream;
    adev->device.close_output_stream = adev_close_output_stream;
    adev->device.open_input_stream = adev_open_input_stream;
    adev->device.close_input_stream = adev_close_input_stream;
    adev->device.dump = adev_dump;

    *device = &adev->device.common;

    return 0;
}

这里再补充一个小的定义,struct generic_audio_device 中的第一个成员 device 就是我们前面提到的 struct audio_hw_device 结构体,它的定义如下: 代码路径:/hardware/libaudio/audio_hw.c

struct generic_audio_device {
    struct audio_hw_device device;
    pthread_mutex_t lock;
    struct audio_stream_out *output;
    struct audio_stream_in *input;
    int fd;
    bool mic_mute;
};

我们可以从 adev_open 函数中的实现中看到,它里面的主要工作就是做一些对 struct audio_hw_device 对象的初始化,将其定义的函数指针指向对应的已经实现好的函数中。例如,这里将struct audio_hw_device中定义的 open_output_stream 函数指针成员指向了 adev_open_output_stream 函数。这样在 Framework 层调用的 struct audio_hw_device 对象的 open_output_stream 函数,其实最终调用的是 adev_open_output_stream函数。

这里,还有一点需要特别注意的就是我们所打开的硬件设备对象是怎么返回的? 答案就是它是通过 open 函数中的第三个参数 hw_device_t** device返回的。在 open 函数初始化并打开特定的硬件设备之后,它就将硬件设备结构体中的第一个成员 struct audio_hw_device 类型的 common 对象返回。

那么为什么这里返回的是 hw_device_t** 类型的硬件设备,而是不是audio_hw_device** 类型呢? 其实这个问题在前面在介绍 hw_module_methods_t 中的 open 函数时已经提到过了。这里主要也蕴含着面向对象编程中的另外一种重要思想多态,这使得使用的 hw_device_t 指针就可以访问到子类中继承了父类的属性和方法,如果要获得某个子类所特有的属性,那么只要将其进行类型的强制转换即可。

总结

理解 Android HAL 层最关键的还是要弄清楚 hw_module_t 、 hw_device_t、 hw_module_methods_t 这三个结构体的含义即关系,以及如何基于这三个结构体来实现特定硬件的硬件模块结构体硬件设备结构体硬件模块方法列表结构体。其实从面向对象编程的角度来考虑,前面三者和后面三者之间的关系,就好比是父类和子类的关系,如下图所示:

后面,我可能会写一片关于 Android 系统加载 HAL 模块的文章。

参考文章

  1. 罗升阳. Android 系统源代码情景分析作者. 电子工业出版社, 2012: 13-44

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多