Android HAL硬件抽象层的原理与应用实例

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android HAL硬件抽象层的原理与应用实例相关的知识,希望对你有一定的参考价值。

前言

先来看下 android 的系统架构图:

HAL 全称 Hardware Abstract Layer,即硬件抽象层,它向下屏蔽了硬件的实现细节,向上提供了抽象接口,HAL是底层硬件和上层框架直接的接口,框架层通过HAL可以操作硬件设备。

为什么需要HAL?

许多早期的计算机系统没有任何形式的硬件抽象,这意味着为该系统编写程序的任何人都必须知道每个硬件设备如何与系统的其余部分进行通信。这对软件开发人员来说是一个巨大的挑战,因为他们必须知道系统中每个硬件设备如何工作才能确保软件的兼容性。使用硬件抽象,而不是直接与硬件设备通信的程序,它将程序传达给操作系统该设备应执行的操作,然后操作系统会向该设备生成硬件相关的指令。这意味着程序员不需要知道特定设备的工作方式,就能使他们的程序与设备兼容。

我们知道 Android 是基于 Linux 进行开发的,传统的 Linux 对硬件的操作基本上都是在内核中,而 Android 把对硬件的操作分为了两部分,HAL 和内核驱动,HAL 实现在用户空间,驱动在内核空间。这是因为 Linux 内核中的代码是需要开源的(遵循GUN License),如果把对硬件的操作放在内核这会损害硬件厂商的利益,因为这是别人厂商的商业机密,而现在有了 HAL 层位于用户空间(该部分代码遵循Apache License,无需对外开放源代码),硬件厂商就可以将自己的核心算法之类的放在 HAL 层,保护了自己的利益,这样 Android 系统才能得到更多厂商的支持。

Windows 下的 HAL 位于操作系统的最底层,它直接操作物理硬件设备,使用抽象接口来隔离不同硬件的具体实现,为上层的操作系统和设备驱动程序提供一个统一接口,起到对硬件抽象作用。Linux 下的 HAL 与 Windows 不同,HAL 层并不位于操作系统的最底层直接操作硬件,而是在操作系统内核层和驱动程序之上,是一个运行在 User Space(用户空间) 的服务程序。

安卓源码编译

参考文章:

  1. Android 系统开发系列(1):Android 12 源代码下载、编译和刷机
  2. [原创]源码编译(1)——Android6.0源码编译详解 ;
  3. 从谷歌官网下载android 6.0源码、编译并刷入nexus 6p手机

HAL体系结构

HAL 层的源码目录主要如下:

1)一般除了以下3个以外都是硬件厂商相关的 hal 目录:
/hardware/libhardware_legacy/   旧的架构、采取链接库模块的方式
/hardware/libhardware         新架构,调整为 HAL stub
/hardware/ril              无线电抽象层

2)libhardware 目录的结构如下:
/hardware/libhardware/hardware.c  编译成libhardware.s置于/system/lib

3/hardware/libhardware/include/hardware目录下包含如下头文件:
hardware.h         通用硬件模块头文件
copybit.h          copybit模块头文件
gralloc.h          gralloc模块头文件
lights.h           背光模块头文件
overlay.h          overlay模块头文件
qemud.h            qemud模块头文件
sensors.h          传感器模块头文件

4/hardware/libhardware/modules  目录下定义了很多硬件模块:
hardware/msm7k 
/hardware/qcom 
/hardware/ti
/device/Samsung 
/device/moto
这些硬件模块都编译成xxx.xxx.so,目标位置为/system/lib/hw目录

HAL新老架构

位于 libhardware_legacy 目录下的 “旧HAL架构” 和位于 libhardware 目录下的 “新HAL架构”,两种框架如下图所示:

1、老式 Module 架构

Android 用户应用程序或框架层代码由 Java 实现,Java 运行在 Dalvik 虚拟机中,没有办法直接访问底层硬件,只能通过调用 so 本地库代码实现,在 so本地库代码里有对底层硬件操作代码。也就是说,应用层或框架层 Java 代码,通过 JNI 技术调用 C 或 C++ 写的 so 库代码,在 so 库代码中调用底层驱动,实现上层应用的提出的硬件请求操作。实现硬件操作的 so 库为:module。

老的 libhardware_legacy 架构,在 so 动态链接库中实现了对驱动的访问逻辑处理,应用或者框架在 Runtime(JNI 部份)通过 so 动态链接库直接调用函数达到对硬件驱动的访问。

这种设计架构虽然满足了 Java 应用访问硬件的需要,但是它使得我们的代码上下层次间的耦合太高,用户程序或框架代码必须要去加载 module 库,如果底层硬件有变化,moudle 要重新编译,上层也要做相应的变化;同时如果多个应用程序同时访问硬件,都去加载 module,则同一 module 被多个进程映射多次,会有代码的重入问题。因此 Google 又提出了新的HAL架构。

2、新式 HAL Stub 架构

新的架构使用的是 Module Stub 方式,Stub 是存根或桩的意思,其实说白了就是指一个对象代表的意思。由上面的架构可知,上层应用层或框架层代码加载 so 库代码,so 库代码我们称为 module,在 HAL 层注册了每个硬件对象的存根 stub,当上层需要访问硬件的时候,就从当前注册的硬件对象 stub 里查找,找到之后 stub 会向上层 module 提供该硬件对象的 operations interface(操作接口),该操作接口就保存在了 module 中,上层应用或框架再通过这个 module 操作接口来访问硬件。

libhardware 架构采用 Proxy 代理模式,Stub 虽然仍是以*.so的形式存在,但是 HAL 已经将*.的具体实现隐藏了起来。Stub 向 HAL 提供 operations 方法,Runtime 通过 Stub 提供的 so 获取它的 operations 方法,并告知 Runtime 的 callback 接口回调方法。这样,Runtime 和 Stub 都有对方调用的方法,一个应用的请求通过 Runtime 调用 Stub 的 operations 方法,而 Stub 响应 operations 方法并完成后,再调用 Runtime 的 callback 方法进行返回。如下图,以 Led 为例的示意图:

Led App 为 Android 应用程序,Led App 里的 Java 代码不能操作硬件,将硬件操作工作交给本地 module 库 led_runtime.so,它从当前系统中查找 Led Stub,查找到之后,Led Stub 将硬件驱动操作返回给 module,Led App 操作硬件时,通过保存在 module 中的操作接口间接访问底层硬件。

3、Module 架构与 Stub 构架对比

在 Module 架构中,本地代码由 so 库实现,上层直接将 so 库映射进进程空间,会有代码重入及设备多次打开的问题。新的 Stub 框架虽然也要加载 module 库,但是这个 module 已经不包含操作底层硬件驱动的功能了,它里面保存的只是底层 Stub 提供的操作接口,底层 Stub 扮演了“接口提供者”的角色。当 Stub 第一次被使用时加载到内存,后续再使用时仅返回硬件对象操作接口,不会存在设备多次打开问题,并且由于多进程访问时返回的只是函数指针,代码没有重入问题。

4、应用调用 HAL 层的完整过程

Android 的 HAL 的实现需要通过 JNI(Java Native Interface),JNI 简单来说就是 java 程序可以调用 C/C++ 写的动态链接库,这样的话,HAL 可以使用 C/C++ 语言编写,效率更高。Android APP 可以直接调用 .so(上面第一种 Module 架构方法),也可以通过app->app_manager->service(java)->service(jni)->HAL来调用(上面第二种 Stub 架构方法)。

第二种方法的基本框架如下所示:

继续以 LED 的调用过程为例:

描述下调用过程,如下:

  1. JNI -> 通用硬件模块 -> 硬件模块 -> 内核驱动接口;
  2. 具体一点:JNI->libhardware.so->xxx.xxx.so->kernel
  3. 再具体来说:android frameworks 中 JNI 调用 hardware.c 中定义的 hw_get_module 函数来获取硬件模块,然后调用硬件模块中的方法,硬件模块中的方法直接调用内核接口完成相关功能(看不懂没关系,下文会具体解释)。

HAL的结构体

一般来说 HAL moudle 需要涉及的是三个关键结构体:

struct hw_module_t;           //抽象硬件模块结构体
struct hw_module_methods_t;   //硬件模块方法结构体
struct hw_device_t;           //抽象硬件设备结构体

这三个结构体定义在 hardware.h 中,具体的代码路径:

如下图所示:

其中头文件 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

    uint32_t tag;
    uint16_t version_major;
    uint16_t version_minor;
    cost char* id;
    const char* author;
    struct hw_module_methods_t* methods;
    void* dso;
    uint32_t reserved[10];
hw_module_t;

/**
* Create a function list
*/
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; 

/**
* 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;
    uint32_t version;
    struct hw_module_t* module;
    uint32_t reserved[12];
    int (*close)(struct hw_device_t* device)
hw_device_t; 

以上关于 HAL 模块的关键变量和结构体,概述如下:

Android HAL 将各类硬件设备抽象为硬件模块,HAL 使用hw_module_t结构体描述一类硬件抽象模块。每个硬件抽象模块都对应一个动态链接库,一般是由厂商提供的,这个动态链接库必须尊重 HAL 的命名规范才能被 HAL 加载到,我们后面会看到。每一类硬件抽象模块又包含多个独立的硬件设备,HAL 使用hw_device_t结构体描述硬件模块中的独立硬件设备。下面来分析下 HAL moudle 的三个结构体。

1、先看看头文件中定义的通用硬件模块结构体hw_module_t,它声明了 JNI 调用的接口函数hw_get_module。具体定义如下:

/**
* 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;
  /** major version number for the module */
  uint16_t version_major;
  /** minor version number of the module */
  uint16_t version_minor;
  /** 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;
  /** padding to 128 bytes, reserved for future use */
  uint32_t reserved[32-7];
 hw_module_t;

如注释所说,所有的 HAL 模块都要有一个以HAL_MODULE_INFO_SYM命名的结构,而且这个结构要以hw_module_t为第一个成员(这里可以理解为是一种继承关系,相当于硬件模块的HAL_MODULE_INFO_SYM结构体,继承了hw_module_t,只不过是 C 语言中没有继承的概念,是通过在结构体中包含的方式间接实现的)。咱们以 lights,sensor 为例:

struct sensors_module_t 
    struct hw_module_t common;
    int (*get_sensors_list)(struct sensors_module_t* module,
            struct sensor_t const** list);
;

/*
 * The lights Module
 */
struct light_module_t HAL_MODULE_INFO_SYM = 
    .common: 
        tag: HARDWARE_MODULE_TAG,
        version_major: 1,
        version_minor: 0,
        id: LIGHTS_HARDWARE_MODULE_ID,
        name: "Lights module",
        author: "Rockchip",
        methods: &light_module_methods,
    
;

const struct sensors_module_t HAL_MODULE_INFO_SYM = 
    .common = 
        .tag = HARDWARE_MODULE_TAG,
        .version_major = 1,
        .version_minor = 0,
        .id = SENSORS_HARDWARE_MODULE_ID,
        .name = "Stingray SENSORS Module",
        .author = "Motorola",
        .methods = &sensors_module_methods,
    ,
    .get_sensors_list = sensors__get_sensors_list
;

2、在 hw_module_t中比较重要的是硬件模块方法结构体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;

该方法只有一个函数指针 open,该方法在定义HAL_MODULE_INFO_SYM的时候被初始化。根据参数可以推断出 open 是用来打开硬件模块获取模块中的硬件设备。由于一个硬件抽象模块中可能包含多个设备,因此需要根据传入的设备id来获取相应的硬件设备hw_device_t。所以这里的 device 就表示一个已经打开的硬件设备。

3、hw_module_methods_t中调用的设备结构体参数 hw_device_t ,前面提到 HAL 使用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 number for hw_device_t */
    uint32_t version;
    /** reference to the module this device belongs to */
    struct hw_module_t* module;
    /** padding reserved for future use */
    uint32_t reserved[12];
    /** Close this device */
    int (*close)(struct hw_device_t* device);
 hw_device_t;

HAL 规定每个硬件设备都必须定义一个硬件设备描述结构体,该结构体必须以hw_device_t作为第一个成员变量,后跟设备相关的公开函数和属性。

  • tag:初始化为常量 HARDWARE_DEVICE_TAG;
  • module:表示该硬件设备归属于哪一个硬件抽象模块;
  • close:函数指针,用来关闭硬件设备。

同样来看下实际的例子:

struct light_device_t 
    struct hw_device_t common;
    int (*set_light)(struct light_device_t* dev,
            struct light_state_t const* state);
;

/**
 * Every device data structure must begin with hw_device_t
 * followed by module specific public methods and attributes.
 */
struct sensors_poll_device_t 
    struct hw_device_t common;
    int (*activate)(struct sensors_poll_device_t *dev,
            int handle, int enabled);

    int (*setDelay)(struct sensors_poll_device_t *dev,
            int handle, int64_t ns);

    int (*poll)(struct sensors_poll_device_t *dev,
            sensors_event_t* data, int count);
;

到此,HAL 的 3 个核心数据结构体就分析完了。硬件厂商必须遵循HAL规范和命名,实现抽象硬件模块结构体 hw_module_t 和抽象硬件设备结构体hw_device_t,并且在硬件模块方法结构体hw_module_methods_t中提供 open 函数来获取 hw_device_t。下面我们来看看 HAL 到底是怎样获取硬件模块和硬件设备的,以及是如何加载和解析对应的动态共享库的。

模块实现方法

在 Android 的 HAL 框架使用通用的 “321架构”,也就是三个结构体,两个常量,一个函数。所有的硬件抽象模块都遵循321架构,在此基础上扩展自有的功能。三个结构体上面已经分析完了,接着来看下两个常量(HAL_MODULE_INFO_SYM值定义同样在上面的 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"

最后看下“一个函数”:在 hardware.c 文件中有一个关键的公共 API——hw_get_module,其作用是根据 module_id 去查找注册相对应的硬件对象,然后载入相应的HAL层驱动模块的 so 文件:

先给个图总结下 hardware.c 文件中定义的 HAL 整体的获取 Module 和 Device 结构对象的流程:

下面以 lights 模块为例进行分析,其源文件位置:(Android 5.1 源码):

/frameworks/base/services/core/java/com/android/server/lights/
/frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp
/hardware/libhardware/include/hardware/lights.h

访问设备大概流程:

app--->frameworks--->hardware--->kernel驱动

(1)frameworks 通过 jni 调用hw_get_module()获得 HAL 对应的模块,在 lights.h中定义有 lights 模块的ID:

#define LIGHTS_HARDWARE_MODULE_ID "lights"

(2)在 Android 5.1 版本的源码frameworks/base/services/core/jni/com_android_server_LightsService.cpp的i nit_native 方法中调用hw_ge_module(),代码如下:

static jint init_native(JNIEnv *env, jobject clazz)

    int err;
    hw_module_t* module;
    Devices* devices;
    devices = (Devices*)malloc(sizeof(Devices));
    err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
    if (err == 0) 
        devices->lights[LIGHT_INDEX_BACKLIGHT]
                = get_device(module, LIGHT_ID_BACKLIGHT);
    //………………………………………….

(3)由上已知hw_get_module函数在 hardware.c 中实现:

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

   return hw_get_module_by_class(id, NULL, module);

(4)来看看hw_get_module_by_class是如何实现的:

/** Base path of the hal modules */
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"

/**
 * 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]));
    
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;

    //根据id生成module name,这里inst为NULL
    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 */
    //首先查询特定的属性名称来获取variant值
    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 */
    //逐一查询variant_keys数组定义的属性名称
    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;
        
    
    //没有找到,尝试默认variant名称为default的共享库
    /* 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); //执行加载和解析共享库的工作

可以看到,在hw_get_module_by_class函数中:

  • 首先根据 class_id 生成 module name,这里就是hw_get_module函数传进来的 id;
  • 再通过 property_get 获得 varient_key 中定义的系统属性,如果系统中有定义该属性,就会获得一个模块名.属性名组成的一个 so 的名称;
  • 然后去/system/lib/hw/vendor/lib/hw下查看,该 so 是否存在,如果存在,调用 load 函数,打开.so文件;
  • 如果不存在,则遍历 variant_keys 数组中定义的属性名称来获取属性值,得到目标模块库名字,检查其是否存在;
  • 如果根据属性值都没有找到模块共享库,则尝试检查 default 的库是否存在,如果仍然不存在,返回错误;
  • 如果上述任何一次尝试找到了目标共享库,path 就是目标共享库的文件路径,调用 load 执行真正的加载库的工作。

(5)再看 load 函数的实现:

/**
 * 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
     */
    //使用dlopen打开path定义的目标共享库,得到库文件的句柄handle
    handle = dlopen(path, RTLD_NOW);
    if (handle == NULL) 
        //出错,通过dlerror获取错误信息
        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"
    //使用dlsym找到符号为“HMI”的地址,这里应该是hw_module_t结构体的地址;并且赋给hmi
    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 */
    //检查模块id是否匹配
    if (strcmp(id, hmi->id) != 0) 
        ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
        status = -EINVAL;
        goto done;
    
    //保存共享库文件的句柄
    hmi以上是关于Android HAL硬件抽象层的原理与应用实例的主要内容,如果未能解决你的问题,请参考以下文章

Android HAL硬件抽象层的原理与应用实例

Android硬件抽象层(HAL)深入剖析

第9章 Android硬件抽象层 学习心得

Android深度探索(卷1)HAL与驱动开发 第九章 硬件抽象层:HAL 读书笔记

Android深度探索与HAL驱动开发(卷1)-- 第九章随笔

硬件抽象层:HAL