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

资讯详情

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

Android HAL模块开发实战:从HIDL接口到内核通信完整实现

Android HAL模块开发实战:从HIDL接口到内核通信完整实现 1. 项目概述从零构建一个Android HAL模块在Android系统开发中硬件抽象层Hardware Abstraction Layer, HAL是连接Android框架与底层Linux内核驱动之间的关键桥梁。对于许多初次接触底层开发的工程师来说HAL的概念常常显得抽象且难以捉摸。网上虽然不乏零散的代码片段但一个完整、可运行、能清晰展示数据流从应用层到内核再返回的示例却不多见。这个系列的目标就是彻底打破这种“黑盒”状态通过一个具体的传感器例子手把手带你从零开始从上到下即从应用层App经过Framework、HAL最终到Kernel Driver实现一个完整的HAL模块。这不仅仅是复制粘贴代码更是理解Android系统分层架构和跨进程通信IPC机制的最佳实践。本篇文章是这个系列的第三部分我们将聚焦于HAL层本身的实现。如果说第一部分是搭建舞台和设计剧本定义HAL接口.h文件第二部分是训练演员实现Kernel Driver那么第三部分就是让这位“演员”正式登台按照HAL定义的“剧本”进行表演。我们将基于第一部分定义的IExampleSensor接口实现一个具体的HAL服务。这个服务会通过JNI与上层Framework交互并通过sysfs或ioctl与第二部分的Kernel Driver通信最终完成一个“读取虚拟传感器数据”的完整闭环。无论你是想为自定义硬件添加Android支持还是单纯想深入理解Android系统架构这个从理论到实践的过程都将为你提供坚实的脚手架。2. HAL实现的核心架构与设计思路在动手写代码之前我们必须厘清HAL实现的核心架构。Android的HAL发展经历了传统HALHIDL和现代AIDL HAL等阶段但核心思想一脉相承定义接口隐藏实现。我们的例子将采用目前Android 13/T仍广泛支持且易于理解的HIDLHardware Interface Definition Language方式因为它能非常清晰地展示进程边界和Binder通信。2.1 选择HIDL作为实现语言为什么选择HIDL而不是直接使用AIDL对于学习而言HIDL的.hal接口定义文件语法更接近C其生成的代码结构能让你直观地看到Bp(Proxy)和Bn(Native)端这对理解Binder IPC的客户端-服务端模型至关重要。AIDL HAL虽然是未来趋势且与Framework的AIDL使用体验更一致但其在HAL层的部分封装更彻底有时会掩盖一些底层细节。从HIDL入手能打下更坚实的基础。我们的设计思路是实现一个单例的HAL服务进程。这个进程在系统启动时被hwservicemanager管理。当Android框架中的传感器服务例如SensorService需要访问我们的硬件时它会通过hwservicemanager查找到我们的HAL服务获取到一个Binder代理对象Bp端然后调用其接口方法。这些调用会跨进程传递到我们的HAL服务进程Bn端最终在我们的实现类中被执行。2.2 模块与服务的映射关系在Android HAL中一个硬件模块例如sensors下可以有一个或多个HAL服务实例。我们遵循这个约定创建一个HAL模块它负责在系统加载时进行初始化并向系统注册我们实现的服务。实现一个HAL服务它继承自HIDL生成的IExampleSensor接口的Bn端并包含真正的硬件操作逻辑与内核驱动通信。这个设计确保了扩展性。未来如果你的设备上有两个同类型但不同I2C地址的传感器你可以在这个模块内实例化两个不同的服务对象来分别管理。2.3 与内核驱动的通信方式选择HAL层是运行在用户空间Userspace的它与内核空间Kernelspace的驱动通信需要通过Linux系统提供的标准机制。常见的有sysfs文件系统驱动在/sys/class/下创建文件节点HAL通过open、read、write、close等标准文件IO操作与之交互。优点是简单直观适用于状态查询和简单控制。我们第二部分实现的驱动就提供了sysfs接口。ioctl设备控制驱动通过misc_register或cdev_add注册为一个字符设备如/dev/example_sensorHAL通过open设备文件并使用ioctl系统调用进行复杂的命令和数据交换。功能更强大可以传递结构体等复杂数据。Netlink网络链路一种用于内核与用户空间双向异步通信的机制常见于Wi-Fi、电源管理等需要事件上报的场景。为了简化示例并覆盖最常见的情况我们将同时演示两种方式使用sysfs读取一个模拟的传感器值并使用ioctl向驱动发送一个控制命令。这能让你在一个例子中掌握两种关键技能。3. 实现HAL服务从接口到具体代码现在我们开始将第一部分定义的IExampleSensor.hal接口转化为具体的C实现。假设我们的HIDL接口文件位于hardware/interfaces/example/1.0/IExampleSensor.hal。3.1 生成HIDL代码骨架首先你需要使用Android构建系统的HIDL编译器来生成代码骨架。在AOSP源码树下定位到接口目录并执行编译这会在out/soong/.intermediates下生成对应的C文件。不过为了理解我们可以直接看生成文件的结构。编译器会生成两个关键头文件IExampleSensor.h 定义了纯虚接口类IExampleSensor。BnHwExampleSensor.h/BpHwExampleSensor.h 分别定义了Binder Native端和Proxy端的基类。我们的任务是创建一个类继承自::android::hardware::example::V1_0::IExampleSensor并实现其所有纯虚函数。3.2 实现HAL服务类我们创建一个文件ExampleSensor.cpp和ExampleSensor.h。ExampleSensor.h#ifndef ANDROID_HARDWARE_EXAMPLE_V1_0_EXAMPLESENSOR_H #define ANDROID_HARDWARE_EXAMPLE_V1_0_EXAMPLESENSOR_H #include android/hardware/example/1.0/IExampleSensor.h #include hidl/MQDescriptor.h #include hidl/Status.h #include fcntl.h // 用于文件操作 #include unistd.h // 用于 read/write/close #include sys/ioctl.h // 用于 ioctl namespace android { namespace hardware { namespace example { namespace V1_0 { namespace implementation { using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::hardware::Return; using ::android::hardware::Void; using ::android::sp; struct ExampleSensor : public IExampleSensor { // 确保构造函数和析构函数是公开的 ExampleSensor(); virtual ~ExampleSensor(); // IExampleSensor 接口方法实现 Returnvoid getName(getName_cb _hidl_cb) override; ReturnResult setMode(SensorMode mode) override; ReturnResult setThreshold(uint32_t threshold) override; Returnvoid readData(readData_cb _hidl_cb) override; // 与内核驱动通信的辅助方法 private: int openSysfsNode(const char* path, int flags); ssize_t readSysfsInt(const char* path, int* value); int mIoctlFd; // ioctl使用的设备文件描述符 static const char* SYSFS_VALUE_PATH; // 例如 /sys/class/example_sensor/example_sensor0/value static const char* DEVICE_NODE; // 例如 /dev/example_sensor // 假设从驱动头文件定义了ioctl命令码 static const unsigned long IOCTL_SET_CUSTOM_CMD 0xE0; }; } // namespace implementation } // namespace V1_0 } // namespace example } // namespace hardware } // namespace android #endif // ANDROID_HARDWARE_EXAMPLE_V1_0_EXAMPLESENSOR_HExampleSensor.cpp#include ExampleSensor.h namespace android { namespace hardware { namespace example { namespace V1_0 { namespace implementation { // 静态成员初始化路径需与你的内核驱动实际创建的一致 const char* ExampleSensor::SYSFS_VALUE_PATH /sys/class/example_sensor/example_sensor0/value; const char* ExampleSensor::DEVICE_NODE /dev/example_sensor; ExampleSensor::ExampleSensor() { // 构造函数打开用于ioctl的设备文件 mIoctlFd open(DEVICE_NODE, O_RDWR); if (mIoctlFd 0) { ALOGE(Failed to open device node %s: %s, DEVICE_NODE, strerror(errno)); // 注意这里不直接让构造函数失败因为sysfs可能还能用。 // 更健壮的做法是设置一个状态标志。 } else { ALOGI(Successfully opened device node %s, DEVICE_NODE); } } ExampleSensor::~ExampleSensor() { // 析构函数关闭文件描述符 if (mIoctlFd 0) { close(mIoctlFd); } } // 辅助函数打开sysfs节点 int ExampleSensor::openSysfsNode(const char* path, int flags) { int fd open(path, flags); if (fd 0) { ALOGE(Failed to open sysfs node %s: %s, path, strerror(errno)); } return fd; } // 辅助函数从sysfs读取一个整数值 ssize_t ExampleSensor::readSysfsInt(const char* path, int* value) { char buffer[32] {0}; int fd openSysfsNode(path, O_RDONLY); if (fd 0) { return -1; } ssize_t bytes_read read(fd, buffer, sizeof(buffer)-1); close(fd); if (bytes_read 0) { buffer[bytes_read] \0; *value atoi(buffer); return bytes_read; } else { ALOGE(Failed to read from %s, path); return -1; } } // 实现接口方法getName Returnvoid ExampleSensor::getName(getName_cb _hidl_cb) { // 返回我们在接口中定义的传感器名字 _hidl_cb(MyVirtualExampleSensor); return Void(); } // 实现接口方法setMode ReturnIExampleSensor::Result ExampleSensor::setMode(SensorMode mode) { ALOGI(setMode called with mode: %d, static_castint(mode)); // 这里演示使用ioctl将模式传递给内核驱动 if (mIoctlFd 0) { ALOGE(Device not open, cannot set mode.); return Result::INVALID_OPERATION; } int kernel_mode static_castint(mode); // 假设驱动定义了一个ioctl命令 EXAMPLE_SENSOR_SET_MODE if (ioctl(mIoctlFd, IOCTL_SET_CUSTOM_CMD, kernel_mode) 0) { ALOGE(ioctl set mode failed: %s, strerror(errno)); return Result::IOCTL_FAILED; // 假设这是返回的错误码之一 } return Result::OK; } // 实现接口方法setThreshold ReturnIExampleSensor::Result ExampleSensor::setThreshold(uint32_t threshold) { ALOGI(setThreshold called with value: %u, threshold); // 这个例子中我们只是记录一下实际可能通过sysfs或ioctl设置 // 为了演示我们假设通过写sysfs来设置 char threshold_str[16]; snprintf(threshold_str, sizeof(threshold_str), %u, threshold); int fd openSysfsNode(/sys/class/example_sensor/example_sensor0/threshold, O_WRONLY); if (fd 0) { return Result::SYSFS_OPEN_FAILED; } ssize_t written write(fd, threshold_str, strlen(threshold_str)); close(fd); if (written 0) { ALOGE(Failed to write threshold: %s, strerror(errno)); return Result::SYSFS_WRITE_FAILED; } return Result::OK; } // 实现接口方法readData Returnvoid ExampleSensor::readData(readData_cb _hidl_cb) { SensorData data {}; int raw_value 0; // 1. 通过sysfs从驱动读取原始值 if (readSysfsInt(SYSFS_VALUE_PATH, raw_value) 0) { data.timestamp ::android::elapsedRealtimeNano(); // 获取当前时间戳 data.value static_castint32_t(raw_value); data.status SensorStatus::DATA_OK; ALOGI(readData: read value %d from sysfs, raw_value); } else { data.status SensorStatus::DATA_INVALID; ALOGE(readData: failed to read from sysfs); } // 2. 通过回调函数将数据返回给调用者上层的Framework _hidl_cb(data); return Void(); } } // namespace implementation } // namespace V1_0 } // namespace example } // namespace hardware } // namespace android关键点解析错误处理与内核驱动通信的每一步open,read,write,ioctl都必须检查返回值。ALOGI和ALOGE是Android的日志宏输出到logcat是调试HAL的必备工具。数据类型转换注意HIDL中定义的uint32_t、枚举等与C基本类型以及内核驱动期望类型之间的转换。回调函数readData方法接收一个readData_cb参数_hidl_cb这是一个HIDL生成的std::function。当你获取到数据后必须调用这个回调将数据传回给调用者。这是HIDL中异步返回数据的标准方式。文件描述符管理在构造函数中打开设备文件在析构函数中关闭这是一种RAII资源获取即初始化思想的简单应用防止资源泄漏。4. 创建HAL模块入口与注册服务仅有服务实现类还不够我们需要创建一个HAL模块并在系统启动时自动注册我们的服务。这是通过实现一个符合Android HAL加载规范的共享库.so文件来完成的。4.1 实现模块的HAL_MODULE_INFO_SYM创建一个文件ExampleSensorHal.cpp#define LOG_TAG ExampleSensorHal #include android/log.h #include hidl/HidlSupport.h #include hidl/HidlTransportSupport.h #include android/hardware/example/1.0/IExampleSensor.h #include ExampleSensor.h using android::hardware::example::V1_0::IExampleSensor; using android::hardware::example::V1_0::implementation::ExampleSensor; using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; using android::sp; using android::status_t; using android::OK; // 这是HAL模块的标准入口结构体 extern C IExampleSensor* HIDL_FETCH_IExampleSensor(const char* /* name */) { return new ExampleSensor(); } // HAL模块信息结构体必须命名为 HMI (HAL_MODULE_INFO_SYM) struct example_sensor_module { struct hw_module_t common; }; // 定义模块的 open 函数 static int open_example_sensor(const struct hw_module_t* module, const char* /* id */, struct hw_device_t** device) { if (module nullptr || device nullptr) { ALOGE(Null pointer passed to open_example_sensor); return -EINVAL; } // 我们这里不返回传统的hw_device_t因为HIDL HAL使用不同的注册机制。 // 这个函数体对于纯HIDL HAL可能为空或仅做简单检查。 // 但为了满足hw_module_methods_t的要求我们仍需定义它。 ALOGI(Legacy open function called for example sensor HAL (likely not used by HIDL).); return 0; // 返回0表示成功但实际设备创建由HIDL完成。 } // 模块的方法表 static struct hw_module_methods_t example_sensor_module_methods { .open open_example_sensor, }; // 核心定义 HAL_MODULE_INFO_SYM 变量 // 系统在加载 android.hardware.example1.0-service.rc 时会查找这个符号。 struct example_sensor_module HAL_MODULE_INFO_SYM { .common { .tag HARDWARE_MODULE_TAG, .module_api_version HARDWARE_MODULE_API_VERSION(1, 0), .hal_api_version HARDWARE_HAL_API_VERSION, .id example.sensor, // 必须与 default 实现的名字匹配 .name Example Sensor HAL, .author Your Name, .methods example_sensor_module_methods, .dso nullptr, // 动态共享对象句柄加载后由系统填充 .reserved {0}, }, };4.2 实现HIDL服务的main函数Service入口对于HIDL HAL我们还需要一个独立的服务进程入口。创建文件service.cpp#define LOG_TAG example.sensor1.0-service #include android-base/logging.h #include hidl/HidlTransportSupport.h #include android/hardware/example/1.0/IExampleSensor.h #include ExampleSensor.h using android::hardware::example::V1_0::IExampleSensor; using android::hardware::example::V1_0::implementation::ExampleSensor; using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; using android::sp; using android::status_t; using android::OK; int main() { // 1. 配置RPC线程池。HIDL使用Binder进行IPC需要线程池来处理并发请求。 // 第一个参数true表示将此线程主线程也加入线程池。 configureRpcThreadpool(1, true /* callerWillJoin */); // 2. 创建我们的HAL服务实例 spIExampleSensor service new ExampleSensor(); if (service nullptr) { LOG(ERROR) Cannot create instance of ExampleSensor HAL; return -1; } // 3. 尝试注册服务到hwservicemanager status_t status service-registerAsService(); if (status ! OK) { LOG(ERROR) Cannot register example.sensor1.0 HAL service ( status ); return -1; } LOG(INFO) Example Sensor HAL service started successfully.; // 4. 加入RPC线程池进入无限循环等待来自Framework的Binder调用。 // 这个调用通常不会返回。 joinRpcThreadpool(); // 正常情况下程序不会执行到这里。 LOG(ERROR) Example Sensor HAL service is shutting down unexpectedly.; return 1; }4.3 编写Android.bp构建文件为了让AOSP构建系统编译我们的HAL需要在模块目录下创建Android.bp// 编译生成HAL实现库libandroid.hardware.example1.0-impl.so cc_library_shared { name: android.hardware.example1.0-impl, relative_install_path: hw, proprietary: true, srcs: [ ExampleSensor.cpp, ExampleSensorHal.cpp, ], shared_libs: [ liblog, libhidlbase, libhidltransport, libutils, android.hardware.example1.0, ], header_libs: [ android.hardware.example1.0-impl-headers, ], export_include_dirs: [.], } // 编译生成可执行的服务程序android.hardware.example1.0-service cc_binary { name: android.hardware.example1.0-service, relative_install_path: hw, init_rc: [android.hardware.example1.0-service.rc], srcs: [service.cpp], shared_libs: [ liblog, libcutils, libdl, libbase, libutils, libhidlbase, libhidltransport, libhwbinder, android.hardware.example1.0, android.hardware.example1.0-impl, // 链接我们上面编译的实现库 ], // 确保服务二进制文件依赖于实现库 required: [android.hardware.example1.0-impl], }4.4 编写Service的init.rc文件创建android.hardware.example1.0-service.rc定义如何启动我们的HAL服务进程service example-hal-1-0 /vendor/bin/hw/android.hardware.example1.0-service class hal user system group system capabilities SYS_NICE # 确保在关键服务之后启动例如 hwservicemanager onrestart restart audioserver # 根据需要添加其他重启触发器 seclabel u:r:hal_example_sensor:s0 # 需要相应的SELinux策略5. 集成、编译与测试验证代码编写完成后我们需要将其集成到AOSP构建系统中编译并推送到设备或模拟器上进行测试。5.1 集成到AOSP源码树假设你的AOSP源码根目录为~/aosp。在hardware/interfaces/example/1.0/目录下应该已经有第一部分创建的IExampleSensor.hal文件。在该目录下创建default/子目录这是HIDL实现约定的常见位置。将我们编写的所有.cpp、.h、Android.bp、.rc文件都放入default/目录。你的目录结构应类似于hardware/interfaces/example/1.0/ ├── Android.bp (由hidl-gen自动生成用于编译接口) ├── IExampleSensor.hal └── default/ ├── Android.bp (我们刚写的编译实现和服务) ├── ExampleSensor.h ├── ExampleSensor.cpp ├── ExampleSensorHal.cpp ├── service.cpp └── android.hardware.example1.0-service.rc5.2 编译与部署在AOSP根目录下执行source build/envsetup.sh lunch aosp_x86_64-eng # 根据你的目标设备选择这里用模拟器 make android.hardware.example1.0-service编译成功后相关文件会输出到out/target/product/device/vendor目录下。vendor/lib64/hw/android.hardware.example1.0-impl.so(或vendor/lib/hw/)vendor/bin/hw/android.hardware.example1.0-service你可以使用adb push将它们推送到设备的对应位置或者直接make snod重新生成system镜像并刷机。对于开发调试推送到/vendor分区如果可写更快捷。5.3 手动测试HAL服务启动服务adb root adb shell setenforce 0 # 临时禁用SELinux避免权限问题 adb shell start example-hal-1-0 # 根据rc文件中的服务名或者直接运行二进制文件adb shell /vendor/bin/hw/android.hardware.example1.0-service 检查服务是否注册成功adb shell lshal | grep example你应该能看到类似android.hardware.example1.0::IExampleSensor/default的条目状态应为registered。使用HIDL测试工具 AOSP提供了hidl-test和lshal debug等工具但更直接的方法是写一个简单的C测试客户端。这里提供一个极简的示例test_client.cpp#include android/hardware/example/1.0/IExampleSensor.h #include hidl/Status.h #include hidl/HidlSupport.h #include iostream #include unistd.h using android::hardware::example::V1_0::IExampleSensor; using android::hardware::example::V1_0::SensorData; using android::hardware::example::V1_0::Result; using android::hardware::example::V1_0::SensorMode; using android::sp; int main() { // 获取HAL服务 spIExampleSensor service IExampleSensor::getService(); if (service nullptr) { std::cerr Failed to get example sensor service! std::endl; return -1; } std::cout Service obtained. std::endl; // 测试 getName service-getName([](const android::hardware::hidl_string name) { std::cout Sensor name: name.c_str() std::endl; }); // 测试 setMode Result res service-setMode(SensorMode::NORMAL); std::cout setMode result: static_castint(res) std::endl; // 测试 readData service-readData([](const SensorData data) { std::cout Read data - Status: static_castint(data.status) , Value: data.value , Timestamp: data.timestamp std::endl; }); sleep(1); // 等待异步回调完成 return 0; }将其编译并推送到设备执行观察logcat输出和程序输出验证所有接口是否工作正常。观察Logcatadb logcat | grep -E “ExampleSensor|example.sensor”这是最重要的调试手段确保你的ALOGI/ALOGE日志都能正确打印从中可以看到函数调用顺序、参数值以及错误信息。6. 常见问题排查与调试心得在实现和调试HAL的过程中你几乎一定会遇到各种问题。以下是一些典型问题及其排查思路问题1服务启动失败lshal看不到服务注册。可能原因SELinux权限拒绝。这是最常见的问题。排查查看adb logcat | grep avc会有详细的SELinux拒绝信息。解决需要为你的HAL服务添加SELinux策略。这涉及在device/vendor/device/sepolicy/目录下添加.te文件定义hal_example_sensor域并允许其访问hwservicemanager、对应的设备节点如/dev/example_sensor和sysfs文件。这是一个专门的话题初期调试可以临时setenforce 0来绕过。问题2readData回调始终返回DATA_INVALID或值不对。可能原因1sysfs文件路径错误或权限不足。排查adb shell进入设备手动cat你代码中定义的SYSFS_VALUE_PATH看是否能读到值以及值是否符合预期。检查文件权限是否为644。可能原因2内核驱动返回的数据格式不对。我们的示例代码用atoi解析字符串如果驱动返回了非数字字符或带换行符会导致解析错误。排查在readSysfsInt函数中将读取到的原始buffer打印出来ALOGI(“Raw read: %s”, buffer)确认其内容。问题3ioctl调用失败返回-1且errno为25 (ENOTTY)。可能原因ioctl的命令码与内核驱动定义的命令码不匹配。ioctl命令码的生成有严格的规则涉及方向、数据大小等必须确保用户空间和内核空间的头文件中定义完全一致。解决确保#include了正确的内核头文件或者使用驱动中通过_IO,_IOR,_IOW,_IOWR宏明确定义的命令码。问题4HIDL接口方法调用没反应日志也没输出。可能原因服务进程崩溃或阻塞。Binder调用是同步的如果服务端在处理方法时崩溃客户端会长时间等待或直接失败。排查使用ps -A | grep example查看服务进程是否在运行。查看logcat是否有服务进程的崩溃栈信息FATAL EXCEPTION,signal等。在服务端每个方法的入口加ALOGI确认调用是否到达。问题5编译时找不到头文件或链接失败。可能原因Android.bp中的依赖声明不正确。排查仔细检查shared_libs和header_libs。对于HIDL接口必须依赖android.hardware.example1.0。对于实现通常还需要链接对应的-impl库。确保路径和模块名拼写正确。实操心得日志是你的眼睛在HAL开发中ALOGD(Debug)、ALOGI(Info)、ALOGE(Error)是你最好的朋友。在关键函数入口、出口、错误分支、数据转换处都加上日志能极大提升调试效率。循序渐进不要试图一次性实现所有功能。先确保服务能成功注册lshal能看到。然后实现一个最简单的方法如getName并测试通过。再逐步添加readData先返回一个硬编码值最后再打通与内核的通信。善用现有HAL作参考AOSP源码中hardware/interfaces/下有大量官方HAL实现如sensors、light、power等。当你不知道如何实现某个细节时去参考这些官方实现是最快的学习途径。例如可以查看hardware/interfaces/sensors/2.0/default/下的代码。理解Binder线程池configureRpcThreadpool和joinRpcThreadpool的调用确保了服务能处理并发请求。如果你在HAL方法中执行了耗时操作如复杂的计算或慢速的I/O应考虑将其移到单独的线程中避免阻塞Binder线程导致整个服务无响应。通过以上步骤你已经完成了一个功能完整的Android HAL服务实现。它接收来自上层的命令通过标准Linux机制与内核驱动交互并将数据返回。这个过程深刻体现了Android系统“分层”与“抽象”的设计哲学。在下一部分我们将探讨如何为这个HAL编写JNI层和Java Framework服务最终让一个普通的Android App能够调用到我们自定义的传感器功能完成从App到Kernel的完整数据链路。
返回列表