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

这篇具有很好参考价值的文章主要介绍了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

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

  我们可以看到,第一步通过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部分节选:

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

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

  这里其实就暴露了上面提到的两个接口,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的这种封装,就是一种应用技巧。

参考文献




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

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

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

PS: 有问题请留言,看到后我会第一时间回复。
文章来源地址https://www.toymoban.com/news/detail-403118.html

到了这里,关于Android HAL机制的深入理解及在Linux上移植和运行的一个好玩的HAL小例子的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入理解Linux 内核追踪机制

    Linux 存在众多 tracing tools,比如 ftrace、perf,他们可用于内核的调试、提高内核的可观测性。众多的工具也意味着繁杂的概念,诸如 tracepoint、trace events、kprobe、eBPF 等,甚至让人搞不清楚他们到底是干什么的。本文尝试理清这些概念。   Probe Handler 如果我们想要追踪内核的一

    2024年02月15日
    浏览(59)
  • 【android12-linux-5.1】【ST芯片】【RK3588】【LSM6DSR】HAL移植

    一、环境介绍 RK3588主板搭载Android12操作系统,内核是Linux5.10,使用ST的六轴传感器LSM6DSR芯片。 二、芯片介绍 LSM6DSR是一款加速度和角速度(陀螺仪)六轴传感器,还内置了一个温度传感器。该芯片可以选择I2C,SPI通讯,还有可编程终端,可以后置摄像头等设备,功能是很强大

    2024年02月09日
    浏览(47)
  • 深入理解Android音视频同步机制(一)ExoPlayer的avsync逻辑

    对于此前没有了解过ExoPlayer的朋友,我们在这里先用下面的时序图简单介绍一下ExoPlayer在音视频同步这块的基本流程: 图中 ExoPlayerImplInternal是Exoplayer的主loop所在处,这个大loop不停的循环运转,将下载、解封装的数据送给AudioTrack和MediaCodec去播放。 MediaCodecAudioRenderer和MediaC

    2023年04月12日
    浏览(52)
  • Android-高级-UI-进阶之路-(二)-深入理解-Android-8-0-View-触摸事件分发机制,查漏补缺

    我们看到内部又调用了父类 dispatchTouchEvent 方法, 所以最终是交给 ViewGroup 顶级 View 来处理分发了。 顶级 View 对点击事件的分发过程 在上一小节中我们知道了一个事件的传递流程,这里我们就大致在回顾一下。首先点击事件到达顶级 ViewGroup 之后,会调用自身的 dispatchTouchE

    2024年04月14日
    浏览(67)
  • 深入理解Java GSS(含kerberos认证及在hadoop、flink案例场景举例)

    在当今的信息安全环境下,保护敏感数据和网络资源的安全至关重要。 Kerberos 认证协议作为一种强大的网络身份验证解决方案,被广泛应用于许多大型分布式系统中,如: Hadoop 。而 Java GSS ( Generic Security Services )作为 Java 提供的通用安全服务,与 Kerberos 认证密切相关。 本

    2024年02月08日
    浏览(44)
  • Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制。该系列教程从Linux内核的各个模块入手,逐一分析其源码实现,并结合实际应用场景进行讲解。通过学习本系列,读者可以深入了解Linux操作系统的底层机制,掌握

    2024年01月21日
    浏览(47)
  • Java垃圾回收机制深入理解

    Java垃圾回收机制是Java虚拟机(JVM)的核心组件之一,对于内存管理起到至关重要的作用。它能自动追踪并管理应用程序中创建的对象,当这些对象不再使用时,垃圾回收机制会自动回收其占用的内存,使这部分内存能够被再次利用。此机制极大地减少了开发者需要手动管理

    2024年02月09日
    浏览(35)
  • Android HAL深入探索(5): 调试HAL报错与解决方案

    在我们学习Android HAL开发时,可能会遇到一些编译或运行时的错误,这些错误可能会影响探索脚本。为了有效地定位和解决这些错误,需要了解Android HAL的架构、工具和方法。本文将介绍一些我自己在学习Android HAL的调试技巧和常见错误的解决方案,希望能帮助到大家。 网上其

    2024年01月24日
    浏览(39)
  • C# 深入理解事件(event)机制

    目录 一,引言 二,事件的定义和用法 2.1 同步事件执行  2.2 异步事件执行 2.3 等待异步事件完成 2.4 捕获异常处理中的异常 三,事件的综合案例 3.1 需求:汽车出停车场时收费,开闸放行 都知道事件的本质是一个多播委托(MulticastDelegate),但对于事件的机制和用法一直懵懵

    2024年02月16日
    浏览(41)
  • 深入理解Windows操作系统机制(二)

    我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。 我们每天都在用Windows操作系统,但是其实我们每天直接在打交道的并不是Windows操作系统的内核,而是Windows操作系统的人机交互界面,这个界面其实只是Window

    2024年02月17日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包