windows驱动开发14.驱动与应用程序异步通信
驱动与应用程序异步通信
1.驱动与应用异步通信是什么?
驱动与应用程序的异步通信是指应用程序向驱动程序发送请求后,驱动程序并不立即返回响应,而是继续执行其他操作,直到请求处理完成,驱动程序才通知应用程序或提供结果。异步通信通常用于需要处理长时间操作或等待外部事件的场景,这样可以避免应用程序在等待时被阻塞。
异步通信的工作原理:
- 请求发送:应用程序通过调用
DeviceIoControl、ReadFile或WriteFile等函数向驱动程序发送请求。 - 请求排队:驱动程序收到请求后,不会立即返回,而是将请求添加到请求队列中,继续处理其他任务或者立即返回,表示操作正在进行。
- 事件通知:当驱动程序完成请求的处理后,它会通过某种机制(如信号量、事件、I/O 完成端口等)通知应用程序,通常是通过回调机制或事件对象。
- 获取结果:应用程序可以使用等待(如
WaitForSingleObject)或轮询的方式等待通知,或者在通知到来时继续执行后续操作。
常见的异步机制:
- **I/O 完成端口 (IOCP)**:用于处理大量的并发 I/O 请求,特别适用于高性能的网络或磁盘驱动场景。应用程序可以等待 I/O 完成端口上的事件,获得 I/O 操作完成的通知。
- **事件 (Event)**:驱动程序可以创建一个事件对象,处理完成时发出信号,应用程序等待该事件的信号。
- 信号量和互斥量:用于协调并发访问资源,在完成某个操作后发出信号通知应用程序。
- 回调函数:驱动程序可以在完成某个操作后调用应用程序提供的回调函数,告知结果。
异步通信的优点:
- 非阻塞:应用程序发出请求后可以继续做其他事情,不会被阻塞。
- 高效性:特别适用于需要处理多个任务或长时间运行的任务(如文件读写、网络通信等),可以提高系统的响应性。
- 用户体验:减少了等待时间,提升用户交互体验,特别是在图形界面应用中,避免了卡死界面的问题。
示例:驱动的异步操作
假设你有一个驱动程序处理文件读取请求,但读取文件可能需要较长时间。使用异步操作,驱动程序可以立即返回,而不是等待文件读取完成。
驱动程序代码(处理异步请求):
1 | NTSTATUS DispatchRead(PDEVICE_OBJECT DeviceObject, PIRP Irp) |
应用程序代码(等待异步操作结果):
1 | BOOL ReadFromDriverAsync(HANDLE hDevice, char* buffer, DWORD bufferSize) |
总结:
- 异步通信有助于提高应用程序和驱动程序之间的效率,避免因长时间任务而阻塞应用程序的执行。
- 驱动程序可以通过事件、回调或其他机制通知应用程序任务完成,而不需要应用程序等待长时间的同步操作。
2.用户层使用异步IO通信
1.应用层程序
要点:
- 使用
DeviceIoControl的异步版本:使用DeviceIoControl时,指定OVERLAPPED结构体来实现异步操作。 - 使用
ReadFile和WriteFile的异步版本:通过指定OVERLAPPED结构体实现异步操作。 - 等待操作完成:在调用异步操作后,需要等待操作完成,可以通过
GetOverlappedResult来获取结果。
1 |
|
2.驱动程序,使用之前的同步通信版本
1 |
|
3.用户层使用异步通信调用 ReadFile / WriteFile /
✅ 改造目标
用户层:
打开设备时指定 FILE_FLAG_OVERLAPPED。
使用 OVERLAPPED 结构异步调用 ReadFile / WriteFile / DeviceIoControl。
使用 IOCP 获取操作完成事件。
内核层:
当前驱动中已经设置了 DO_BUFFERED_IO,不需要修改。
保持原有分发逻辑,配合异步使用 IoMarkIrpPending 时才需要内核主动完成 IRP,本例暂不涉及。
🧠 IOCP 异步通信原理简述
每个 HANDLE(设备、文件等)可关联到一个完成端口(IOCP)。
每个 I/O 操作发起时,传入 OVERLAPPED。
操作完成后,调用 GetQueuedCompletionStatus() 来获取通知。
🧾 用户层代码改为异步 + IOCP
1 |
|
4.用户层使用异步通信有几种方法?
以下是三种异步通信方式的用户层示例代码:
1. 使用事件的异步通信
在这种方式中,用户层在调用 DeviceIoControl 时传递一个事件句柄。驱动完成操作后,会触发这个事件,通知用户层操作完成。
用户层代码(使用事件)
1 |
|
2. 使用回调的异步通信
在这种方式中,用户层提供一个回调函数,驱动完成操作后调用这个回调函数。回调函数负责处理结果并通知用户层。
用户层代码(使用回调)
1 |
|
3. 使用 I/O 完成端口的异步通信
在这种方式中,用户层使用 I/O 完成端口来接收异步操作的完成通知。驱动在操作完成时将请求提交到 I/O 完成端口,用户层可以通过 GetQueuedCompletionStatus 来获取完成状态。
用户层代码(使用 I/O 完成端口)
1 |
|
总结
- 事件机制:通过事件通知用户层异步操作的完成。
- 回调机制:驱动在完成操作后调用用户层提供的回调函数。
- I/O 完成端口:通过完成端口机制,用户层可以异步接收操作结果。
每种方式的选择依据实际场景而定,通常 I/O 完成端口用于更复杂的异步模型,特别是在高并发或高性能要求的场景中。事件和回调则适用于较为简单的异步需求。
2.驱动层使用异步通信有几种方法?
是的,为了支持以上三种异步通信方式,驱动代码需要做一些修改。以下是每种方式所需的驱动端修改:
1.使用事件的异步通信
为了支持事件机制,驱动需要通过 IoMarkIrpPending 和 IoSetCompletionRoutine 来异步处理 I/O 操作。这意味着用户层传递的事件句柄将在操作完成时被触发。
驱动端代码修改:
1 | NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp) |
2.使用回调的异步通信
要支持回调机制,驱动代码需要使用 IoSetCompletionRoutine 来指定回调函数。当 I/O 操作完成时,驱动会调用回调函数。
驱动端代码修改:
1 | NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp) |
3.使用 I/O 完成端口的异步通信
驱动需要使用 IoCreateCompletionPort 和 IoQueueDpc 来支持 I/O 完成端口。完成端口允许用户层通过 GetQueuedCompletionStatus 来异步获取操作结果。
驱动端代码修改:
1 | NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp) |
总结
事件机制:驱动代码需使用 IoMarkIrpPending 和 IoSetCompletionRoutine 来支持事件驱动的异步操作。
回调机制:通过 IoSetCompletionRoutine 在驱动完成操作时调用回调函数。
I/O 完成端口:驱动需要使用 IoQueueDpc 或直接将请求提交到完成端口,用户层通过 GetQueuedCompletionStatus 来获取操作完成的通知。
每种方式都需要对驱动的 I/O 处理流程进行相应的调整,以便正确支持异步操作并通知用户层。
