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 架构中的桥梁作用:
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 框架加载的完整流程:
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 接口访问任意硬件设备。