3. 传统HAL架构(libhardware):核心结构体与加载流程
传统HAL架构(即libhardware方案)是Android系统从早期版本(如Android 1.0至Android 8.0之前)一直沿用的标准硬件抽象层实现方式。它通过定义统一的结构体接口和动态加载机制,将底层驱动与上层Framework解耦。本章将深入剖析其两大核心结构体——hw_module_t与hw_device_t,并详细拆解hw_get_module()函数的工作流程。
学习提示:传统HAL架构虽然已被Treble架构(HIDL)逐步取代,但大量存量设备、第三方驱动以及嵌入式Android系统仍广泛使用libhardware。理解其设计思想是掌握Android驱动开发的基石。
3.1 hw_module_t:HAL模块的“身份证”
hw_module_t是每个HAL模块必须首先暴露给上层的数据结构,它描述了模块的基本信息、版本、作者以及最重要的——指向模块中所有设备的操作入口。该结构体定义在hardware/libhardware/include/hardware/hardware.h中。
3.1.1 结构体定义与字段解析
typedef struct hw_module_t {
/** 标签,必须设置为 HARDWARE_MODULE_TAG */
uint32_t tag;
/** 模块主版本号 */
uint16_t version_major;
/** 模块次版本号 */
uint16_t version_minor;
/** 模块ID,用于唯一标识模块类型,如 "audio"、"gps"、"lights" */
const char *id;
/** 模块名称(可读字符串) */
const char *name;
/** 模块作者 */
const char *author;
/** 指向模块自定义方法的结构体指针,通常用于扩展 */
struct hw_module_methods_t *methods;
/** 模块的dlsym句柄,由HAL框架填充,模块自身不应修改 */
void *dso;
/** 预留字段,用于未来扩展,必须初始化为0 */
uint32_t reserved[32-7];
} hw_module_t;
关键字段解读:
- tag:验证结构体有效性的魔数,必须为
HARDWARE_MODULE_TAG(0x484D4F44)。 - id:模块的字符串标识,Framework通过此ID查找对应的共享库(如
audio.primary.so)。 - methods:指向
hw_module_methods_t的指针,该结构体目前只包含一个函数指针open,用于打开模块中的具体设备。 - dso:由
hw_get_module()在加载共享库后自动填充,指向动态库句柄,模块自身不应修改。
3.1.2 hw_module_methods_t:打开设备的唯一入口
typedef struct hw_module_methods_t {
/** 打开指定设备,返回hw_device_t指针 */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
该结构体是模块与设备之间的桥梁。每个HAL模块只需实现一个open函数,Framework通过调用它来实例化具体的设备操作接口。
3.2 hw_device_t:具体设备的操作句柄
hw_device_t代表一个具体的硬件设备实例。当Framework调用open()成功后,会获得一个指向hw_device_t的指针,通过该指针可以访问设备的所有操作函数。
3.2.1 结构体定义与字段解析
typedef struct hw_device_t {
/** 标签,必须设置为 HARDWARE_DEVICE_TAG */
uint32_t tag;
/** 设备版本号 */
uint32_t version;
/** 指向所属模块的指针,用于反向引用 */
struct hw_module_t* module;
/** 预留字段,必须初始化为0 */
uint32_t reserved[12];
/** 设备关闭函数指针 */
int (*close)(struct hw_device_t* device);
} hw_device_t;
设计要点:
hw_device_t是一个“基类”结构体,具体的设备类型(如audio_hw_device_t、gps_device_t)会在其基础上扩展,将hw_device_t作为第一个成员(C语言模拟继承)。- module字段指向设备所属的
hw_module_t,使得设备可以反向访问模块信息。 - close函数指针用于释放设备资源,由具体设备实现。
3.2.2 典型扩展示例:以GPS设备为例
/** GPS设备结构体,继承自 hw_device_t */
typedef struct gps_device_t {
/** 基类结构体,必须放在第一个字段 */
struct hw_device_t common;
/** GPS模块特有的操作接口 */
const struct gps_interface_t *gps_iface;
} gps_device_t;
/** GPS接口定义 */
typedef struct gps_interface_t {
int (*init)(struct gps_device_t* dev);
int (*start)(struct gps_device_t* dev);
int (*stop)(struct gps_device_t* dev);
void (*cleanup)(struct gps_device_t* dev);
} gps_interface_t;
在C语言中,通过将hw_device_t作为第一个成员,可以实现安全的类型转换:
// Framework 层获取设备
hw_device_t* dev = NULL;
module->methods->open(module, GPS_HARDWARE_MODULE_ID, &dev);
// 转换为具体的GPS设备类型
gps_device_t* gps_dev = (gps_device_t*)dev;
gps_dev->gps_iface->start(gps_dev);
3.3 hw_get_module():模块加载的核心函数
hw_get_module()是传统HAL架构中Framework加载HAL模块的入口函数。它根据模块ID,在系统指定路径下查找对应的共享库(.so),动态加载并返回模块的hw_module_t指针。
3.3.1 函数原型与参数
/**
* 加载指定ID的HAL模块
* @param id 模块ID,如 "audio"、"gps"、"gralloc"
* @param module 输出参数,指向加载成功的hw_module_t结构体
* @return 0 成功,负值 失败
*/
int hw_get_module(const char *id, const hw_module_t **module);
3.3.2 工作流程详解(6个步骤)
| 步骤 | 操作 | 关键函数/逻辑 |
|---|---|---|
| 1 | 检查参数有效性 | 验证id不为NULL,module指针有效 |
| 2 | 获取HAL库搜索路径列表 | 读取系统属性 ro.hardware、ro.board.platform 等,构建路径数组 |
| 3 | 遍历路径,尝试加载共享库 | 使用 dlopen() 打开 id.variant.so 文件 |
| 4 | 查找模块入口符号 | 使用 dlsym() 查找 HAL_MODULE_INFO_SYM 符号 |
| 5 | 验证模块结构体有效性 | 检查 tag 字段是否为 HARDWARE_MODULE_TAG,id 是否匹配 |
| 6 | 填充dso字段并返回 | 将 dlopen 返回的句柄赋值给 module->dso,通过输出参数返回 |
避坑指南:
- 搜索路径优先级:
/vendor/lib/hw/>/system/lib/hw/>/odm/lib/hw/。系统会优先加载vendor分区下的HAL库。 - 模块命名规则:
<id>.<variant>.so,例如audio.primary.so、gps.msm8998.so。variant通常对应硬件平台或设备类型。 - 如果
dlopen()失败,函数会尝试下一个路径;所有路径都失败后返回-ENOENT。
3.3.3 核心代码逻辑(简化版)
int hw_get_module(const char *id, const hw_module_t **module) {
int status = -ENOENT;
const char *hal_lib_path = NULL;
char prop_value[PROPERTY_VALUE_MAX] = {0};
// 1. 获取硬件平台变体
property_get("ro.hardware", prop_value, "");
if (prop_value[0] == '\0') {
property_get("ro.board.platform", prop_value, "");
}
// 2. 构建搜索路径列表
const char *search_paths[] = {
"/vendor/lib/hw",
"/system/lib/hw",
"/odm/lib/hw",
NULL
};
// 3. 遍历路径
for (int i = 0; search_paths[i] != NULL; i++) {
char lib_path[PATH_MAX];
// 构造完整路径: /vendor/lib/hw/audio.primary.so
snprintf(lib_path, sizeof(lib_path), "%s/%s.%s.so",
search_paths[i], id, prop_value);
// 4. 尝试加载共享库
void *handle = dlopen(lib_path, RTLD_NOW);
if (handle == NULL) {
continue; // 加载失败,尝试下一个路径
}
// 5. 查找模块入口符号
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hw_module_t *hmi = (hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
dlclose(handle);
continue;
}
// 6. 验证结构体有效性
if (hmi->tag != HARDWARE_MODULE_TAG ||
strcmp(hmi->id, id) != 0) {
dlclose(handle);
continue;
}
// 7. 填充dso并返回
hmi->dso = handle;
*module = hmi;
status = 0;
break;
}
return status;
}
3.4 模块定义与注册:HAL_MODULE_INFO_SYM
每个HAL共享库必须导出一个名为HAL_MODULE_INFO_SYM的全局符号,该符号指向模块的hw_module_t结构体实例。这是hw_get_module()通过dlsym()查找的目标。
3.4.1 标准定义宏
/**
* 定义HAL模块信息符号的宏
* 展开后为: __attribute__((visibility("default"))) hw_module_t HAL_MODULE_INFO_SYM
*/
#define HAL_MODULE_INFO_SYM HMI
#define HAL_MODULE_INFO_SYM_AS_STR "HMI"
3.4.2 典型模块定义示例
/* 1. 定义模块的open函数 */
static int gps_device_open(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device) {
if (strcmp(id, GPS_HARDWARE_MODULE_ID) != 0) {
return -EINVAL;
}
gps_device_t *dev = calloc(1, sizeof(gps_device_t));
if (!dev) return -ENOMEM;
/* 初始化基类 */
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t*)module;
dev->common.close = gps_device_close;
/* 初始化GPS特有接口 */
dev->gps_iface = &gps_interface;
*device = (hw_device_t*)dev;
return 0;
}
/* 2. 定义模块方法结构体 */
static struct hw_module_methods_t gps_module_methods = {
.open = gps_device_open,
};
/* 3. 定义并导出模块实例 */
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = GPS_HARDWARE_MODULE_ID,
.name = "GPS HAL Module",
.author = "Embedded Dev Team",
.methods = &gps_module_methods,
.dso = NULL, /* 由hw_get_module填充 */
.reserved = {0},
};
最佳实践:
- 始终使用
HAL_MODULE_INFO_SYM宏定义模块入口,不要手动命名符号。 - 在
open()函数中,必须根据id参数判断请求的设备类型,一个模块可能支持多个设备(如audio模块支持primary、a2dp、usb等)。 - 内存管理:
open()中分配的设备结构体,应在close()中释放。
3.5 本章小结
| 概念 | 核心作用 | 关键字段/函数 |
|---|---|---|
| hw_module_t | 描述HAL模块的元数据,是模块的入口 | id, methods, dso |
| hw_device_t | 具体设备实例的基类,包含关闭接口 | tag, module, close |
| hw_module_methods_t | 提供打开设备的函数指针 | open(module, id, device) |
| hw_get_module() | 根据ID动态加载HAL共享库 | dlopen → dlsym → 验证 → 返回 |
| HAL_MODULE_INFO_SYM | 共享库中必须导出的模块实例符号 | 全局 hw_module_t 变量 |