Android HAL硬件抽象层原理
前言
先来看下 Android 的系统架构图:
Android系统由上至下,分为五个部分:
- 应用程序层(System Apps) :Android 装配了一个核心应用程序集合,包括 E-mail 客户端、SMS 短消息程序、日历、地图、浏览器、联系人管理程序和其他程序,所有应用程序都是用 Java 编程语言编写的。用户开发的 Android 应用程序和 Android 的核心应用程序是同一层次的,它们都是基于 Android 的系统 API 构建的。(即所有安装到手机的应用程序都属于这一层,比如系统自带的联系人、短信等,或者是你从应用商店下载的所有app,当然也包括你自己开发的应用程序)
- 应用程序框架层(Application Framework/Java API Framework) :应用程序的体系结构旨在简化组件的重用,任何应用程序都能发布它的功能且任何其他应用程序都可以使用这些功能(需要服从框架执行的安全限制),这一机制允许用户替换组件。开发者完全可以访问核心应用程序所使用的 API 框架。通过提供开放的开发平台,Android 使开发者能够编制极其丰富和新颖的应用程序。开发者可以自由地利用设备硬件优势访问位置信息、运行后台服务、设置闹钟、向状态栏添加通知等。(这一层主要提供了构建应用程序时可能用到的各种API,Android自带的一些核心应用就是使用这些API完成的,开发者也可以自行使用这些API进行自己App的开发。)这一层是编写Google发布的核心应用时所使用的API框架,开发人员同样可以使用这些框架来开发自己的应用,这样便简化了程序开发的结构设计,但是必须要遵守其框架的开发原则。
所有的应用程序都是由一系列的服务和系统组成的,主要包括以下几种:
| 系统/服务 | 英文名称 | 说明 |
|---|---|---|
| 视图 | View | 这里的视图指的是丰富的、可扩展的视图集合,可用于构建一个应用程序,包括列表 (Lists)、网格 (Grids)、文本框 (TextBoxes)、按钮 (Buttons),甚至是内嵌的 Web 浏览器。 |
| 内容管理器 | Content Provider | 内容管理器使得应用程序可以访问另一个应用程序的数据(如联系人数据库)或者共享自己的数据。 |
| 资源管理器 | Resource Manager | 资源管理器提供访问非代码资源,如本地字符串、图形和分层文件 (layout files)。 |
| 通知管理器 | Notification Manager | 通知管理器使得所有的应用程序都能够在状态栏显示通知信息。 |
| 活动管理器 | Activity Manager | 在大多数情况下,每个 Android 应用程序都运行在自己的 Linux 进程中。当应用程序的某些代码需要运行时,这个进程就被创建并一直运行下去,直到系统认为该进程不再有用为止,然后系统将回收该进程占用的内存以便分配给其他的应用程序。活动管理器管理应用程序生命周期,并且提供通用的导航回退功能。 |
- 系统运行库层(C/C++库及Android运行库) :Android 本地框架是由 C/C++ 实现的,包含 C/C++ 库,以供 Android 系统的各个组件使用。这些功能通过 Android 的应用程序框架为开发者提供服务。这一层通过一些C/C++库来为Android提供主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。同样在这一层还要Android运行时库,它主要提供了一些核心库,能够允许开发者使用Java语言来编写Android程序。不仅如此,它还包括了Dalvik虚拟机(android5.0后改为ART运行环境),它使得每一个android程序可以运行在独立的进程中,并且拥有自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。
| 名称 | 说明 |
|---|---|
| 系统C语言库 | 标准C语言系统库 (libc) 的 BSD 衍生,调整为基于嵌入式 Linux 设备。 |
| 媒体库 | 基于 PacketVideo 的 OpenCORE,这些库支持播放和录制许多流行的音频和视频格式,以及静态图像文件,包括 MPEG4、H.264、MP3、AAC、AMR、JPG、PNG。 |
| 界面管理 | 管理访问显示子系统,并且为多个应用程序提供 2D 和 3D 图层的无缝融合。 |
| LibWebCore | 新式的 Web 浏览器引擎,支持 Android 浏览器和内嵌的 Web 视图。 |
| SGL | 一个内置的 2D 图形引擎。 |
| 3D 库 | 基于 OpenGL ES 1.0 APIs 实现,该库可以使用硬件 3D 加速或包含高度优化的 3D 软件光栅。 |
| FreeType | 位图和矢量字体显示渲染。 |
| SQLite | SQLite 是一个所有应用程序都可以使用的强大且轻量级的关系数据库引擎。 |
- 硬件抽象层( Hardware Abstraction Layer,HAL) :硬件抽象层 (HAL) 提供标准界面,向更高级别的 Java API 框架显示设备硬件功能。HAL 包含多个库模块,其中每个模块都为特定类型的硬件组件实现一个界面,例如:Audio音频模块,BlueTooth:蓝牙模块,Camera:相机模块,Sensors:传感器。系统内置对传感器的支持达13种,他们分别是:加速度传感器(accelerometer)、磁力传感器(magnetic field)、方向传感器(orientation)、陀螺仪(gyroscope)、环境光照传感器(light)、压力传感器(pressure)、温度传感器(temperature)和距离传感器(proximity)等。当框架 API 要求访问设备硬件时,Android 系统将为该硬件组件加载库模块。厂商会在这层定义自己的HAL接口。
- Linux内核层(Linux Kernel) :众所周知,Android系统是基于Linux内核层提供核心系统服务,例如安全、内存管理、进程间通信、网络堆栈、驱动模型。这一层为android设备的各种硬件提供了底层的强大驱动,除了标准Linux内核外,Android还增加了内核的驱动程序,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。
为什么需要HAL?
HAL 全称 Hardware Abstract Layer,即硬件抽象层,它向下屏蔽了硬件的实现细节,向上提供了抽象接口,HAL是底层硬件和上层框架直接的接口,框架层通过HAL可以操作硬件设备。
许多早期的计算机系统没有任何形式的硬件抽象,这意味着为该系统编写程序的任何人都必须知道每个硬件设备如何与系统的其余部分进行通信。这对软件开发人员来说是一个巨大的挑战,因为他们必须知道系统中每个硬件设备如何工作才能确保软件的兼容性。使用硬件抽象,而不是直接与硬件设备通信的程序,它将程序传达给操作系统该设备应执行的操作,然后操作系统会向该设备生成硬件相关的指令。这意味着程序员不需要知道特定设备的工作方式,就能使他们的程序与设备兼容。
我们知道 Android 是基于 Linux 进行开发的,传统的 Linux 对硬件的操作基本上都是在内核中,而 Android 把对硬件的操作分为了两部分,HAL 和内核驱动,HAL 实现在用户空间,驱动在内核空间。这是因为 Linux 内核中的代码是需要开源的(遵循GUN License),如果把对硬件的操作放在内核这会损害硬件厂商的利益,因为这是别人厂商的商业机密,而现在有了 HAL 层位于用户空间(该部分代码遵循Apache License,无需对外开放源代码),硬件厂商就可以将自己的核心算法之类的放在 HAL 层,保护了自己的利益,这样 Android 系统才能得到更多厂商的支持。
总结HAL的意义有以下两个方面:
(1)HAL层屏蔽掉不同硬件设备的差异,为Android提供了统一的设备访问接口。不同的硬件厂商遵循HAL标准来实现自己的硬件控制逻辑,开发者不必关心硬件设备的差异,只需按照HAL提供的标准接口对硬件进行访问即可。
(2)HAL层帮助硬件厂商隐藏了设备的核心细节,HAL层位于用户空间,遵循Apache协议,允许硬件厂商不公开源码,将设备相关的实现放在Android系统中HAL具有两种实现方式:Legacy以及Stub HAL,初期使用的是Legacy HAL的方式,该方式为标准的Linux共享库,其它应用程序直接调用HAL层共享库导出的函数。Google后来提出了Stub HAL的方式,仍然以共享库(.so)的形式提供,它把所有供外部访问的的方法(函数)的入口指针保存在统一的数据结构,其它程序需要访问HAL中方法时,需要先获得Stub,然后通过具体的函数指针去读写底层设备。(详细可参考文章3)HAL层中实现,并以共享库(.so)的形式进行提供。
Windows 下的 HAL 位于操作系统的最底层,它直接操作物理硬件设备,使用抽象接口来隔离不同硬件的具体实现,为上层的操作系统和设备驱动程序提供一个统一接口,起到对硬件抽象作用。Linux 下的 HAL 与 Windows 不同,HAL 层并不位于操作系统的最底层直接操作硬件,而是在操作系统内核层和驱动程序之上,是一个运行在 User Space(用户空间) 的服务程序。
一、Android源码的下载与编译
二、HAL体系结构
1、HAL层的源码目录结构主要如下:
1)一般除了以下三个以外都是硬件厂商相关的hal目录
/hardware/libhardware_legacy/ 旧的架构、采取链接库模块的方式
/hardware/libhardware/
/hardware/ril/ 无线电抽象层
2)libhardware 目录的结构如下:
/hardware/libhardware/hardware.c 编译成libhardware.s置于/system/lib
3)/hardware/libhardware/include/hardware目录下包含如下头文件:
hardware.h 通用硬件模块头文件
copybit.h copybit模块头文件
gralloc.h gralloc模块头文件
lights.h 背光模块头文件
overlay.h overlay模块头文件
qemud.h qemud模块头文件
sensors.h 传感器模块头文件
.....
4)/hardware/libhardware/modules 目录下定义了很多硬件模块:
/hardware/msm7k
/hardware/qcom
/hardware/ti
/device/Samstruct hw_module_t;
struct hw_module_methods_t;硬件的操作接口集合使用hw_device_t来描述,并可以通过自定义一个更大的包含hw_device_t的结构体来拓展硬件操作集合
struct hw_device_t;sung
/device/moto
.....
这些硬件模块都编译成xxx.xxx.so,目标位置为/system/lib/hw目录
Android系统中HAL具有两种实现方式:Legacy以及Stub HAL,初期使用的是Legacy HAL的方式,该方式为标准的Linux共享库,其它应用程序直接调用HAL层共享库导出的函数。Google后来提出了Stub HAL的方式,仍然以共享库(.so)的形式提供,它把所有供外部访问的的方法(函数)的入口指针保存在统一的数据结构,其它程序需要访问HAL中方法时,需要先获得Stub,然后通过具体的函数指针去读写底层设备。(详细可参考文章3)
2、HAL层三个重要结构体:
代码位置:
定义:hardware/libhardware/include/hardware/hardware.h
实现:hardware/libhardware/hardware.c
Android系统的HAL层并不复杂,主要包含下面3个关键结构体:
struct hw_module_t; //每一个硬件都通过hw_module_t来描述,具有固定的名字HMI
struct hw_module_methods_t; //每一个硬件都必须实现hw_module_t里面的open方法,用于打开硬件设备,并返回对应的操作接口集合
struct hw_device_t; //硬件的操作接口集合使用hw_device_t来描述,并可以通过自定义一个更大的包含hw_device_t的结构体来拓展硬件操作集合
其中头文件hardware.h代码如下:(删除部分源码,留下主体,完整请看源码)
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;
uint16_t module_api_version;
#define version_major module_api_version
uint16_t hal_api_version;
#define version_minor hal_api_version
/** Identifier of module */
const char *id;
/** Name of this module */
const char *name;
/** Author/owner/implementor of the module */
const char *author;
/** Modules methods */
struct hw_module_methods_t* methods; //硬件模块方法
/** module's dshw_get_module_by_classo */
void* dso;
/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];
} hw_module_t;
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** padding reserved for future use */
uint32_t reserved[12];
/** Close this device */
int (*close)(struct hw_device_t* device);
} hw_device_t;
//以下为其中的两个方法
int hw_get_module(const char *id, const struct hw_module_t **module);
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module);
3、HAL层三个重要结构体详解
Android HAL 将各类硬件设备抽象为硬件模块,HAL 使用hw_module_t结构体描述一类硬件抽象模块。每个硬件抽象模块都对应一个动态链接库,一般是由厂商提供的,这个动态链接库必须尊重 HAL 的命名规范才能被 HAL 加载到,我们后面会看到。每一类硬件抽象模块又包含多个独立的硬件设备,HAL 使用hw_device_t结构体描述硬件模块中的独立硬件设备。下面来分析下 HAL moudle 的三个结构体。
(1)首先看下通用硬件模块结构体 hw_module_t ,定义在 hardware/libhardware/include/
hardware/hardware.h 文件中:
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;
uint16_t module_api_version;
#define version_major module_api_version
uint16_t hal_api_version;
#define version_minor hal_api_version
const char *id;
const char *name;
const char *author;
struct hw_module_methods_t* methods;
void* dso;
#ifdef __LP64__
uint64_t reserved[32-7];
#else
uint32_t reserved[32-7];
#endif
} hw_module_t;
需要注意:
- 每一个硬件模块都必须有一个名为HAL_MODULE_INFO_SYM命名的结构,而且这个结构的第一个成员变量必须以hw_module_t开头,作为模块特定的信息。(这里可以理解为是一种继承关系,相当于硬件模块的HAL_MODULE_INFO_SYM结构体,继承了hw_module_t,只不过是 C 语言中没有继承的概念,是通过在结构体中包含的方式间接实现的)
- HAL_MODULE_INFO_SYM指向一个自定义硬件抽象层模块结构体。
- tag的值必须为HARDWARE_MODULE_TAG, 用来标志这是一个硬件抽象层模块结构体。
- dso用来保存加载硬件抽象层模块后得到的句柄值。因为每一个硬件抽象层模块都对应以一个动态链接库文件。加载硬件抽象层的过程实际就是调用 dlopen 来加载对应的硬件动态链接库的过程。对应于调用 dlclose 的时候需要卸载对应的硬件抽象层需要用到这个句柄,所以要将其保存到这个成员变量中。
- hw_module_methods_t 定义了硬件抽象层模块的操作方法列表。
硬件抽象层HAL由一个一个的模块组成,Android规定,每一个模块都是一个命名为HAL_MODULE_INFO_SYM的自定义结构体,并且该结构体的第一个成员必须为hw_module_t类型的变量,其它成员变量根据需要由开发者设置。以sensor模块为例,sensor 模块对应的结构体定义在hardware/libhardware/include/hardware/sensors.h文件中
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
struct sensors_module_t {
struct hw_module_t common;
/**
* Enumerate all available sensors. The list is returned in "list".
* return number of sensors in the list
*/
int (*get_sensors_list)(struct sensors_module_t* module,
struct sensor_t const** list);
/**
* Place the module in a specific mode. The following modes are defined
*
* 0 - Normal operation. Default state of the module.
* 1 - Loopback mode. Data is injected for the supported
* sensors by the sensor service in this mode.
* return 0 on success
* -EINVAL if requested mode is not supported
* -EPERM if operation is not allowed
*/
int (*set_operation_mode)(unsigned int mode);
};
/*
* The lights Module
*/
struct light_module_t HAL_MODULE_INFO_SYM = {
.common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: LIGHTS_HARDWARE_MODULE_ID,
name: "Lights module",
author: "Rockchip",
methods: &light_module_methods,
}
};
const struct sensors_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = SENSORS_HARDWARE_MODULE_ID,
.name = "Stingray SENSORS Module",
.author = "Motorola",
.methods = &sensors_module_methods,
},
.get_sensors_list = sensors__get_sensors_list
};
(2)在 hw_module_t 中比较重要的是硬件模块方法结构体hw_module_methods_t,该方法只有一个函数指针 open,该方法在定义HAL_MODULE_INFO_SYM的时候被初始化。根据参数可以推断出 open 是用来打开硬件模块获取模块中的硬件设备。由于一个硬件抽象模块中可能包含多个设备,因此需要根据传入的设备id来获取相应的硬件设备hw_device_t。所以这里的 device 就表示一个已经打开的硬件设备。
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
一个指针函数用来打开硬件抽象层模块中的硬件设备,其输入参数:
- module:硬件设备所在模块
- id:要打开硬件设备对应的ID。因为硬件抽象层中可能有很多设备,所以打开时候需要制定ID。
- device:输出参数,用来描述一个已经打开的硬件设备。
(3)hw_module_methods_t中调用的设备结构体参数 hw_device_t ,前面提到 HAL 使用hw_device_t结构体描述硬件模块中的独立硬件设备,其定义如下:
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** padding reserved for future use */
#ifdef __LP64__
uint64_t reserved[12];
#else
uint32_t reserved[12];
#endif
/** Close this device */
int (*close)(struct hw_device_t* device);
} hw_device_t;
HAL 规定每个硬件设备都必须定义一个硬件设备描述结构体,该结构体必须以hw_device_t作为第一个成员变量,后跟设备相关的公开函数和属性。
- tag:初始化为常量 HARDWARE_DEVICE_TAG;
- module:表示该硬件设备归属于哪一个硬件抽象模块;
- close:函数指针,用来关闭硬件设备。
到此,HAL 的 3 个核心数据结构体就分析完了。硬件厂商必须遵循HAL规范和命名,实现抽象硬件模块结构体 hw_module_t 和抽象硬件设备结构体hw_device_t,并且在硬件模块方法结构体hw_module_methods_t中提供 open 函数来获取 hw_device_t。下面我们来看看 HAL 到底是怎样获取硬件模块和硬件设备的,以及是如何加载和解析对应的动态共享库的。
三、模块实现方法
1、321架构
在Android的HAL框架使用通用的 “321架构” ,也就是三个结构体,两个常量,一个函数。所有硬件抽象模块都遵循321架构,再此基础上扩展自有的功能。三个结构体以及分析完了,接下来看下两个常量:
/**
* Name of the hal_module_info
*/
#define HAL_MODULE_INFO_SYM HMI
/**
* Name of the hal_module_info as a string
*/
#define HAL_MODULE_INFO_SYM_AS_STR "HMI"
最后看一下“一个函数”,在 hardware.c 中有一个关键的公共 API,hw_get_module,其作用是根据 module_id 去查找注册相对应的硬件对象,然后载入相应的HAL层驱动模块的 so 文件:
int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
HAL整体获取Module和Device结构对象的流程,如下图所示:
2、Android如何使用硬件抽象层(hardware.c)
硬件抽象层的作用是对上层Application Framework屏蔽Linux底层驱动程序,那么Application Framework与硬件抽象层通信的接口是谁呢?答案是hw_get_module函数,该函数定义在hardware/libhardware/hardware.c文件中:
****hw_get_module 函数的作用是由第一个参数 id 指定的模块 ID ,找到模块对应的 hw_module_t 结构体,保存在参数 module中。
int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int status;
int i;
const struct hw_module_t *hmi = NULL;
char prop[PATH_MAX];
char path[PATH_MAX];
char name[PATH_MAX];
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, prop);
if (access(path, R_OK) == 0) break;
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, prop);
if (access(path, R_OK) == 0) break;
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH2, name);
if (access(path, R_OK) == 0) break;
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH1, name);
if (access(path, R_OK) == 0) break;
}
}
status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+1) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(class_id, path, module);
}
return status;
}
首先通过for循环获取模块名及路径,保存在path中。循环次数为 HAL_VARIANT_KEYS_COUNT 次,HAL_VARIANT_KEYS_COUNT 是下面要用到的 variant_keys 数组的数组元素个数。
为了说明这个for循环是如何获得模块名及其路径,我们要先来看一下variant_keys数组的定义:
/**
* 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
*/
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
上面的注释说明了 module文件的命名规则 <MODULE_ID>.variant.so
1、MODULE_ID是模块对应的ID ,不同模块对应一个唯一固定的ID。
2、那么variant是什么呢?又怎么获得variant呢?这就跟下面的variant_keys数组有关了。
定义的variant_keys[] 有四个值 "ro.hardware" 、"ro.product.board"、 "ro.board.platform"、"ro.arch" 的四个字符串的指针, 可以把这些值理解为属性,系统会对这 些值进行属性的set 。
- ro.hardware”属性的属性值是在系统启动时由init进程负责设置的。它首先会读取/proc/cmdline文件,检查里面有没有一个名为androidboot.hardware的属性,如果有,就把它的值赋值给“ro.hardware”,否则,就将/proc/cpuinfo文件的内容读取出来,并解析出Haredware字段的内容赋值给“ro.hardware”。例如在Android模拟器中,从/proc/cpuinfo文件中读取出来的Hardware字段内容为goldfish,于是,init进程就会将 “ro.hardware” 属性设置为goldfish。
- “ ro.product.board”、“ ro.board.platform”、“ ro.arch”属性是从/system/build.prop文件读取出来的。/system/build.prop文件是由编译系统中的编译脚本build/core/Makefile和shell脚本build/tools/buildinfo.sh生成的,这里不再详细分析。
3、HAL_VARIANT_KEYS_COUNT变量,它是variant_keys数组的大小。
4、通过 snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH2, name, prop)找到 对应的so 文件路径 保存在path中。
5、if (access(path, R_OK) == 0) break; 通过access 来读取动态链接库是否存在。
6、如果没有找到variant_keys[i]对应的属性,则使用<MODULE_ID>.default.so
7、最后调用 load 方法来尝试打开在上面找到的so库,并获得hw_module_t 结构体。
/**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status;
void *handle;
struct hw_module_t *hmi;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
if (strcmp(id, hmi->id) != 0) {
ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
}
hmi->dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, *pHmi, handle);
}
*pHmi = hmi;
return status;
}
通过dlopen 方法打开so文件 得到的handle句柄,保存在 hw_module_t 的 dso 中(方便我们后面调用 close 方法来卸载对应的硬件)。最后将 hw_module_t 结构赋值给传递进来的参数 pHmi,即返回给上层调用函数。
分析到这里,我们可以看出,通过hw_get_module函数,Application Framework代码可以通过指定的模块ID找到模块hw_module_t结构体。有了hw_module_t结构体,就可以调用hw_module_t-> methods->open函数,在open函数中,完成对设备对应的hw_device_t结构体的初始化,并指定设备相关的自定义函数。
3、总结
硬件抽象层加载一个模块无非就是:
- 调用者传目标模块的ID参数到加载函数 。
- 获取目标模块的存储路径。
- 检查该路径下是否有该模块的.so文件。
- 加载模块至内存。
- 将句柄返回给调用者。
四、系统HAL模块源码分析
1、lights模块分析
其源文件位置:
/frameworks/base/services/core/java/com/android/server/lights/
/frameworks/base/services/core/jni/com_android_server_lights_LightsService.cpp
/hardware/libhardware/include/hardware/lights.h
访问设备的流程:
app--->frameworks--->hardware--->kernel驱动
(1)frameworks通过JNI调用hw_get_module()得 HAL 对应的模块,在 lights.h中定义有 lights 模块的ID:
#define LIGHTS_HARDWARE_MODULE_ID "lights"
(2)frameworks/base/services/core/jni/com_android_server_LightsService.cpp的init_native 方法中调用hw_ge_module(),代码如下:
static jint init_native(JNIEnv *env, jobject clazz)
{
int err;
hw_module_t* module;
Devices* devices;
devices = (Devices*)malloc(sizeof(Devices));
err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
devices->lights[LIGHT_INDEX_BACKLIGHT]
= get_device(module, LIGHT_ID_BACKLIGHT);
//………………………………………….
}
(3)由上已知hw_get_module函数在 hardware.c 中实现:
int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
(4)来看看hw_get_module_by_class是如何实现的:
/** Base path of the hal modules */
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
/**
* 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
*/
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int i = 0;
char prop[PATH_MAX] = {0};
char path[PATH_MAX] = {0};
char name[PATH_MAX] = {0};
char prop_name[PATH_MAX] = {0};
//根据id生成module name,这里inst为NULL
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* First try a property specific to the class and possibly instance */
//首先查询特定的属性名称来获取variant值
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) > 0) {
//检查目标模块共享库是否存在
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found; //存在,找到了
}
}
/* Loop through the configuration variants looking for a module */
//逐一查询variant_keys数组定义的属性名称
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
//检查目标模块共享库是否存在
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
//没有找到,尝试默认variant名称为default的共享库
/* Nothing found, try the default */
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
found:
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
return load(class_id, path, module); //执行加载和解析共享库的工作
}
可以看到,在hw_get_module_by_class函数中:
- 首先根据 class_id 生成 module name,这里就是hw_get_module函数传进来的 id;
- 再通过 property_get 获得 varient_key 中定义的系统属性,如果系统中有定义该属性,就会获得一个模块名.属性名组成的一个 so 的名称;
- 然后去/system/lib/hw、/vendor/lib/hw下查看,该 so 是否存在,如果存在,调用 load 函数,打开.so文件;
- 如果不存在,则遍历 variant_keys 数组中定义的属性名称来获取属性值,得到目标模块库名字,检查其是否存在;
- 如果根据属性值都没有找到模块共享库,则尝试检查 default 的库是否存在,如果仍然不存在,返回错误;
- 如果上述任何一次尝试找到了目标共享库,path 就是目标共享库的文件路径,调用 load 执行真正的加载库的工作。
(5)再看 load 函数的实现:
/**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status = -EINVAL;
void *handle = NULL;
struct hw_module_t *hmi = NULL;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
//使用dlopen打开path定义的目标共享库,得到库文件的句柄handle
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
//出错,通过dlerror获取错误信息
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR; //"HMI"
//使用dlsym找到符号为“HMI”的地址,这里应该是hw_module_t结构体的地址;并且赋给hmi
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
//检查模块id是否匹配
if (strcmp(id, hmi->id) != 0) {
ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
}
//保存共享库文件的句柄
hmi->dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, *pHmi, handle);
}
//返回得到的hw_module_t结构体的指针
*pHmi = hmi;
return status;
}
load 函数的主要工作时通过 dlopen 来打开目标模块共享库,打开成功后,使用 dlsym 来得到符号名字为 “HMI” 的地址。这里的 HMI 应该是模块定义的hw_module_t结构体的名字,如此,就得到了模块对应的hw_module_t的指针。
至此,我们终于得到了表示硬件模块的 hw_module_t 的指针,有了这个指针,就可以对硬件模块进行操作了。HAL 是如何查找和加载模块共享库的过程就分析完了,最终还是通过 dlopen 和 dlsym 拿到了模块的hw_module_t的指针,就可以为所欲为了。
(6)HAL 与 frameworks 关联代码如下:
public class LightsService extends SystemService {
//...
private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) {
if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
+ Integer.toHexString(color));
mColor = color;
mMode = mode;
mOnMS = onMS;
mOffMS = offMS;
Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")");
try {
setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
}
public LightsService(Context context) {
super(context);
mNativePointer = init_native();
//...
}
private static native long init_native();//这里调用HAL层的hw_get_module
private static native void finalize_native(long ptr);
static native void setLight_native(long ptr, int light, int color, int mode,
int onMS, int offMS, int brightnessMode);//访问硬件
private long mNativePointer;//保存hal返回的句柄
}
2、GPS模块分析
GpsInterface定义了操作GPS模块的基本的接口标准,得到了GpsInterface就可以通过这些接口操作GPS了,与硬件打交道了。某一个具体的 GPS 模块会将 GpsInterface 中的接口初始化为其平台相关的具体实现。比如在hardware/qcom/gps/loc_api/libloc_api_50001/loc.cpp:
// Defines the GpsInterface in gps.h
static const GpsInterface sLocEngInterface =
{
sizeof(GpsInterface),
loc_init,
loc_start,
loc_stop,
loc_cleanup,
loc_inject_time,
loc_inject_location,
loc_delete_aiding_data,
loc_set_position_mode,
loc_get_extension
};
到这里,整个 GPS HAL 的加载过程就结束了,后面就可以通过 GpsInterface 操作 GPS 模块了。
五、HAL简单示例
1、HAL加法函数项目结构
手动实现一个HAL服务,用于提供一个加法函数,项目结构如下:
2、代码编写步骤
步骤一:首先在 hardware/libhardware/include/hardware 目录下创建 hello.h 文件:
#ifndef ANDROID_LIGHTS_INTERFACE_H
#define ANDROID_LIGHTS_INTERFACE_H
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#include <hardware/hardware.h>
//HAL模块名
#define HELLO_HARDWARE_MODULE_ID "hello"
//HAL版本号
#define HELLO_MODULE_API_VERSION_1_0 HARDWARE_MODULE_API_VERSION(0,1)
//设备名
#define HARDWARE_HELLO "hello"
//自定义HAL模块结构体
typedef struct hello_module
{
struct hw_module_t common
/* data */
}hello_module_t;
//自定义HAL设备结构体
typedef struct hello_device
{
struct hw_device_t common;
//加法函数
int (*additionTest)(const struct hello_device *dev,int a,int b,int *total);
};hello_device_t;
//给外部调用提供打开设备的函数
static inline int _hello_open(const struct hw_module_t *module,
hello_device_t **device)
{
return module->methods->open(module, HARDWARE_HELLO,
(struct hw_device_t **) device);
/* data */
};
我们可以看到在 hello.h 中自定义了两个结构体,分别继承 hw_module_t 和 hw_device_t 且在第一个变量,满足前面说的规则,另外定义在 device 结构体中的函数的第一个参数也必须是 device 结构体。
步骤二: 接着在 hardware/libhardware/modules/ 目录下创建一个 hello 的文件夹,里面包含一个 hello.c 和 Android.bp,我们来看 hello.c 文件:
#define LOG_TAG "HelloHal"
#include <malloc.h>
#include <stdint.h>
#include <string.h>
#include <log/log.h>
#include <hardware/hello.h>
#include <hardware/hardware.h>
//加法函数实现
static int additionTest(const struct hello_device *dev,int a,int b,int *total)
{
if(!dev){
return -1;
}
*total = a + b;
return 0;
}
//关闭设备函数
static int hello_close(hw_device_t *dev)
{
if (dev) {
free(dev);
return 0;
} else {
return -1;
}
}
//打开设备函数
static int hello_open(const hw_module_t* module,const char __unused *id,
hw_device_t** device)
{
if (device == NULL) {
ALOGE("NULL device on open");
return -1;
}
hello_device_t *dev = malloc(sizeof(hello_device_t));
memset(dev, 0, sizeof(hello_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = HELLO_MODULE_API_VERSION_1_0;
dev->common.module = (struct hw_module_t*) module;
dev->common.close = hello_close;
dev->additionTest = additionTest;
*device = &(dev->common);
return 0;
}
static struct hw_module_methods_t hello_module_methods = {
.open = hello_open,
};
//导出符号HAL_MODULE_INFO_SYM,指向自定义模块
hello_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = HELLO_MODULE_API_VERSION_1_0,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = HELLO_HARDWARE_MODULE_ID,
.name = "Demo Hello HAL",
.author = "dongjiao.tang@tcl.com",
.methods = &hello_module_methods,
},
};
前面我们说过,要想自定义的 HAL 模块被外部使用需要导出符号 HAL_MODULE_INFO_SYM,导出的这个模块结构体最重要的其实就是 tag,id和 methods,tag 必须定义为HARDWARE_MODULE_TAG,id 就是在 hello.h 中定义的模块名,methods 指向 hello_module_methods 地址, hello_module_methods 指向结构体 hw_module_methods_t,它里面的open函数指针作用是打开模块下的设备,它指向 hello_open 函数。
hello_open 函数也很简单,就是创建一个 hello_device_t 结构体,这是我们自定义 HAL 模块下的设备结构体,然后给这个结构体赋值,我们定义的加法函数 additionTest 赋值给设备结构体的函数指针,外部就可以使用,因为这个 HAL 模块导出了符号HAL_MODULE_INFO_SYM,所以我们能通过模块 id 找到这个 HAL 模块,有了模块之后就可以调用它的 hello_open 函数打开设备,hello_open 接收三个参数,module 代表这个 HAL 模块,id 代表要打开的是这个模块下哪一个设备,最后一个参数是一个二级指针,其实是我们自定义设备结构体指针的地址,传个地址过来就可以给这个设备结构体赋值。
步骤三:这样最简单的一个 HAL 模块就定义好了,它仅仅提供了一个加法函数,对于 Android.bp 如下(最终编译成一个共享 lib 库):
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "hardware_libhardware_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["hardware_libhardware_license"],
}
cc_library_shared {
name: "hello.default",
relative_install_path: "hw",
proprietary: true,
srcs: ["hello.c"],
header_libs: ["libhardware_headers"],
shared_libs: ["liblog"],
}
步骤四:我们在根目录执行如下命令来编译一下:
mmm hardware/libhardware/modules/hello/
编译成功后生成了一个hello.default.so共享 lib 库:
我们将这个 so 库 push 进手机/system/lib64路径下,接着需要写一个简单测试程序来测一下。我们在 HAL 模块目录下再创建一个 test 目录,test 目录下创建一个 HelloTest.cpp 测试文件和一个 Android.bp 文件,HelloTest.cpp 源码如下:
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <log/log.h>
#define TAG "HelloHal"
int main(){
hello_device_t* hello_device = NULL;
const hello_module_t * module = NULL;
int ret = hw_get_module(HELLO_HARDWARE_MODULE_ID,(const struct hw_module_t**)&module);
if (!ret) {
ret = _hello_open((const struct hw_module_t*)module,&hello_device);
}
if (ret < 0) {
ALOGD("get hello-hal failed.......");
return -1;
}
int total = 0;
hello_device->additionTest(hello_device,3,5,&total);
ALOGD("success start hello-hal....total = %d",total);
return 0;
}
通过 hardware.c 提供的 hw_get_module 函数,传递模块名获取对应的 HAL 模块,接着调用我们自定义的 _hello_open 函数通过获取到的模块打开设备,获取到设备之后,我们调用定义在设备结构体中的加法函数。
6、Android.bp 定义如下:
cc_binary {
name: "hello_hal",
srcs: ["HelloTest.cpp"],
shared_libs: [
"liblog",
"libhardware",
],
}
这个 test 会被编译成二进制可执行文件 hello_hal :
7、我们将这个可执行文件 push 进手机 system/bin 目录,然后执行:
8、可以看到 log 已经成功打印:
到此我们最简单的 HAL 就已经实现了,这个 HAL 并不设计底层驱动,实际开发中自己可以在设备结构体中定义函数访问底层硬件。
实际上 HAL 服务的访问场景,往往不是以上的二进制客户端文件,而是从 native 层通过 HIDL 访问 HAL,或者应用层 APK 通过 AIDL 访问 framework 层,framework 层通过 JNI 访问 native 层,打通应用层到 HAL 层的整个开发框架。参见如下专栏:blog.csdn.net/qq_34211365…