尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Android HAL硬件抽象层:从原理到实践,实现自定义硬件驱动

Android HAL硬件抽象层:从原理到实践,实现自定义硬件驱动 1. 项目概述为什么我们需要HAL在Android开发或者嵌入式开发领域尤其是当你需要让Android系统去控制一个特定的硬件比如一个自定义的传感器、一块特殊的显示屏或者一个非标准的通信模块时你很快就会遇到一个绕不开的概念HAL也就是硬件抽象层。我第一次深入接触HAL是在一个智能家居项目里需要让Android平板通过GPIO去读取一个温湿度传感器类似DHT11的数据。当时直接去写Linux内核驱动发现应用层调用极其麻烦而且不同硬件平台比如高通、MTK、瑞芯微的驱动接口差异巨大代码根本无法复用。这时HAL的价值就凸显出来了。简单来说Android HAL就是架设在Linux内核驱动和Android框架服务之间的一座“桥梁”。它的核心使命是定义一套标准的接口让上层的Android服务比如传感器服务、相机服务、音频服务能够以统一的方式去访问底层千差万别的硬件而无需关心这个硬件具体是哪个厂商的、用了哪款芯片。对于应用开发者你调用的是SensorManager对于系统开发者你实现的是hardware/libhardware中定义的hw_module_t和hw_device_t结构。HAL层就是那个把getTemperature()这样的抽象调用翻译成具体I2C读寄存器操作的“翻译官”。理解HAL不仅能让你看懂Android系统如何管理硬件更能让你获得“魔改”设备、为Android系统添加新硬件支持的能力。无论是想为开发板移植Android还是想深度定制系统功能HAL都是必须攻克的一关。接下来我会结合原理和实践拆解HAL的运作机制并分享一个从零实现一个简单HAL模块的完整过程。2. HAL的核心架构与设计思想2.1 HAL在Android系统中的位置要理解HAL必须先看清它在整个Android架构中的位置。Android系统采用分层架构从上到下大致是应用层 - 应用框架层 - 系统服务 Native库 - HAL层 - Linux内核驱动层。应用框架层提供Java API给应用开发者使用例如Camera2 API、SensorManager。系统服务运行在system_server进程中的核心服务如CameraService、SensorService。它们通过JNI调用Native库。Native库通常是*.so动态库实现了框架服务所需的本地接口。例如libcameraservice.so会去查找并加载对应的Camera HAL。HAL层以*.so库的形式存在实现了hardware/libhardware头文件中定义的标准接口。它是对内核驱动的封装和抽象。内核驱动真正的硬件操作者通过Linux内核接口如open、ioctl、sysfs与硬件通信。HAL层的关键在于它通过动态库的形式将硬件相关的代码与Android框架完全解耦。框架服务只需要调用hw_get_module()函数传入一个模块ID例如SENSORS_HARDWARE_MODULE_ID系统就会自动找到并加载对应的HAL实现库。这种设计带来了巨大的灵活性芯片厂商如Qualcomm, MTK可以提供自己优化的HAL实现设备制造商OEM可以在不修改Android框架源码的情况下替换或定制HAL来适配自己的硬件。2.2 HAL接口的两种主要形式Passthrough与Binderized在Android 8.0Oreo之后HAL的形态发生了重要演变主要分为了两种模式1. Passthrough HAL (直通式HAL)这是传统且目前仍广泛使用的形式尤其在嵌入式或系统移植领域。HAL模块被编译成一个动态库如lights.default.so由调用它的进程通常是system_server直接加载到自己的进程空间内。因此HAL和框架服务运行在同一个进程调用开销小但稳定性较差——HAL库的崩溃会导致整个系统服务崩溃。注意很多在Android.mk或Blueprint文件中命名为xxx.default.so的模块就是这种直通式HAL的默认实现。例如你在为STM32开发板移植Android时为DHT11传感器编写的HAL大概率就是这种形式。2. Binderized HAL (绑定器化HAL)这是Android推进“模块化”和“Treble”项目后的新标准。HAL被实现为一个独立的进程守护进程通过Android Binder IPC机制与框架服务通信。例如android.hardware.sensors2.0-service就是一个独立的传感器HAL服务进程。这种方式的优点是稳定性高HAL进程崩溃不会影响系统服务、安全性好进程隔离、便于独立更新。新项目尤其是涉及android.hardware.*命名空间的HAL都应优先考虑这种方式。对于初学者或硬件功能简单的项目从Passthrough HAL入手学习曲线更平缓。我们后续的实践也将基于这种形式。2.3 HAL模块与设备理解hw_module_t和hw_device_t这是HAL接口定义的基石定义在hardware/libhardware/include/hardware/hardware.h中。理解这两个结构体就理解了HAL的契约。hw_module_t (硬件模块)代表一类硬件设备的抽象工厂。每个HAL库必须导出一个名为HAL_MODULE_INFO_SYM通常是HMI的符号它就是一个hw_module_t结构体。这个结构体包含了模块的ID、版本号、作者等元信息以及一个关键的open函数指针。// 简化示意 struct hw_module_t { uint32_t tag; // 必须为HARDWARE_MODULE_TAG uint16_t module_api_version; const char *id; // 模块唯一ID如 sensors const char *name; const char *author; // ... 其他成员 int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device); // 关键打开具体设备的函数 };框架服务通过hw_get_module(“sensors”, (const hw_module_t**)module)来获取传感器模块。hw_device_t (硬件设备)代表一个具体的、可操作的硬件实例。它由hw_module_t-open()函数创建并返回。这个结构体包含了设备版本、所属模块指针以及一个必须实现的close函数指针。更重要的是它通常会被扩展在它的后面追加设备特定的操作函数结构体。// 简化示意 struct hw_device_t { uint32_t tag; // 必须为HARDWARE_DEVICE_TAG uint32_t version; struct hw_module_t* module; int (*close)(struct hw_device_t* device); // 关键关闭设备的函数 };例如传感器设备会定义一个sensors_device_t它“继承”了hw_device_t然后后面加上poll、activate等传感器特有的函数指针。这种设计非常巧妙hw_module_t负责“找厂家”模块hw_device_t负责“拿产品”设备实例而产品型号设备特定操作则由HAL实现者定义。框架服务通过基类指针hw_device_t*操作设备再通过强制类型转换到具体的设备结构体来调用特定功能。3. 动手实践为模拟传感器编写一个Passthrough HAL理论说得再多不如动手写一遍。我们假设要为一个虚拟的“环境传感器”能读温度、湿度实现一个HAL。这个传感器在内核中可能对应一个生成随机数据的虚拟设备文件/dev/virtual_sensor。3.1 环境准备与代码结构我们将在AOSPAndroid Open Source Project源码树的环境下进行开发这是最标准的方式。假设你的AOSP源码目录是~/aosp。确定HAL模块位置HAL代码通常放在hardware/目录下可以放在厂商目录如hardware/xxx/或直接放在hardware/libhardware/modules/下。我们创建一个新目录~/aosp/hardware/myvendor/virtual_sensor/创建必要文件hardware/myvendor/virtual_sensor/ ├── Android.bp // 构建蓝图文件 (Soong构建系统) ├── virtual_sensor.h // HAL头文件定义设备操作接口 └── virtual_sensor.c // HAL实现文件3.2 定义HAL接口头文件首先我们需要在virtual_sensor.h中定义我们的设备操作接口。这个文件应该放在hardware/libhardware/include/hardware/下或者放在我们自己的目录并在构建时导出。这里我们放在本地。// hardware/myvendor/virtual_sensor/virtual_sensor.h #ifndef ANDROID_VIRTUAL_SENSOR_INTERFACE_H #define ANDROID_VIRTUAL_SENSOR_INTERFACE_H #include hardware/hardware.h // 必须包含定义了hw_module_t等 __BEGIN_DECLS // 定义我们模块的ID框架服务将通过这个ID来查找我们 #define VIRTUAL_SENSOR_HARDWARE_MODULE_ID virtual_sensor // 定义我们设备操作结构体它“继承”自hw_device_t struct virtual_sensor_device_t { struct hw_device_t common; // 第一个成员必须是hw_device_t // 以下是设备特定的操作函数指针 // 读取当前温度温度值通过参数指针返回 int (*read_temperature)(struct virtual_sensor_device_t* dev, float* temperature); // 读取当前湿度湿度值通过参数指针返回 int (*read_humidity)(struct virtual_sensor_device_t* dev, float* humidity); // 可以添加更多控制函数比如设置采样率等 // int (*set_sample_rate)(struct virtual_sensor_device_t* dev, int rate); }; __END_DECLS #endif // ANDROID_VIRTUAL_SENSOR_INTERFACE_H这个头文件就是我们的“契约”。上层的服务比如我们未来会写的一个测试服务知道了这个结构就可以调用read_temperature来获取数据。3.3 实现HAL模块接下来是核心在virtual_sensor.c中实现这个接口。// hardware/myvendor/virtual_sensor/virtual_sensor.c #define LOG_TAG VirtualSensorHAL #include log/log.h // 使用Android日志系统 #include hardware/hardware.h #include fcntl.h #include unistd.h #include string.h #include virtual_sensor.h // 包含我们自己的接口定义 // 假设我们的虚拟设备文件 #define VIRTUAL_SENSOR_DEVICE_NODE /dev/virtual_sensor // 1. 实现设备操作函数 static int virtual_sensor_read_temperature(struct virtual_sensor_device_t* dev, float* temperature) { // 参数检查 if (!dev || !temperature) { ALOGE(Invalid arguments to read_temperature); return -EINVAL; } int fd open(VIRTUAL_SENSOR_DEVICE_NODE, O_RDONLY); if (fd 0) { ALOGE(Failed to open %s: %s, VIRTUAL_SENSOR_DEVICE_NODE, strerror(errno)); return -errno; } char buf[32]; ssize_t len read(fd, buf, sizeof(buf)-1); close(fd); if (len 0) { ALOGE(Failed to read from device); return -EIO; } buf[len] \0; // 模拟解析数据假设设备文件返回 25.5,60.2 (温度,湿度) // 实际项目中这里需要根据你的硬件协议解析 float temp 0.0f, humi 0.0f; if (sscanf(buf, %f,%f, temp, humi) 2) { *temperature temp; ALOGD(Read temperature: %.2f, *temperature); return 0; // 成功 } ALOGE(Failed to parse data: %s, buf); return -EIO; } static int virtual_sensor_read_humidity(struct virtual_sensor_device_t* dev, float* humidity) { // 实现逻辑与read_temperature类似解析湿度部分 // 这里为了简化我们直接模拟一个值 *humidity 60.0f; ALOGD(Read humidity: %.2f (simulated), *humidity); return 0; } static int virtual_sensor_close(struct hw_device_t* device) { struct virtual_sensor_device_t* ctx (struct virtual_sensor_device_t*)device; if (ctx) { free(ctx); // 释放设备结构体内存 } ALOGD(Virtual sensor device closed); return 0; } // 2. 实现模块的open函数 static int virtual_sensor_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) { ALOGD(Opening virtual sensor device, id: %s, id); if (!module || !device) { ALOGE(Invalid arguments to open); return -EINVAL; } // 分配并初始化设备结构体 struct virtual_sensor_device_t* dev calloc(1, sizeof(struct virtual_sensor_device_t)); if (!dev) { ALOGE(Failed to allocate memory for device); return -ENOMEM; } // 填充hw_device_t公共部分 dev-common.tag HARDWARE_DEVICE_TAG; dev-common.version 0; // 设备接口版本 dev-common.module (struct hw_module_t*)module; // 指向所属模块 dev-common.close virtual_sensor_close; // 设置关闭函数 // 填充我们自己的操作函数 dev-read_temperature virtual_sensor_read_temperature; dev-read_humidity virtual_sensor_read_humidity; *device (dev-common); // 将设备指针返回给调用者 ALOGD(Virtual sensor device opened successfully); return 0; } // 3. 定义模块方法结构可选但常见 static struct hw_module_methods_t virtual_sensor_module_methods { .open virtual_sensor_open, // 将open函数指针关联起来 }; // 4. 定义并导出模块信息结构体 // HAL_MODULE_INFO_SYM 是框架查找模块时认的符号名 struct virtual_sensor_module_t HAL_MODULE_INFO_SYM { .common { // 填充hw_module_t公共部分 .tag HARDWARE_MODULE_TAG, .module_api_version 1, // 模块API版本 .hal_api_version 0, // HAL API版本 .id VIRTUAL_SENSOR_HARDWARE_MODULE_ID, // 模块ID必须与头文件定义一致 .name Virtual Environment Sensor HAL, .author MyVendor, .methods virtual_sensor_module_methods, // 关联模块方法 }, // 这里可以添加模块级别的扩展数据如果有 };关键点解析设备操作函数virtual_sensor_read_temperature和virtual_sensor_read_humidity是实际与硬件这里是/dev/virtual_sensor通信的地方。这里使用了标准的文件I/O操作。在真实场景中这里可能是ioctl调用、sysfs读写或更复杂的协议。open函数这是模块的“工厂方法”。当框架调用hw_get_module并找到这个模块后会调用其open方法来获取一个设备实例。这里负责分配内存、初始化函数指针。模块信息结构体HAL_MODULE_INFO_SYM是整个HAL库的入口点。构建系统会确保这个符号被导出。框架的hw_get_module函数本质上就是在动态库中查找这个符号。3.4 编写构建文件Android.bpAOSP现在主要使用Soong构建系统我们需要编写Android.bp来告诉系统如何编译我们的HAL模块。// hardware/myvendor/virtual_sensor/Android.bp cc_library_shared { name: virtual_sensor.default, // 模块名.default后缀表明是直通式HAL的默认实现 relative_install_path: hw, // 安装到 /vendor/lib64/hw/ 或 /system/lib64/hw/ vendor: true, // 标记为vendor模块在Treble架构下通常放在vendor分区 srcs: [virtual_sensor.c], header_libs: [libhardware_headers], // 包含hardware.h等头文件 shared_libs: [ liblog, // 链接日志库 libcutils, ], cflags: [ -Wall, -Werror, -DLOG_TAG\VirtualSensorHAL\, ], export_include_dirs: [.], // 导出我们的头文件目录 }3.5 编译与刷入在AOSP根目录下执行source build/envsetup.sh lunch 你的设备目标 # 例如 aosp_x86_64-eng mmm hardware/myvendor/virtual_sensor/编译成功后会生成virtual_sensor.default.so。你可以通过adb push将其推送到设备的/vendor/lib64/hw/64位或/vendor/lib/hw/32位目录下并设置正确的权限。3.6 编写测试程序验证HAL最后我们需要验证HAL是否能被正确加载和调用。编写一个简单的Native测试程序。// test_virtual_sensor.c #include stdio.h #include stdlib.h #include hardware/hardware.h #include virtual_sensor.h // 需要能找到这个头文件 int main() { const struct hw_module_t* module NULL; struct virtual_sensor_device_t* dev NULL; float temp 0.0f, humi 0.0f; // 1. 获取模块 int err hw_get_module(VIRTUAL_SENSOR_HARDWARE_MODULE_ID, module); if (err ! 0) { printf(ERROR: failed to get module, error %d\n, err); return -1; } printf(Module found: %s\n, module-name); // 2. 打开设备 err module-methods-open(module, NULL, (struct hw_device_t**)dev); if (err ! 0 || dev NULL) { printf(ERROR: failed to open device, error %d\n, err); return -1; } printf(Device opened successfully.\n); // 3. 使用设备 err dev-read_temperature(dev, temp); if (err 0) { printf(Current temperature: %.2f °C\n, temp); } else { printf(Failed to read temperature: %d\n, err); } err dev-read_humidity(dev, humi); if (err 0) { printf(Current humidity: %.2f %%\n, humi); } else { printf(Failed to read humidity: %d\n, err); } // 4. 关闭设备 dev-common.close(dev-common); printf(Test finished.\n); return 0; }将这个测试程序编译并推送到设备执行如果一切正常你应该能看到调用HAL接口读取到的模拟数据。4. 进阶将HAL集成到Android系统服务让HAL被一个独立的测试程序调用只是第一步。真正的价值在于让Android系统服务如SensorService来管理它。这通常涉及以下步骤定义HIDL或AIDL接口针对Binderized HAL对于新项目Google推荐使用HIDLHAL Interface Definition Language或AIDL来定义进程间接口。这需要编写.hal或.aidl接口文件并使用hidl-gen或aidl工具生成C/Java的桩代码。实现HIDL/AIDL接口你的HAL实现将继承自生成的接口类并实现其中的纯虚函数。这些函数内部再去操作实际的硬件。编写Service入口创建一个可执行文件cc_binary或android.hardware.sensors2.0-service这样的服务在main函数中注册你的HAL实现为Binder服务。配置Manifest和VINTF在vendor分区下需要提供兼容性矩阵VINTF告诉系统你的设备提供了哪些HAL接口及版本。修改系统配置可能需要修改device.mk、BoardConfig.mk等设备配置文件将你的HAL服务添加到启动项中。对于Passthrough HAL集成相对简单确保HAL库在正确路径编译系统通常会将.default.so库自动安装到/vendor/lib/hw/或/system/lib/hw/。系统服务自动加载像SensorService这样的服务在初始化时会遍历hw目录根据sensors.h中定义的SENSORS_HARDWARE_MODULE_ID来查找并加载所有传感器HAL。你只需要确保你的模块ID和文件名符合规范例如virtual_sensor.default.so服务就能找到它。添加权限和SELinux策略这是最容易出错的地方。你的HAL进程或库需要访问硬件设备文件如/dev/virtual_sensor必须在SELinux策略文件中通常是device/xxx/sepolicy/下的file_contexts和xxx.te文件添加相应的标签和权限规则否则会被SELinux拒绝访问导致HAL失效。5. 调试技巧与常见问题排查在实际开发中HAL层的调试往往比较棘手因为它处于承上启下的位置。以下是我积累的一些实用技巧和常见坑点1. 确认HAL库被正确加载检查文件是否存在及权限adb shell ls -lZ /vendor/lib64/hw/virtual_sensor.default.so。确保文件存在且SELinux标签正确通常是u:object_r:hal_sensor_default_exec:s0。查看系统日志adb logcat | grep -i “hw_module”或adb logcat | grep -i “virtual_sensor”。在系统启动或服务初始化时会打印加载HAL模块的日志。如果没看到你的模块ID说明加载失败。使用lsof或procrank在设备上adb shell后执行lsof | grep virtual_sensor看看是否有进程打开了你的HAL库。2. HAL的open函数被调用但设备操作失败首先检查内核驱动确保你的底层设备驱动已经正确加载并创建了设备节点如/dev/virtual_sensor。用adb shell ls -l /dev/查看。检查文件操作权限在HAL的open或read函数中打印errno用strerror(errno)。Permission denied(13) 通常是SELinux问题No such file or directory(2) 是路径问题。SELinux策略这是最高频的坑。查看内核日志获取AVC拒绝信息adb shell dmesg | grep avc或adb logcat | grep avc。输出会明确告诉你哪个进程如hal_sensor_default缺少对哪个资源如virtual_sensor_device的什么权限如open、read。你需要据此在SELinux策略文件中添加对应的allow规则。3. 内存泄漏与资源管理谁分配谁释放在HAL的open函数中calloc了设备结构体必须在对应的close函数中free掉。文件描述符泄漏确保每一个open()成功的文件描述符在函数返回前都有对应的close()尤其是在错误处理路径上。使用Valgrind或AddressSanitizer在模拟器或支持的工具链下使用这些内存调试工具来检测HAL库中的内存问题。4. 版本兼容性问题模块与设备版本号hw_module_t和hw_device_t中的version字段很重要。上层服务可能会检查版本号以确定支持哪些功能。确保你定义的版本与框架期望的版本兼容。如果不确定可以从0或1开始。HIDL/AIDL版本如果使用Binderized HAL务必注意接口的版本号。客户端和服务端的版本需要匹配或者服务端需要实现所有客户端可能调用的旧版本接口方法。5. 调试工具strace/ltrace在调试版系统上可以使用strace跟踪系统调用查看HAL库是否成功打开了设备文件、进行了哪些ioctl调用。ltrace可以跟踪库函数调用。自定义日志像示例中一样大量使用ALOGD,ALOGI,ALOGE。通过adb logcat -s VirtualSensorHAL:D *:S可以只过滤你标签的调试日志非常清晰。GDB调试对于复杂的HAL可以将GDB连接到运行HAL的进程可能是system_server或独立的HAL服务进程进行断点调试。这需要设备支持gdbserver。6. 从Passthrough迁移到Binderized HAL的考量如果你的项目需要长期维护或者面向较新的Android版本Android 10考虑使用Binderized HAL是更面向未来的选择。迁移会带来一些额外工作但收益明显优势稳定性HAL进程崩溃不会导致系统服务重启。可更新性HAL可以独立于系统镜像进行OTA更新。进程隔离与安全权限控制更精细。清晰的接口契约HIDL/AIDL强制定义了严格的接口减少了隐式依赖。迁移核心步骤定义接口文件创建IVirtualSensor.hal文件用HIDL语法定义readTemperature()和readHumidity()等方法。生成代码使用hidl-gen工具生成C的接口桩、代理和实现模板。实现接口编写你的实现类继承自生成的IVirtualSensor类在实现方法中封装对硬件的操作。编写服务入口创建一个main.cpp在其中注册你的实现为Binder服务通过registerAsService()。配置与编译编写新的Android.bp将你的实现编译成一个可执行文件或库并配置为系统服务。更新VINTF在manifest.xml中声明你的HAL服务。实操心得 迁移过程最大的挑战往往是构建系统的配置和SELinux策略的更新。Binderized HAL作为一个独立进程它需要的SELinux域domain和权限与直通式HAL完全不同需要仔细根据avc拒绝日志来补充。建议先在一个简单的、功能验证通过的Passthrough HAL基础上进行迁移分步测试。理解并掌握Android HAL就像是拿到了Android系统与硬件世界对话的“协议手册”。从看懂原理到动手实现一个简单的模块再到集成、调试、乃至架构升级每一步都需要耐心和细致的实践。希望这篇结合了原理深度和实操细节的长文能为你深入Android底层开发打下坚实的基础。当你成功让系统识别出你自己编写的硬件并流畅地读取数据时那种成就感是纯粹的应用开发难以比拟的。如果在实践中遇到具体问题多查源码hardware/libhardware、多分析日志、善用调试工具大部分难题都能找到突破口。
返回列表