4. HDF驱动模型与消息机制:驱动入口与退出函数、消息分发与处理、异步消息与同步调用、HDF消息队列实现原理
好,咱们进入第四章。这一章讲的是HDF驱动框架的“神经中枢”——消息机制。很多刚接触HDF的兄弟,写驱动时能把Bind、Init、Release这几个入口函数填好,但一到用户态和内核态通信、或者驱动间消息传递,就开始懵了。
我个人习惯,把HDF的消息机制看作是驱动世界的“快递系统”。你想想看,驱动本身是个干活儿的,但怎么通知它干活?干完活怎么把结果告诉你?这就是消息机制要解决的问题。
4.1 驱动入口与退出函数:驱动的“生老病死”
每个HDF驱动,都有三个核心生命周期函数。我在项目中见过不少新手,只写了Init,把Release扔一边,结果卸载驱动时资源没释放,系统直接挂掉。
| 函数 | 作用 | 调用时机 |
|---|---|---|
Bind |
绑定服务接口,注册消息处理函数 | 驱动加载时,最早调用 |
Init |
初始化硬件、分配资源、配置寄存器 | Bind成功后调用 |
Release |
释放资源、卸载硬件、清理消息队列 | 驱动卸载时调用 |
来看一个典型的驱动入口代码片段:
// 驱动入口结构体
struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_SAMPLE",
.Bind = SampleDriverBind,
.Init = SampleDriverInit,
.Release = SampleDriverRelease,
};
// 驱动入口宏
HDF_INIT(g_sampleDriverEntry);
Init里申请了DMA缓冲区,但在Release里忘了释放。结果驱动反复加载卸载几次后,系统内存就耗尽了。记住:Bind注册、Init分配、Release清理,这个闭环一定要完整。
4.2 消息分发与处理:谁来接单?
驱动起来了,用户态程序怎么跟它说话?HDF用的是服务接口+消息分发的模式。说白了,就是驱动在Bind里注册一个“客服窗口”,用户态发消息过来,驱动根据消息ID决定谁来处理。
消息分发的核心结构体是HdfDriverService和HdfDevObject。我习惯把消息分发函数设计成一个switch-case结构:
// 消息分发函数
static int32_t SampleDriverDispatch(
struct HdfDeviceIoService *service,
int32_t cmdId,
struct HdfSBuf *data,
struct HdfSBuf *reply)
{
(void)service;
switch (cmdId) {
case SAMPLE_CMD_READ:
// 处理读命令
return SampleReadHandler(data, reply);
case SAMPLE_CMD_WRITE:
// 处理写命令
return SampleWriteHandler(data, reply);
case SAMPLE_CMD_IOCTL:
// 处理控制命令
return SampleIoctlHandler(data, reply);
default:
HDF_LOGE("Unknown cmdId: %d", cmdId);
return HDF_ERR_INVALID_PARAM;
}
}
// 在Bind中注册
static int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{
static struct HdfDeviceIoService service = {
.Dispatch = SampleDriverDispatch,
.Open = NULL,
.Release = NULL,
};
deviceObject->service = &service;
return HDF_SUCCESS;
}
4.3 异步消息与同步调用:等还是不等?
这是消息机制里最容易搞混的地方。同步调用,就是用户态发个消息过去,然后阻塞等待驱动处理完返回结果。异步消息呢,就是发完消息就不管了,驱动处理完后通过回调或者事件通知你。
| 特性 | 同步调用 | 异步消息 |
|---|---|---|
| 调用方是否阻塞 | 是,等待返回 | 否,立即返回 |
| 适用场景 | 短操作、配置类命令 | 长操作、数据流、中断处理 |
| 实现复杂度 | 低 | 高(需要回调/事件机制) |
| 资源占用 | 占用调用线程 | 不占用调用线程 |
用户态调用同步接口的典型写法:
// 用户态同步调用
int32_t ret;
struct HdfSBuf *data = HdfSBufObtainDefaultSize();
struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
// 填充数据
HdfSbufWriteInt32(data, 0x1234);
// 同步调用,会阻塞直到驱动返回
ret = serv->Dispatch(serv, SAMPLE_CMD_READ, data, reply);
if (ret != HDF_SUCCESS) {
HDF_LOGE("Dispatch failed, ret=%d", ret);
}
// 读取返回结果
int32_t value;
HdfSbufReadInt32(reply, &value);
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
4.4 HDF消息队列实现原理:快递中转站
消息队列,说白了就是驱动内部的一个“快递中转站”。用户态发来的消息,先放到队列里,驱动的工作线程再从队列里取出来处理。这样做的好处是:解耦——发送方不用等接收方处理完;削峰填谷——消息多的时候排队,不会把系统冲垮。
HDF的消息队列实现,核心是HdfMessageQueue结构体。我画了一张图,帮你理解它的内部结构:
消息队列的核心数据结构长这样:
// 消息节点结构体
struct HdfMessageNode {
struct HdfMessageNode *next; // 链表指针
uint32_t cmdId; // 消息ID
struct HdfSBuf *data; // 消息数据
struct HdfSBuf *reply; // 回复数据(同步用)
struct HdfCompletion *completion; // 完成量(同步用)
};
// 消息队列结构体
struct HdfMessageQueue {
struct HdfMessageNode *head; // 队列头
struct HdfMessageNode *tail; // 队列尾
uint32_t count; // 队列中消息数量
struct OsalMutex lock; // 互斥锁
struct OsalSem sem; // 信号量,用于通知工作线程
};
OsalMutex保护链表操作,用OsalSem实现生产者和消费者的同步。工作线程在OsalSemWait上阻塞,有新消息入队时OsalSemPost唤醒它。这个模式在嵌入式系统里非常经典。
入队和出队的核心逻辑:
// 消息入队
int32_t HdfSendMessageToQueue(struct HdfMessageQueue *queue,
struct HdfMessageNode *node)
{
if (queue == NULL || node == NULL) {
return HDF_ERR_INVALID_PARAM;
}
OsalMutexLock(&queue->lock);
// 尾插法
if (queue->tail == NULL) {
queue->head = node;
queue->tail = node;
} else {
queue->tail->next = node;
queue->tail = node;
}
queue->count++;
OsalMutexUnlock(&queue->lock);
// 唤醒工作线程
OsalSemPost(&queue->sem);
return HDF_SUCCESS;
}
// 消息出队
struct HdfMessageNode *HdfGetMessageFromQueue(struct HdfMessageQueue *queue)
{
struct HdfMessageNode *node = NULL;
// 等待消息
OsalSemWait(&queue->sem, HDF_WAIT_FOREVER);
OsalMutexLock(&queue->lock);
node = queue->head;
if (node != NULL) {
queue->head = node->next;
if (queue->head == NULL) {
queue->tail = NULL;
}
node->next = NULL;
queue->count--;
}
OsalMutexUnlock(&queue->lock);
return node;
}
OsalSemWait,结果死锁了。为什么?因为工作线程自己把自己阻塞了,队列里的消息永远没人处理。记住:工作线程里不要再等待同一个队列的消息。
嗯,消息队列这块,说白了就是“生产者-消费者”模型在驱动里的具体实现。你理解了这一点,HDF的消息机制就掌握了一大半。同步调用和异步消息的区别,本质上就是“要不要等回复”的问题——同步调用在消息节点里塞了个completion,发完消息后等它完成;异步消息则通过注册的回调函数来通知结果。