Android HAL架构原理与驱动开发入门 - 第4节

4.1 Stub 机制的核心概念

在 Android HAL 的早期实现(即传统 HAL,亦称 Legacy HAL)中,Stub(存根)是 HAL 模块的基本组成单元。每个硬件设备对应一个 Stub 实例,它封装了与该设备交互的所有底层操作。Stub 机制的核心思想是:HAL 框架层通过标准接口加载动态库,动态库内部通过注册机制暴露设备操作函数,上层应用无需关心硬件细节。

Stub 的本质:一个遵循 HAL 模块规范的结构体实例,包含模块描述信息(hw_module_t)和设备操作接口(hw_device_t),通过预定义的符号名导出给 HAL 框架加载器。

4.1.1 Stub 在 HAL 架构中的位置

下图展示了 Stub 在 Android HAL 架构中的桥梁作用:

HAL Stub 在 Android 架构中的位置 Android 应用 / Framework HAL 框架加载器 (hw_get_module) HAL Stub 模块 (动态库 .so) hw_module_t + hw_device_t + open/close/ioctl Linux 内核驱动 硬件设备 (GPIO/寄存器) Stub 是 HAL 框架与 内核驱动之间的适配层

4.2 Stub 模块的组成结构

一个标准的 HAL Stub 模块由三个核心部分组成:

组成部分 数据结构 职责描述
模块描述符 hw_module_t 标识模块类型、版本、作者,以及打开设备的回调函数指针
设备接口 hw_device_t 描述设备句柄、关闭函数,以及自定义的设备操作函数指针
注册符号 HAL_MODULE_INFO_SYM 导出的全局符号,HAL 加载器通过该符号找到模块入口
关键约定:每个 HAL Stub 动态库必须导出名为 HAL_MODULE_INFO_SYM(实际展开为 HMI)的全局变量,类型为 hw_module_t。这是 HAL 框架加载器定位模块的唯一入口。

4.3 编写一个简单的 LED HAL Stub 模块

下面我们以控制开发板上的 LED 灯为例,完整实现一个 HAL Stub 模块。该模块支持 打开、关闭 和 读取状态 三个操作。

4.3.1 头文件定义(led_hw.h)

头文件定义了设备操作接口和模块 ID,供上层调用方和底层实现共同引用:

// led_hw.h
#ifndef ANDROID_LED_HW_H
#define ANDROID_LED_HW_H

#include <stdint.h>
#include <sys/types.h>
#include <hardware/hardware.h>

/* 模块 ID —— HAL 加载器通过此 ID 查找对应的 .so 库 */
#define LED_HARDWARE_MODULE_ID "led"

/* 设备结构体:继承 hw_device_t,扩展 LED 专有操作 */
struct led_device_t {
    struct hw_device_t common;   // 必须为第一个字段

    /* LED 控制函数指针 */
    int (*led_on)(struct led_device_t* dev);
    int (*led_off)(struct led_device_t* dev);
    int (*led_read)(struct led_device_t* dev, int* status);
};

#endif // ANDROID_LED_HW_H

4.3.2 模块实现(led_stub.c)

实现文件包含模块注册、设备打开/关闭以及 LED 控制逻辑。这里使用 /sys/class/leds 文件系统模拟真实硬件操作:

// led_stub.c
#define LOG_TAG "led_stub"
#include <cutils/log.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <hardware/hardware.h>
#include "led_hw.h"

/* LED 设备路径(以友善之臂 Tiny4412 为例) */
#define LED_DEVICE_PATH "/sys/class/leds/led1/brightness"

/* 打开设备:由 HAL 框架调用 hw_module_t->methods->open 触发 */
static int led_device_open(const struct hw_module_t* module,
                           const char* name,
                           struct hw_device_t** device) {
    if (strcmp(name, LED_HARDWARE_MODULE_ID) != 0) {
        ALOGE("LED: unknown device name %s", name);
        return -EINVAL;
    }

    struct led_device_t* dev = calloc(1, sizeof(struct led_device_t));
    if (!dev) {
        ALOGE("LED: failed to allocate device");
        return -ENOMEM;
    }

    /* 填充 hw_device_t 公共字段 */
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 1;
    dev->common.module = (struct hw_module_t*)module;
    dev->common.close = (int (*)(struct hw_device_t*))led_device_close;

    /* 绑定 LED 操作函数 */
    dev->led_on  = led_on_func;
    dev->led_off = led_off_func;
    dev->led_read = led_read_func;

    *device = &dev->common;
    ALOGI("LED: device opened successfully");
    return 0;
}

/* 关闭设备 */
static int led_device_close(struct hw_device_t* device) {
    struct led_device_t* dev = (struct led_device_t*)device;
    if (dev) {
        free(dev);
    }
    ALOGI("LED: device closed");
    return 0;
}

/* LED 点亮:向 sysfs 写入 "1" */
static int led_on_func(struct led_device_t* dev) {
    int fd = open(LED_DEVICE_PATH, O_WRONLY);
    if (fd < 0) {
        ALOGE("LED: failed to open %s", LED_DEVICE_PATH);
        return -1;
    }
    write(fd, "1", 1);
    close(fd);
    return 0;
}

/* LED 熄灭:向 sysfs 写入 "0" */
static int led_off_func(struct led_device_t* dev) {
    int fd = open(LED_DEVICE_PATH, O_WRONLY);
    if (fd < 0) {
        ALOGE("LED: failed to open %s", LED_DEVICE_PATH);
        return -1;
    }
    write(fd, "0", 1);
    close(fd);
    return 0;
}

/* 读取 LED 状态 */
static int led_read_func(struct led_device_t* dev, int* status) {
    int fd = open(LED_DEVICE_PATH, O_RDONLY);
    if (fd < 0) {
        ALOGE("LED: failed to open %s", LED_DEVICE_PATH);
        return -1;
    }
    char buf[4] = {0};
    read(fd, buf, sizeof(buf) - 1);
    *status = atoi(buf);
    close(fd);
    return 0;
}

4.3.3 模块注册(HAL_MODULE_INFO_SYM)

每个 HAL Stub 库必须导出一个名为 HAL_MODULE_INFO_SYM 的 hw_module_t 结构体实例。HAL 加载器通过 dlsym 查找该符号,从而获取模块入口:

/* 模块注册 —— 全局符号,必须命名为 HAL_MODULE_INFO_SYM */
struct hw_module_t HAL_MODULE_INFO_SYM = {
    .tag            = HARDWARE_MODULE_TAG,
    .version_major  = 1,
    .version_minor  = 0,
    .id             = LED_HARDWARE_MODULE_ID,
    .name           = "LED HAL Stub Module",
    .author         = "Android Driver Course",
    .methods        = &led_module_methods,
};

/* 模块方法表:目前只提供 open 回调 */
static struct hw_module_methods_t led_module_methods = {
    .open = led_device_open,
};
常见错误:如果 HAL_MODULE_INFO_SYM 拼写错误、未导出或类型不匹配,hw_get_module 将返回 -ENOENT,导致 HAL 加载失败。务必检查编译后的动态库是否包含该符号:readelf -s led.default.so | grep HMI。

4.4 Stub 模块的编译与注册流程

下图展示了从源码到 HAL 框架加载的完整流程:

LED HAL Stub 编译与注册流程 ① 编写源码 led_hw.h + led_stub.c ② Android.mk 编译 生成 led.default.so ③ 推送到设备 /vendor/lib/hw/ 或 /system/lib/hw/ ④ 应用调用 hw_get_module 传入 "led" 模块 ID ⑤ HAL 加载器查找 dlsym 找到 HMI 符号 ⑥ 调用 open 回调 获取 led_device_t 接口 命名规则: <module_id>.<variant>.so 如 led.default.so

4.5 Stub 机制的关键要点

  • 模块命名规范:动态库必须命名为 <module_id>.<variant>.so,例如 led.default.so。variant 通常为 default,表示通用实现。
  • 符号导出:必须导出 HAL_MODULE_INFO_SYM 符号,且类型为 hw_module_t。编译时注意不要被编译器优化掉。
  • 设备生命周期:open 回调中分配设备结构体,close 回调中释放。上层通过 hw_device_t 指针操作设备。
  • 线程安全:Stub 实现本身不保证线程安全,若上层多线程调用,需在实现中加入互斥锁保护。
  • 调试手段:使用 ALOGI/ALOGE 输出日志,通过 logcat -s led_stub 查看模块加载和运行状态。
总结:HAL Stub 机制通过标准化的模块描述符 + 设备接口 + 符号注册,实现了硬件抽象层与上层框架的解耦。开发者只需按照约定编写头文件、实现设备操作、注册模块符号,即可让上层应用通过统一的 hw_get_module 接口访问任意硬件设备。