• 一、HAL层的前世今生
  • 二、HAL层的通用结构剖析
  • 三、一个例子
  • 四、击破Audio HAL Module
  • 五、小的总结

一、HAL层的前世今生

  1. HAL(Hardware Abstraction Layer),硬件抽象层。由于部分硬件厂商不想把自己的核心代码公开,如果把代码放在内核空间里就需要遵循GUN License,会损害厂家的利益。所以,Google为了响应厂家在Android的架构里提出HAL的概念,把对硬件的支持分为用户空间和内核空间,而HAL层就属于这里面的用户空间,该部分代码遵循Apache License,所以厂家可以把核心的代码实现在HAL层,无需对外开放源代码。

  2. HAL层的新旧架构

    • 上层通过jni直接调用so库,在so库中实现底层驱动的操作(Module)

    • 上层查找已经注册的Stub,返回硬件对象的操作接口,后续直接通过接口操作硬件(ModuleStub)

二、HAL层的通用结构剖析

  1. 通用的321架构
    HAL层的主要框架是由三个结构体,两个常量,一个函数构成,所有的硬件抽象模块都是必须遵循321架构,在此基础上扩展自有的功能。

    • 三个结构体
//文件位置: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;
......
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;/*** Every device data structure must begin with hw_device_t* followed by module specific public methods and attributes.*/
typedef struct hw_device_t {/** tag must be initialized to HARDWARE_DEVICE_TAG */uint32_t tag;
......

hw_module_methods_t是封装在hw_module_t里的,里面提供的一个open方法需要具体的实现模块去重载;hw_module_t里面有很多关于模块的信息需要在初始化的时候去填充,非常关键的一个就是重载hw_module_methods_t中的open函数;hw_device_t里主要是和硬件设备相关的一些操作,各硬件模块需要继承该结构体。
- 两个常量和一个函数

//文件位置:hardware/libhardware/include/hardware/hardware.h
/*** 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"/*** Get the module info associated with a module by id.** @return: 0 == success, <0 == error and *module == NULL*/
int hw_get_module(const char *id, const struct hw_module_t **module);

HAL_MODULE_INFO_SYM和HAL_MODULE_INFO_SYM_AS_STR与HAL的模块入口有关,在上层调用hw_get_module时,通过模块ID和对应的HMI(通过dlopen/dlsym进行映射)结构体找到模块入口,然后通过之前重载的open函数,就可以获得设备的操作接口(hw_device_t),之后就可以通过module访问模块实现的相关函数

三、一个例子

编程的学习从HelloWorld开始,那嵌入式应该就是从点灯开始。
下面我们就先从一个简单的LED模块的例子,来全面的的理解下HAL的基础框架。

在前面提到了三个结构体,各实现模块需要去继承并客制化自己的操作接口。我们可以从图中看到LED模块遵循该准则,其中hw_module_methods_t是封装在hw_module_t里的,具体的关系图如下:

- 头文件的代码实现:

//文件位置:hardware/modules/include/mokoid/led.h
struct led_module_t {  //结构体继承struct hw_module_t common;int (*init_led)(struct led_control_device_t *dev);
};struct led_control_device_t {  //结构体继承struct hw_device_t common;int fd; /* file descriptor of LED device *///硬件模块的客制化操作/* supporting control APIs go here */int (*set_on)(struct led_control_device_t *dev, int32_t led);int (*set_off)(struct led_control_device_t *dev, int32_t led);int (*set_name)(struct led_control_device_t *dev, char *name);
};/*****************************************************************************/
//ID hw_get_module的时候会使用
#define LED_HARDWARE_MODULE_ID "led"/** helper APIs */
static inline int led_control_open(const struct hw_module_t* module,struct led_control_device_t** device) {return module->methods->open(module,LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device); //注意指针类型的转换
  • 源文件的代码实现:
//文件位置:hardware/modules/led/led.cpp
int led_device_open(const struct hw_module_t* module, const char* name,struct hw_device_t** device)
{struct led_control_device_t *dev;dev = (struct led_control_device_t *)malloc(sizeof(*dev));memset(dev, 0, sizeof(*dev));dev->common.tag = HARDWARE_DEVICE_TAG;dev->common.version = 0;dev->common.module = const_cast<struct hw_module_t *>(module);dev->common.close = led_device_close;dev->set_on = led_on;dev->set_off = led_off;*device = &dev->common;  //返回通用的device结构体,这就是device通用结构体必须放在开头的原因/* open device file */dev->fd = open("/dev/cdata-test", O_RDWR);success:return 0;
}
......
struct hw_module_methods_t led_module_methods = {open: led_device_open      //重载open函数
};/*** instance of led_module_t */
const struct led_module_t HAL_MODULE_INFO_SYM = {  //HAL_MODULE_INFO_SYM结构体名字,dlopen/dlsym依赖于该符号进行映射common: {tag: HARDWARE_MODULE_TAG,version_major: 1,version_minor: 0,id: LED_HARDWARE_MODULE_ID,    //模块IDname: "Sample LED Stub",author: "The Mokoid Open Source Project",methods: &led_module_methods,    //关联hw_module_methods_t},
.......
};
  • 上层的调用
//文件位置:frameworks/base/service/jni/com_mokoid_server_LedService.cpp
static jboolean
mokoid_init(JNIEnv *env, jclass clazz)
{led_module_t* module;LOGI("LedService JNI: mokoid_init() is invoked.");if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {LOGI("LedService JNI: LED Stub found.");if (led_control_open(&module->common, &sLedDevice) == 0) { //后续对硬件的操作可以直接操作sLedDeviceLOGI("LedService JNI: Got Stub operations.");return 0;}}LOGE("LedService JNI: Get Stub operations failed.");return -1;
}

大体的调用关系如下:

四、击破Audio HAL Module

在了解了LED模块的HAL框架之后,同样的,其他模块也都是按照这一个套路,只是有些模块功能复杂一点,对接口的封装程度更高而已。
下面我们就稍微分析一下audio模块的HAL框架,老样子我们还是先上图:

从图中我们可以了解到,audio模块对通用结构体的封装程度跟高,对于设备操作这一块的接口又重新定义了AudioHardwareInterface、AudioStreamIn、AudioStreamOut等通用接口,让子类去继承和实现。但是,一层层的解剖我们还是可以看到前面提到的321架构。

注:以下代码基于android4.4分析,不同的版本可能文件存放的路径和实现方式稍有差异

  • 头文件的代码实现:
//文件位置:hardware/libhardware/include/hardware/audio.h
......
#define AUDIO_HARDWARE_MODULE_ID_PRIMARY "primary"
#define AUDIO_HARDWARE_MODULE_ID_A2DP "a2dp"
#define AUDIO_HARDWARE_MODULE_ID_USB "usb"
#define AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX "r_submix"
#define AUDIO_HARDWARE_MODULE_ID_CODEC_OFFLOAD "codec_offload"
......
struct audio_module {struct hw_module_t common;
};struct audio_hw_device {struct hw_device_t common;uint32_t (*get_supported_devices)(const struct audio_hw_device *dev);int (*init_check)(const struct audio_hw_device *dev);int (*set_voice_volume)(struct audio_hw_device *dev, float volume);int (*set_master_volume)(struct audio_hw_device *dev, float volume);int (*get_master_volume)(struct audio_hw_device *dev, float *volume);int (*set_mode)(struct audio_hw_device *dev, audio_mode_t mode);int (*set_mic_mute)(struct audio_hw_device *dev, bool state);int (*get_mic_mute)(const struct audio_hw_device *dev, bool *state);int (*set_parameters)(struct audio_hw_device *dev, const char *kv_pairs);char * (*get_parameters)(const struct audio_hw_device *dev,const char *keys);
......
static inline int audio_hw_device_open(const struct hw_module_t* module,struct audio_hw_device** device)
{return module->methods->open(module, AUDIO_HARDWARE_INTERFACE,(struct hw_device_t**)device);
}static inline int audio_hw_device_close(struct audio_hw_device* device)
{return device->common.close(&device->common);
}
  • 源文件的代码实现
//文件位置:hardware/rk29/audio/audio_hw_hal.cpp
static struct hw_module_methods_t legacy_audio_module_methods = {open: legacy_adev_open
};struct legacy_audio_module HAL_MODULE_INFO_SYM = {module: {common: {tag: HARDWARE_MODULE_TAG,module_api_version: AUDIO_MODULE_API_VERSION_0_1,hal_api_version: HARDWARE_HAL_API_VERSION,id: AUDIO_HARDWARE_MODULE_ID,name: "LEGACY Audio HW HAL",author: "The Android Open Source Project",methods: &legacy_audio_module_methods,dso : NULL,reserved : {0},},},
};
  • 上层调用
    下图时,audio初始化的大体时序图,我们可以看到在AudioPolicyManagerBase会执行load模块的策略,然后让AudioFlinger去执行对应的操作。
//文件位置:frameworks/av/services/audioflinger/AudioFlinger.cpp
static int load_audio_interface(const char *if_name, audio_hw_device_t **dev)
{const hw_module_t *mod;int rc;rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);ALOGE_IF(rc, "%s couldn't load audio hw module %s.%s (%s)", __func__,AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));if (rc) {goto out;}rc = audio_hw_device_open(mod, dev);ALOGE_IF(rc, "%s couldn't open audio hw device in %s.%s (%s)", __func__,AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));if (rc) {goto out;}if ((*dev)->common.version != AUDIO_DEVICE_API_VERSION_CURRENT) {ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version);rc = BAD_VALUE;goto out;}return 0;out:*dev = NULL;return rc;
}
  • 最后我们再来深究以下,为什么模块的结构体必须命名成HAL_MODULE_INFO_SYM,也就是字符串”HMI”

这个原理我们就需要跟踪下hw_get_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);elsestrlcpy(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;
}

该函数,通过查找动态库的配置路径,最后调用load函数,我们继续往下追查

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符号字符串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;    //获取module的入口return status;
}

该函数,通过dlopen/dlsym完成module的符号映射,而其中的dlsym就是根据字符”HMI”去查找对应的module。

  • 动态库跟踪
    我们知道linux下的elf文件有符号表相关的结构,而hal层编译出来的so文件就是elf文件。
    所以,我们通过命令查看so文件:readelf -s audio.primary.default.so
Symbol table '.dynsym' contains 16 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize2: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit3: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr04: 00000000 0 FUNC GLOBAL DEFAULT UND strcmp5: 00000000 0 FUNC GLOBAL DEFAULT UND calloc6: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr17: 00000000 0 FUNC GLOBAL DEFAULT UND __popcountsi28: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_uidiv9: 00000000 0 FUNC GLOBAL DEFAULT UND usleep10: 00000000 0 FUNC GLOBAL DEFAULT UND strdup11: 00000000 0 FUNC GLOBAL DEFAULT UND free12: 00002008 128 OBJECT GLOBAL DEFAULT 15 HMI13: 00002088 0 NOTYPE GLOBAL DEFAULT ABS _edata14: 00002088 0 NOTYPE GLOBAL DEFAULT ABS __bss_start15: 00002088 0 NOTYPE GLOBAL DEFAULT ABS _end

里面HMI的符号,就是我们初始化的硬件模块结构体,dlopen/dlsym可以通过该符号找到模块的入口函数。
到此,基本分析完成了,对于elf文件结构的知识,建议阅读《程序员的自我修养》

五、小的总结

最近,一直在阅读音频部分相关的源码,包括android和linux下的一些音频框架。
稍微总结几点源码阅读的个人建议(可能不全,欢迎补充)
1. 提前了解一些UML类图、时序图等的表现规则
- 网上包括书上很多的文章都是基于这些图来分析的
2. 多画图,多总结,从简单到复杂
- 感觉图形表达出来的东西非常直观,容易快速的形成概念
- 当然,更细节的东西还是要一步步去啃代码
- 阅读源码过程,注定枯燥无味,但是图形的表示方法可以让过程稍微有趣点
- 我们可以从一个小的功能点切入,然后再扩展到整个系统
3. 结合编译脚本,建立代码关系
- 比如,android基于每个小模块都有一个mk文件,可以从脚本里了解到一些关联的模块
4. 睡前不要看代码!

参考博文:
深入浅出 - Android系统移植与平台开发(七)- 初识HAL
深入浅出 - Android系统移植与平台开发(八)- HAL Stub框架分析
Android HAL实例解析
HAL的由来,android为什么不是开源系统

【Android】HAL层浅析相关推荐

  1. Android HAL层浅析

    文章目录 1.HAL层在Android系统中的位置 2.HAL层概述 3.旧的HAL架构module 4.新的HAL架构module stub 5.HAL Stub框架分析 1.HAL层在Androi ...

  2. Android Hal层简要分析

    Android Hal层简要分析 Android Hal层(即 Hardware Abstraction Layer)是Google开发的Android系统里上层应用对底层硬件操作屏蔽的一个软件层次, ...

  3. Android HAL 层,三个重要的结构体的源码~

    最近在学习android HAL 层 ~ 离不开这三个数据结构~~先贴出来 typedef struct hw_module_t {     /** tag must be initialized t ...

  4. Android 系统(4)---Android HAL层与Linux Kernel层驱动开发简介

    Android HAL层与Linux Kernel层驱动开发简介 近日稍微对Android中的驱动开发做了一些简要的了解,稍稍理清了一下Android驱动开发的套路,总结一下笔记. HAL:Hardw ...

  5. Android HAL层与Linux Kernel层驱动开发简介

    Android HAL层与Linux Kernel层驱动开发简介 阅读数:5070 近日稍微对Android中的驱动开发做了一些简要的了解,稍稍理清了一下Android驱动开发的套路,总结一下笔记. ...

  6. Android hal 层 C 堆栈打印方法

    Android hal 层 C 堆栈打印方法 一.添加CallStack文件 在hardware/qcom/audio/hal/ 下添加 callstack.cpp 和 callstack.h文件 # ...

  7. 【转】android hal 层GPS 研究总结——关于GPS GGA/GSV/RMC 数据的解析

    [转]android hal 层GPS 研究总结 2011-09-22 快跑的未必能赢,力战的未必获胜,聪明的未必得粮食,明哲的未必得资财,灵活的未必得喜悦.所临到世人的,是在乎当时的机会. 人生一世 ...

  8. Android Hal层回调APP应用接口

    前面文章讲述了Android APP怎么调用hal层的方法,和Android系统自带的灯光子系统类似:本文将讲述hal层如何回调APP应用接口,和android系统振动传感器数据上报类似,同时本文也会 ...

  9. Android HAL层到驱动代码搜索---基于背光亮度的调试

    以前阅读源码时一般只阅读到fwk层部分,近期因为一个问题,想顺便把fwk层到hal 到驱动的调用关系都看下.在此做下记录. 原始问题是客户嫌屏幕调到最大亮度后仍然觉得亮度不够亮,让看看能不能把亮度调亮 ...

最新文章

  1. 项目管理三大认证体系,该选择谁?
  2. Apache Hadoop版本详解
  3. java 匿名函数 构造_Java匿名内部类与Lambda表达式
  4. oracle怎么查询换行符,关于oracle:如何检查表中所有列的换行符
  5. WCBuffer合并写
  6. 发现数据对象 -- 数据库开发的关键
  7. 学习笔记(16):Python网络编程并发编程-开启子进程的两种方式
  8. ssl1613-最短路径问题【图论,最短路径(还不明显?)】
  9. vue旋转图片功能,旋转放大图片功能;vue旋转放大div元素
  10. SpringCloud工作笔记062---APP消息推送_个推平台API使用经验
  11. centos7忘记密码处理办法
  12. 【图像融合】自适应参考图像的可见光与热红外彩色图像融合算法
  13. cmake--生成--动态库
  14. linux内存寻址解析 (一)
  15. 如何将pdf转换成txt破解版
  16. 定时下载快速精密星历
  17. 月薪30K+的电子工程师应具备什么?
  18. C++借助Eigen库实现矩阵开方(开根号)运算
  19. Java使用EasyExcel下载xls、xlsx 出现文件格式与扩展名不匹配
  20. Java抓取网页图片

热门文章

  1. python爬虫 京东关键词搜索商品及具体参数和评论
  2. python发html邮件_在python中如何制作发送HTML格式的邮件?
  3. 「Slack」- 安装 @20210303
  4. AMA指标代码逐一解释,附源码(python)
  5. MUSCI算法估计空间方位角
  6. [Redis实战]单文件夹启动多实例,redis哨兵+主从复制完整demo样例[windows环境]
  7. 仿微信清理内存图表动画(解决surfaceView闪烁问题)
  8. C++ 两点之间最短距离
  9. 使用Floyd-Warshall算法求出两点之间的最短路径
  10. 【MATLAB教程案例16】基于GWO灰狼优化算法的函数极值计算matlab仿真及其他应用