Android系统HAL层驱动实现原理是基于谷歌提供的框架来展开,从而更好的规避因底层使用linux内核开源核心代码的要求,同时因为其安卓本身遵循Apache Lincence协议,允许厂商修改而不必公开修改的源代码。本文章就其HAL层(硬件抽象层),主要介绍此框架的组成部分、原理框架以及调用流程,为后续的HAL驱动具体实现提供相关的理论基础。
开胃菜-两种开源协议
GPL协议
- GPL,是GNU General Public License的缩写,是GNU通用公共授权非正式的中文翻译。它并非由自由软件基金会所发表,亦非使用GNU通用公共授权的软件的法定发布条款─只有GNU通用公共授权英文原文的版本始具有此等效力。
- Linux内核源代码遵循的是GPL协议,即对内核源代码进行修改必须将其公开。这带来的问题是如果将驱动的实现细节封装在内核中,就必须公开源代码,这无疑是大大的损坏了众多驱动厂商的经济利益。
Apache Licence 协议
- apache licence(Apache许可证),是Apache软件基金会发布的一个自由软件许可证。
- 安卓系统源代码遵循此协议,允许厂商自由修改源代码而不必公开实现的细节。如果可以讲相关的驱动实现细节放到源代码中(及不在内核空间实现),使驱动上移到用户空间中,就能很好的规避这个问题,既维护了第三方厂商的利益,而且还有助于商业的快速推广。
HAL层驱动总体概括
基础架构概括
由于安卓本身基于Linux系统,与硬件直接打交道的驱动实现必须实现在内核空间中。但是由于Linxu协议问题,要求必须公示修改的源代码。最好的方法就是将驱动分别实现于内核空间和用户空间。
- 内核空间:以硬件驱动模块形式支持,但是只是简单的提供硬件的访问通道。
- 用户空间:以硬件抽象层模块形式支持,封装硬件的实现细节和参数。
这样,通过上面的分析可以看出,这种折中的方式很好的规避协议带来的问题。驱动开发者只需要在内核中实现相关驱动的硬件硬件接口,将具体驱动的实现细节上升到HAl层实现即可,完全可以将自己的商业利益最大化。
HAL架构
HAL层中重要的三个结构体如下图所示,所有的驱动开发厂商都依照这个框架进行实现自己的驱动程序。

函数指针
由于驱动层的开发基本都使用c/c++实现,但是由于c没有类似面向对象的类概念,但是有结构体可以代替。但是结构体又不能直接存储函数实现,就必须依照指针声明函数来实现,在结构体声明完毕后,需要将结构体的指针进行赋值即可实现类似面向对象的功能。
三个重要的结构体
- struct hw_device_t: 硬件抽象层的设备基类,所有的具体设备驱动必须实现此基类,其内部函数指针others由用户自行定义,扩充自己的驱动实现。
- struct hw_module_t: 硬件抽象层的模块基类,一个模块中可以包含多个设备,驱动开发者同样要对这个基类实现自己的驱动模块。内部含有一个重要的成员methods,其对应结构体hw_module_methods_t,在驱动的实现中就静态的实现函数的指向,方便后续的打开操作。其内部others为用户扩充驱动实现。
- struct hw_module_methods_t: 硬件抽象层的函数表定义。其内部只有一个重要的指针函数 open(),其具体功能是协助模块打开设备并完成结构体中函数指针的赋值等重要操作。
模块导出符号HMI
在谷歌硬件抽象层模块的编写规范中,其硬件抽象层的每一个模块都必须存在一个符号 HAL_MODULE_INFO_SYM,此符号宏定义于框架中,值为“HMI”。其指向一个自定义的硬件抽象层模块结构体,方便系统加载*.so时辩之为对应的硬件抽象层驱动模块,在后面的模版代码中会详细介绍。
驱动的命名
HAL模块驱动以*.so文件存在于 /system/lib/hw 和 /system/lib64/hw 下,其拥有独特的命名方式,具体参考可在 hardware/libandroid/hardware.c 中。
/**
* 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
*/
- 硬件抽象层模块文件的命名规范为“<MODULE_ID>.variant.so”,
- 其中,MODULE_ID代表模块的ID,variant表示四个系统属性 ro.hardware, ro.pproduct.board, ro.board.platform, ro.arch 之一。
- 系统在加载硬件抽象层模块是会依次取出其值,根据对应值到文件名称,检查文件是否存在。存在便找到了加载的硬件抽象层模块,否则便继续查找下一个属性。
- 如果四个属性对应的文件都不存在,便使用“<MODULE_ID>.defaule.so”作为加载的硬件抽象层模块文件。
驱动的调用
HAL框架提供了一个公用的函数 hw_get_module 实现一键加载hal驱动。其大致调用流程如下:
- 传入模块id,根据module_id去查找注册在当前系统中与id对应的硬件对象
- 然后载入(load)其相应的HAL层驱动模块的 *so 文件
- 从*.so里查找”HMI”这个符号,如果在so 代码里有定义的函数名或变量名为HMI,返回其地址
图示如下:

HAL模版代码
头文件
HAL层头文件路径一般位于/hardware/libhardware/include/hardware/*下。头文件除了在编写中还需要#include <hardware/hardware.h>这个HAL层关键的头文件,其文件定于本路径的上级include同级目录下。
头文件模版代码如下:
#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
#define LED_HARDWARE_MODULE_ID "led"
#define LED_HARDWARE_DEVICE_ID "led"
typedef struct led_module_t {
struct hw_module_t common;
} led_module_t;
typedef struct led_device_t {
struct hw_device_t common;
others (void*);
} led_device_t;
static inline int led_device_open(const struct hw_module_t* module,
led_device_t** dev){
return module->methods->open(module,LED_HARDWARE_DEVICE_ID,
(struct hw_device_t**) dev);
}
static inline int led_device_close(led_device_t* dev){
return dev->common.close(&dev->common);
}
__END_DECLS
#endif
- 模版代码中,宏定义模块及设备id,方便c层文件导包导入so模块。
- 结构体 struct led_module_t 是用户扩充框架中的 struct hw_module_t 结构体,其内部成员commom必须放在第一个位置,保证led_module_t的地址和hw_module_t相一致。此模版为源码中约定俗成的固定写法,用户实现参考即可。
- 结构体 struct led_device_t 扩充框架中的 struct hw_device_t 结构体。其为设备的具体信息,包括tag,version,id等等。后年的赋值操作会涉及到。
- typedef 用于提前声明变量,便于在后续导包开发中避免多次声明指定的结构体变量。
- 两个静态inline内联函数,方便模块设备的打开以及关闭。其也可不必放在头文件中。这里参考源码中的形式放于此处。
驱动本体实现
驱动的本体实现代码路径位于 /hardware/libhardware/modules/led/*下,其中的最后的led为实际模块的名称文件夹。其同级文件下还有一个mk编译文件,方便生成指定的动态链接库。
具体实现参考代码如下:
#define LOG_TAG "LedHALStub"
#include <hardware/hardware.h>
#include <hardware/led.h>
#include <cutils/log.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>
#include <memory.h>
#define MODULE_NAME "Led"
#define MODULE_AUTHOR "891904833@qq.com"
static int led_set_on(led_device_t* dev) {...}
static int led_set_off(led_device_t* dev) {...}
static int led_getCount(led_device_t* device) {...}
static int led_close(hw_device_t* device) {
if(device){
free(device);
}
return 0;
}
static int led_open(const hw_module_t* module, const char* id,
hw_device_t** device) {
led_device_t *dev = (led_device_t *)calloc(1, sizeof(led_device_t));
if(!dev) {
ALOGE("Failed to alloc space for led_device_t.");
return -EFAULT;
}
memset(dev, 0, sizeof(struct led_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
dev->common.close = led_close;
dev->set_on = led_set_on;
dev->set_off = led_set_off;
dev->getCount = led_getCount;
*device = &dev->common;
ALOGI("Open led_device successfully!");
return 0;
}
static struct hw_module_methods_t my_methods = {
.open = led_open,
};
struct led_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = LED_HARDWARE_MODULE_ID,
.name = MODULE_NAME,
.author = MODULE_AUTHOR,
.methods = &my_methods,
},
};
- 首先代码最后声明结构体 led_module_t HAL_MODULE_INFO_SYM ,方便系统根据模块id导入合适的库文件,其第一个成员变量的类型必须为 hw_module_t,从中我们可以看出common即为指定的结构体变量。其次是common中的第一成员id,必须指定为HARDWARE_MODULE_TAG。方便用户调用 hw_get_module 传入模块名称找到对应的模块设备。同时此模块的另些属性也在此期间进行赋值。
- struct hw_module_methods_t 为模版结构体中重要的函数指针映射。其来源于导库操作中的最后一个成员的赋值 .methods = &my_methods,通过函数指针其最终指向led_open这个方法,协助设备的打开操作。
- led_open 方法具体为设备的打开过程。通过 calloc 操作分配内存,memset 实现内存的初始化。其中需要注意设备的 close 方法属于 dev->common.close 内。通过指针赋值,预先将结构体中相关的函数指针填充,为后续调用做基础。
- 最上面的static int(void)** 方法为用户自定义驱动具体实现,这里省略。
- 在头文件中涉及到设备的打开,具体是通过框架提供的 hw_module_t 结构体中的 methods 方法 open 函数实现, 即 module->methods->open(module,LED_HARDWARE_DEVICE_ID,
(struct hw_device_t**) dev); - 同样设备的关闭操作通过 struct hw_device_t 的 close 方法实现,即 dev->common.close(&dev->common);
打开驱动设备代码预览
驱动本体实现完成后,通过mk文件打包成 *.so 文件,生成于 /system/lib/hw 和 /system/lib64/hw 下。通过上层c文件实现驱动的打开调用,基本参考代码流程如下:
static jint led_init(JNIEnv *env, jobject instance){
led_module_t* module;
ALOGI("led_init start ...");
if (hw_get_module(LED_HARDWARE_MODULE_ID, (hw_module_t const**)&module) == 0) {
ALOGI("Device led found...");
if (led_device_open(&(module->common), &mLedDevice ) == 0) {
ALOGI("led_init success!!!");
return 0;
}
ALOGE("led_init error!!!");
}
ALOGI("hw_get_module function call error!!!");
return -1;
}
- 通过 hw_get_module 方法,传入模块id,找到指定模块。
- 通过 led_device_open 方法,打开设备,将设备实例放于变量 mLedDevice 中。
- 通过获取到的设备实例 mLedDevice ,直接调用其具体驱动方法即可。
HAL层驱动调用总结
根据HAL层驱动框架的介绍,分析出驱动模块的具体流程,首先来看一下代码的总体结构,如下图:

找到 hw_get_module
根据HAL框架提供的公有函数 hw_get_module ,传入模块设备名称,在导入的so库文件中根据 HAL_MODULE_INFO_SYM 识别出驱动模块后,找到对应的具体 id 驱动模块,并在运行时完成结构体中相关指针函数的赋值。其中涉及到open方法的映射,方便设备的进一步打开。
open 打开设备分配内存
在上述流程走完之后,相关结构体中的函数指针均已经赋值完成,继而调用框架提供的 hw_module_t 结构体中的 methods 方法 open 函数实现设备的打开,设备打开过程中,便实现了用户自有驱动实现方法函数指针的赋值过程,此流程走完之后,便可以通过自定的 结构体(扩充 hw_device_t 实现的)调用自有驱动逻辑。
获取自有设备驱动实例
在open方法调用后,完成了设备实例结构体内具体函数指针的赋值,此时用户便可以通过获取的设备实例直接调用自有结构体内的指针函数,实现驱动的具体调用。
总结
通过上面的介绍分析,总体知晓了安卓硬件抽象层的工作流程,后续会针对此部分来一篇实战博客进一步加强。谷歌通过硬件抽象层框架,成功将驱动分别实现在内核和用户空间,通过内核空间和用户空间的配合,实现驱动的无缝链接。此举一方面维护了众多厂商的商业利益,另一方面也遵循了GPL协议和Apache Licence协议,真可谓一举两的。在这里不由得赞叹谷歌架构师的超高水平。
其他
参考书籍文章如下:
高焕堂架构师系列视频
Android系统源代码情景分析