2. HDF驱动框架初探:核心概念与加载流程
好,咱们正式开始接触鸿蒙的驱动框架——HDF。说实话,我第一次看HDF的代码时,第一反应是“这玩意儿怎么这么绕”。但后来做多了才发现,它的设计其实很精巧。说白了,HDF就是一套帮你管理驱动生命周期的“管家”,你只需要告诉它“我的驱动长什么样、怎么初始化”,剩下的加载、卸载、消息传递,它都帮你包办了。
这一节,咱们先摸清HDF的三个核心概念,再走一遍驱动的加载流程,最后看看驱动之间怎么互相“喊话”。
2.1 三个核心概念:DeviceObject、DriverEntry、HdfDriverEntry
在HDF里,驱动被抽象成了几个关键结构体。你写驱动,本质上就是在填充这些结构体。我习惯把它们比作“身份证”、“简历”和“入职登记表”。
| 概念 | 结构体名 | 我的比喻 | 核心作用 |
|---|---|---|---|
| DeviceObject | DeviceObject |
身份证 | 代表一个具体的设备实例,包含设备号、属性、服务接口 |
| DriverEntry | DriverEntry |
简历 | 描述驱动的基本信息,比如名字、所属厂商、版本 |
| HdfDriverEntry | HdfDriverEntry |
入职登记表 | 驱动真正的入口,定义了Init、Bind、Release三个回调函数 |
关键点: HdfDriverEntry 是驱动开发者最常打交道的结构体。你写的驱动代码,最终都要挂到它的三个回调里。
咱们直接看代码。一个典型的传感器驱动入口长这样:
// 传感器驱动的HdfDriverEntry定义
struct HdfDriverEntry g_sensorDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_SENSOR_DRIVER",
.Bind = SensorDriverBind, // 绑定设备服务到HDF框架
.Init = SensorDriverInit, // 初始化硬件和资源
.Release = SensorDriverRelease, // 释放资源
};
// 驱动入口注册宏
HDF_INIT(g_sensorDriverEntry);
嗯,这里要注意:HDF_INIT 这个宏会把你的驱动入口放到特定的段里,HDF框架启动时会自动扫描这个段,找到所有驱动。我曾经在移植一个加速度计驱动时,忘了加这个宏,结果驱动死活加载不上,排查了半天……
2.2 驱动加载流程:从开机到驱动跑起来
驱动是怎么从无到有的?我画了一张流程图,你看完就明白了。
流程其实不复杂,但有几个坑我得提醒你:
- Bind() 里不要做耗时操作——它只是“绑定”,不是“初始化”。我见过有人把I2C初始化写在Bind里,结果导致驱动加载超时。
- Init() 失败要回滚——如果Init里申请了内存但后续步骤失败,记得释放。否则会造成内存泄漏。
- Release() 一定要幂等——HDF可能会多次调用Release,你的代码要能扛得住重复释放。
曾经踩过的坑: 我在调试一个温湿度传感器驱动时,Init()里调用了GPIO配置,但没检查返回值。结果GPIO被其他驱动占用了,Init返回成功,但后续读写全部失败。后来我养成了一个习惯:所有硬件操作都要检查返回值,并在Init失败时打印明确的错误码。
2.3 驱动消息机制:驱动之间怎么“打电话”
驱动不是孤岛。比如你的传感器驱动需要把数据发给显示驱动,或者上层APP要下发配置。HDF提供了一套消息机制,让驱动之间可以互相通信。
消息机制的核心是 HdfIoService 和 HdfSBuf。前者是服务的“电话线”,后者是传递数据的“信封”。
来看一个实际例子:上层APP通过消息给传感器驱动下发采样率配置。
// 上层发送消息(简化版)
struct HdfIoService *service = HdfIoServiceBind("HDF_SENSOR_DRIVER");
if (service == NULL) {
printf("服务绑定失败\n");
return;
}
struct HdfSBuf *data = HdfSBufObtain(1024);
// 把采样率写入SBuf
uint32_t sampleRate = 100; // 100Hz
HdfSBufWriteUint32(data, sampleRate);
// 发送消息,消息ID为 CMD_SET_SAMPLE_RATE
int ret = service->dispatcher->Dispatch(service, CMD_SET_SAMPLE_RATE, data, NULL);
if (ret != 0) {
printf("消息发送失败, ret=%d\n", ret);
}
HdfSBufRecycle(data);
HdfIoServiceRecycle(service);
而在驱动端,你需要在Bind()里注册一个Dispatch回调:
static int SensorDriverDispatch(struct HdfDeviceIoClient *client,
int cmdId, struct HdfSBuf *data, struct HdfSBuf *reply)
{
switch (cmdId) {
case CMD_SET_SAMPLE_RATE: {
uint32_t rate;
HdfSBufReadUint32(data, &rate);
// 设置硬件采样率
SensorSetSampleRate(rate);
break;
}
default:
break;
}
return 0;
}
static int SensorDriverBind(struct HdfDeviceObject *device)
{
static struct IDeviceIoService service = {
.Dispatch = SensorDriverDispatch,
};
device->service = &service;
return 0;
}
个人建议: 消息ID最好用枚举定义,并加上注释。我曾经在一个项目里用了魔数(比如0x01、0x02),三个月后自己都忘了0x01是干啥的。后来统一改成枚举,代码可读性好了很多。
消息机制有几个特点值得注意:
- 同步/异步:Dispatch默认是同步的,如果需要异步,可以用HdfSendEvent。
- 数据序列化:HdfSBuf支持多种数据类型(Uint32、String、Blob等),但要注意字节序——不同平台可能不一样。
- 权限控制:HDF框架会检查调用方的权限,不是谁都能随便发消息的。
2.4 小结:HDF框架的“三板斧”
回顾一下,HDF驱动开发的核心就三件事:
- 填好HdfDriverEntry——把Bind、Init、Release三个回调写好。
- 理解加载流程——从系统启动到驱动就绪,每一步都有明确的职责。
- 用好消息机制——通过Dispatch和SBuf实现驱动间通信。