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通过调用它来实例化具体的设备操作接口。

hw_module_t 结构体关系图 hw_module_t tag: uint32_t version_major: uint16_t version_minor: uint16_t id: const char* name: const char* author: const char* methods: 指针 → dso: void* reserved[32-7] hw_module_methods_t open(module, id, device) → int (返回状态) 说明 每个HAL模块必须定义一个 hw_module_t 类型的全局变量 HAL_MODULE_INFO_SYM。 open() 是获取设备接口的唯一入口。

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);
hw_device_t 继承与扩展关系图 hw_device_t (基类) tag: uint32_t version: uint32_t module: hw_module_t* reserved[12] close(device): int gps_device_t common: hw_device_t gps_iface: 指针 → (start/stop/init等) audio_hw_device_t common + audio特有接口 C语言模拟继承 具体设备结构体将 hw_device_t 作为第一个 成员,实现“继承”。

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;
}
hw_get_module() 工作流程图 1. 检查参数有效性 2. 获取HAL库搜索路径 3. 遍历搜索路径列表 4. dlopen() 加载共享库 5. dlsym() 查找模块入口 6. 验证并返回 hw_module_t dlopen 失败? 尝试下一路径 关键路径优先级 1. /vendor/lib/hw/ 2. /system/lib/hw/ 3. /odm/lib/hw/ 模块名: id.variant.so

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 变量