驱动与应用程序异步通信

1.驱动与应用异步通信是什么?

驱动与应用程序的异步通信是指应用程序向驱动程序发送请求后,驱动程序并不立即返回响应,而是继续执行其他操作,直到请求处理完成,驱动程序才通知应用程序或提供结果。异步通信通常用于需要处理长时间操作或等待外部事件的场景,这样可以避免应用程序在等待时被阻塞。

异步通信的工作原理:

  1. 请求发送:应用程序通过调用 DeviceIoControlReadFileWriteFile 等函数向驱动程序发送请求。
  2. 请求排队:驱动程序收到请求后,不会立即返回,而是将请求添加到请求队列中,继续处理其他任务或者立即返回,表示操作正在进行。
  3. 事件通知:当驱动程序完成请求的处理后,它会通过某种机制(如信号量、事件、I/O 完成端口等)通知应用程序,通常是通过回调机制事件对象
  4. 获取结果:应用程序可以使用等待(如WaitForSingleObject)或轮询的方式等待通知,或者在通知到来时继续执行后续操作。

常见的异步机制:

  • **I/O 完成端口 (IOCP)**:用于处理大量的并发 I/O 请求,特别适用于高性能的网络或磁盘驱动场景。应用程序可以等待 I/O 完成端口上的事件,获得 I/O 操作完成的通知。
  • **事件 (Event)**:驱动程序可以创建一个事件对象,处理完成时发出信号,应用程序等待该事件的信号。
  • 信号量和互斥量:用于协调并发访问资源,在完成某个操作后发出信号通知应用程序。
  • 回调函数:驱动程序可以在完成某个操作后调用应用程序提供的回调函数,告知结果。

异步通信的优点:

  1. 非阻塞:应用程序发出请求后可以继续做其他事情,不会被阻塞。
  2. 高效性:特别适用于需要处理多个任务或长时间运行的任务(如文件读写、网络通信等),可以提高系统的响应性。
  3. 用户体验:减少了等待时间,提升用户交互体验,特别是在图形界面应用中,避免了卡死界面的问题。

示例:驱动的异步操作

假设你有一个驱动程序处理文件读取请求,但读取文件可能需要较长时间。使用异步操作,驱动程序可以立即返回,而不是等待文件读取完成。

驱动程序代码(处理异步请求):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NTSTATUS DispatchRead(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
PIO_COMPLETION_ROUTINE completionRoutine = stack->CompletionRoutine;

// 模拟一个需要处理的异步任务
// 提交到线程池或延时执行
KeDelayExecutionThread(KernelMode, FALSE, &Interval);

// 任务完成后,调用回调函数
if (completionRoutine) {
completionRoutine(DeviceObject, Irp);
}

return STATUS_PENDING; // 立即返回,表示异步操作
}

应用程序代码(等待异步操作结果):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
BOOL ReadFromDriverAsync(HANDLE hDevice, char* buffer, DWORD bufferSize)
{
DWORD bytesRead;
OVERLAPPED overlap = {0}; // 用于异步操作的结构体

// 设置回调函数来处理异步完成
overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

BOOL result = ReadFile(
hDevice,
buffer,
bufferSize,
&bytesRead,
&overlap);

if (!result) {
DWORD error = GetLastError();
if (error == ERROR_IO_PENDING) {
// 等待异步操作完成
WaitForSingleObject(overlap.hEvent, INFINITE);
printf("读取完成!\n");
}
} else {
printf("同步读取完成!\n");
}

CloseHandle(overlap.hEvent);
return result;
}

总结:

  • 异步通信有助于提高应用程序和驱动程序之间的效率,避免因长时间任务而阻塞应用程序的执行。
  • 驱动程序可以通过事件、回调或其他机制通知应用程序任务完成,而不需要应用程序等待长时间的同步操作。

2.用户层使用异步IO通信

1.应用层程序

要点:

  1. 使用 DeviceIoControl 的异步版本:使用 DeviceIoControl 时,指定 OVERLAPPED 结构体来实现异步操作。
  2. 使用 ReadFileWriteFile 的异步版本:通过指定 OVERLAPPED 结构体实现异步操作。
  3. 等待操作完成:在调用异步操作后,需要等待操作完成,可以通过 GetOverlappedResult 来获取结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <windows.h>
#include <stdio.h>

#define IOCTL_SEND_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GET_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DEVICE_NAME L"\\\\.\\MyMinimalDevice"
#define MAX_BUFFER 256

HANDLE OpenDevice()
{
HANDLE hDevice = CreateFile(
DEVICE_NAME,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, // 👈 异步模式
NULL);

if (hDevice == INVALID_HANDLE_VALUE)
printf("无法打开设备,错误码 = %lu\n", GetLastError());
else
printf("设备已打开。\n");

return hDevice;
}

BOOL WaitForOverlapped(HANDLE hDevice, OVERLAPPED* ov, DWORD* transferred)
{
DWORD result = WaitForSingleObject(ov->hEvent, INFINITE);
if (result != WAIT_OBJECT_0) {
printf("等待事件失败: %lu\n", GetLastError());
return FALSE;
}

if (!GetOverlappedResult(hDevice, ov, transferred, FALSE)) {
printf("GetOverlappedResult 失败: %lu\n", GetLastError());
return FALSE;
}

return TRUE;
}

BOOL SendMessageToDriver(HANDLE hDevice, const char* message)
{
DWORD returned = 0;
OVERLAPPED ov = { 0 };
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

BOOL result = DeviceIoControl(
hDevice,
IOCTL_SEND_MESSAGE,
(LPVOID)message,
(DWORD)strlen(message),
NULL,
0,
&returned,
&ov);

if (!result && GetLastError() == ERROR_IO_PENDING) {
result = WaitForOverlapped(hDevice, &ov, &returned);
}

CloseHandle(ov.hEvent);

if (!result)
printf("发送失败(IOCTL),错误码 = %lu\n", GetLastError());

return result;
}

BOOL ReceiveMessageFromDriver(HANDLE hDevice, char* buffer, DWORD bufferSize)
{
DWORD returned = 0;
OVERLAPPED ov = { 0 };
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

BOOL result = DeviceIoControl(
hDevice,
IOCTL_GET_MESSAGE,
NULL,
0,
buffer,
bufferSize,
&returned,
&ov);

if (!result && GetLastError() == ERROR_IO_PENDING) {
result = WaitForOverlapped(hDevice, &ov, &returned);
}

CloseHandle(ov.hEvent);

if (!result)
printf("读取失败(IOCTL),错误码 = %lu\n", GetLastError());

return result;
}

BOOL WriteToDriver(HANDLE hDevice, const char* message)
{
DWORD written = 0;
OVERLAPPED ov = { 0 };
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

BOOL result = WriteFile(
hDevice,
message,
(DWORD)strlen(message),
&written,
&ov);

if (!result && GetLastError() == ERROR_IO_PENDING) {
result = WaitForOverlapped(hDevice, &ov, &written);
}

CloseHandle(ov.hEvent);

if (!result)
printf("WriteFile 失败,错误码 = %lu\n", GetLastError());
else
printf("WriteFile 写入了 %lu 字节。\n", written);

return result;
}

BOOL ReadFromDriver(HANDLE hDevice, char* buffer, DWORD bufferSize)
{
DWORD bytesRead = 0;
OVERLAPPED ov = { 0 };
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

BOOL result = ReadFile(
hDevice,
buffer,
bufferSize,
&bytesRead,
&ov);

if (!result && GetLastError() == ERROR_IO_PENDING) {
result = WaitForOverlapped(hDevice, &ov, &bytesRead);
}

CloseHandle(ov.hEvent);

if (!result)
printf("ReadFile 失败,错误码 = %lu\n", GetLastError());
else
printf("ReadFile 读取了 %lu 字节。\n", bytesRead);

return result;
}

int main()
{
HANDLE hDevice = OpenDevice();
if (hDevice == INVALID_HANDLE_VALUE) return 1;

const char* msg = "Hello Kernel via IOCTL!";
if (SendMessageToDriver(hDevice, msg))
printf("已发送 (IOCTL): %s\n", msg);

char buffer[MAX_BUFFER] = { 0 };
if (ReceiveMessageFromDriver(hDevice, buffer, sizeof(buffer)))
printf("接收到 (IOCTL): %s\n", buffer);

const char* msg2 = "Hello Kernel via WriteFile!";
if (WriteToDriver(hDevice, msg2))
printf("已写入 (WriteFile): %s\n", msg2);

ZeroMemory(buffer, sizeof(buffer));
if (ReadFromDriver(hDevice, buffer, sizeof(buffer)))
printf("接收到 (ReadFile): %s\n", buffer);

CloseHandle(hDevice);
return 0;
}

2.驱动程序,使用之前的同步通信版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#include <ntddk.h>

#define DEVICE_NAME L"\\Device\\MyMinimalDevice"
#define SYMBOLIC_NAME L"\\DosDevices\\MyMinimalDevice"

#define IOCTL_SEND_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GET_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define MAX_BUFFER_SIZE 256
#define Log(...) DbgPrint("qi: " __VA_ARGS__)

// 内核全局缓冲
static char g_LastMessage[MAX_BUFFER_SIZE] = "Default Kernel Message";
static PDEVICE_OBJECT g_DeviceObject = NULL;

// 前向声明
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath);
VOID UnloadDriver(PDRIVER_OBJECT DriverObject);
NTSTATUS DispatchCommon(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS CreateDeviceAndSymbolicLink(PDRIVER_OBJECT DriverObject);
VOID DeleteDeviceAndSymbolicLink();

// 驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
Log("Driver loaded\n");

NTSTATUS status = CreateDeviceAndSymbolicLink(DriverObject);
if (!NT_SUCCESS(status)) {
Log("CreateDevice failed: 0x%X\n", status);
return status;
}

// 分发
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCommon;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCommon;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DispatchCommon;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchCommon;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchCommon;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl;
DriverObject->DriverUnload = UnloadDriver;

return STATUS_SUCCESS;
}

// 卸载例程
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DeleteDeviceAndSymbolicLink();
Log("Driver unloaded\n");
}

// 创建设备 + 符号链接
NTSTATUS CreateDeviceAndSymbolicLink(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING devName = RTL_CONSTANT_STRING(DEVICE_NAME);
UNICODE_STRING symName = RTL_CONSTANT_STRING(SYMBOLIC_NAME);

NTSTATUS status = IoCreateDevice(
DriverObject,
0,
&devName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&g_DeviceObject
);
if (!NT_SUCCESS(status)) return status;

// —— 关键:启用 Buffered I/O,否则 SystemBuffer 在 Read/Write/IOCTL 下可能为 NULL
g_DeviceObject->Flags |= DO_BUFFERED_IO;

status = IoCreateSymbolicLink(&symName, &devName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(g_DeviceObject);
g_DeviceObject = NULL;
}
return status;
}

// 删除设备 + 符号链接
VOID DeleteDeviceAndSymbolicLink()
{
UNICODE_STRING symName = RTL_CONSTANT_STRING(SYMBOLIC_NAME);
IoDeleteSymbolicLink(&symName);

if (g_DeviceObject) {
IoDeleteDevice(g_DeviceObject);
g_DeviceObject = NULL;
}
}

// Create/Close/Cleanup/Read/Write 都走这里
NTSTATUS DispatchCommon(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG info = 0;

switch (stack->MajorFunction)
{
case IRP_MJ_CREATE:
Log("Device opened\n");
break;

case IRP_MJ_CLOSE:
Log("Device closed\n");
break;

case IRP_MJ_CLEANUP:
Log("Device cleanup\n");
break;

case IRP_MJ_READ:
{
// 只用 METHOD_BUFFERED,SystemBuffer 一定有效
char* userBuf = (char*)Irp->AssociatedIrp.SystemBuffer;
ULONG outLen = stack->Parameters.Read.Length;

if (userBuf && outLen > 0) {
size_t msgLen = strnlen(g_LastMessage, MAX_BUFFER_SIZE);
size_t copyLen = min((size_t)outLen, msgLen + 1);
RtlCopyMemory(userBuf, g_LastMessage, copyLen);
info = (ULONG)copyLen;
Log("Handled IRP_MJ_READ, %u bytes\n", info);
}
else {
status = STATUS_INVALID_PARAMETER;
}
break;
}

case IRP_MJ_WRITE:
{
char* buffer = (char*)Irp->AssociatedIrp.SystemBuffer;
ULONG writeLen = stack->Parameters.Write.Length;

if (buffer && writeLen > 0)
{
RtlZeroMemory(g_LastMessage, MAX_BUFFER_SIZE);
RtlCopyMemory(g_LastMessage, buffer,
min((size_t)writeLen, MAX_BUFFER_SIZE - 1));
Log("Write from user: %s;\n", g_LastMessage);

info = writeLen; // <-- 把写入长度告诉 I/O 管理器
}
else
{
status = STATUS_INVALID_PARAMETER;
}
Log("Handled IRP_MJ_WRITE, %u bytes\n", info);
break;
}

default:
status = STATUS_INVALID_DEVICE_REQUEST;
Log("Unknown IRP in DispatchCommon: 0x%X\n", stack->MajorFunction);
break;
}

Irp->IoStatus.Status = status;
Irp->IoStatus.Information = info;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}

// 只有 IOCTL 走这里
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;
char* buffer = (char*)Irp->AssociatedIrp.SystemBuffer;

NTSTATUS status = STATUS_SUCCESS;
ULONG info = 0;

switch (code)
{
case IOCTL_SEND_MESSAGE:
if (buffer && inLen > 0) {
RtlZeroMemory(g_LastMessage, MAX_BUFFER_SIZE);
RtlCopyMemory(g_LastMessage, buffer, min((size_t)inLen, MAX_BUFFER_SIZE - 1));
Log("Received from user (IOCTL): %s\n", g_LastMessage);
}
else {
status = STATUS_INVALID_PARAMETER;
Log("Empty IOCTL_SEND_MESSAGE\n");
}
break;

case IOCTL_GET_MESSAGE:
if (buffer && outLen > 0) {
size_t msgLen = strnlen(g_LastMessage, MAX_BUFFER_SIZE);
size_t copyLen = min((size_t)outLen, msgLen + 1);
RtlCopyMemory(buffer, g_LastMessage, copyLen);
info = (ULONG)copyLen;
Log("Returned to user (IOCTL): %s\n", g_LastMessage);
}
else {
status = STATUS_INVALID_PARAMETER;
Log("Invalid IOCTL_GET_MESSAGE buffer\n");
}
break;

default:
status = STATUS_INVALID_DEVICE_REQUEST;
Log("Unknown IOCTL: 0x%X\n", code);
break;
}

Irp->IoStatus.Status = status;
Irp->IoStatus.Information = info;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <windows.h>
#include <stdio.h>

#define IOCTL_SEND_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_GET_MESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DEVICE_NAME L"\\\\.\\MyMinimalDevice"
#define MAX_BUFFER 256

typedef struct _ASYNC_CONTEXT {
OVERLAPPED overlapped;
char buffer[MAX_BUFFER];
} ASYNC_CONTEXT;

HANDLE OpenDevice(HANDLE* pCompletionPort)
{
HANDLE hDevice = CreateFileW(
DEVICE_NAME,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("无法打开设备,错误码 = %lu\n", GetLastError());
return INVALID_HANDLE_VALUE;
}

HANDLE hPort = CreateIoCompletionPort(hDevice, NULL, (ULONG_PTR)hDevice, 0);
if (!hPort) {
printf("无法创建 IOCP,错误码 = %lu\n", GetLastError());
CloseHandle(hDevice);
return INVALID_HANDLE_VALUE;
}

*pCompletionPort = hPort;
return hDevice;
}
BOOL AsyncWrite(HANDLE hDevice, HANDLE hPort, const char* message)
{
ASYNC_CONTEXT* ctx = (ASYNC_CONTEXT*)calloc(1, sizeof(ASYNC_CONTEXT));
strcpy_s(ctx->buffer, sizeof(ctx->buffer), message);

BOOL result = WriteFile(
hDevice,
ctx->buffer,
(DWORD)strlen(ctx->buffer),
NULL, &ctx->overlapped
);

if (!result && GetLastError() != ERROR_IO_PENDING) {
printf("WriteFile 异步失败,错误码 = %lu\n", GetLastError());
free(ctx);
return FALSE;
}

DWORD bytes;
ULONG_PTR key;
LPOVERLAPPED pol = NULL;
if (GetQueuedCompletionStatus(hPort, &bytes, &key, &pol, INFINITE)) {
printf("异步写入完成:%lu 字节\n", bytes);
}
else {
printf("GetQueuedCompletionStatus 失败:%lu\n", GetLastError());
}

free(ctx);
return TRUE;
}

BOOL AsyncRead(HANDLE hDevice, HANDLE hPort)
{
ASYNC_CONTEXT* ctx = (ASYNC_CONTEXT*)calloc(1, sizeof(ASYNC_CONTEXT));

BOOL result = ReadFile(
hDevice,
ctx->buffer,
sizeof(ctx->buffer),
NULL, &ctx->overlapped
);

if (!result && GetLastError() != ERROR_IO_PENDING) {
printf("ReadFile 异步失败,错误码 = %lu\n", GetLastError());
free(ctx);
return FALSE;
}

DWORD bytes;
ULONG_PTR key;
LPOVERLAPPED pol = NULL;
if (GetQueuedCompletionStatus(hPort, &bytes, &key, &pol, INFINITE)) {
printf("异步读取完成:%lu 字节,内容:%s\n", bytes, ctx->buffer);
}
else {
printf("GetQueuedCompletionStatus 失败:%lu\n", GetLastError());
}

free(ctx);
return TRUE;
}

int main()
{
HANDLE hIOCP = NULL;
HANDLE hDevice = OpenDevice(&hIOCP);
if (hDevice == INVALID_HANDLE_VALUE) return 1;

AsyncWrite(hDevice, hIOCP, "Hello from Async WriteFile!");
AsyncRead(hDevice, hIOCP);

CloseHandle(hDevice);
CloseHandle(hIOCP);
return 0;
}

4.用户层使用异步通信有几种方法?

以下是三种异步通信方式的用户层示例代码:

1. 使用事件的异步通信

在这种方式中,用户层在调用 DeviceIoControl 时传递一个事件句柄。驱动完成操作后,会触发这个事件,通知用户层操作完成。

用户层代码(使用事件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <windows.h>
#include <stdio.h>

#define IOCTL_EXCHANGE_MESSAGE 0x800
#define DEVICE_NAME L"\\\\.\\MyMinimalDevice"

typedef struct {
ULONG MsgType;
ULONG PayloadSize;
CHAR Payload[128];
} USER_REQUEST;

int main() {
HANDLE hDevice = CreateFile(
DEVICE_NAME, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
0, NULL);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device, error %lu\n", GetLastError());
return 1;
}

// 创建事件
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hEvent == NULL) {
printf("CreateEvent failed, error %lu\n", GetLastError());
CloseHandle(hDevice);
return 1;
}

USER_REQUEST req = { 0 };
req.MsgType = 1; // 示例消息类型
req.PayloadSize = strlen("Hello from User Layer");
strcpy_s(req.Payload, sizeof(req.Payload), "Hello from User Layer");

DWORD bytesReturned = 0;
BOOL result = DeviceIoControl(
hDevice,
IOCTL_EXCHANGE_MESSAGE,
&req,
sizeof(req),
NULL,
0,
&bytesReturned,
hEvent // 传递事件句柄
);

if (!result) {
printf("DeviceIoControl failed, error %lu\n", GetLastError());
CloseHandle(hEvent);
CloseHandle(hDevice);
return 1;
}

// 等待异步操作完成
DWORD dwWaitResult = WaitForSingleObject(hEvent, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0) {
printf("Asynchronous operation completed.\n");
} else {
printf("Failed to wait for event, error %lu\n", GetLastError());
}

CloseHandle(hEvent);
CloseHandle(hDevice);
return 0;
}

2. 使用回调的异步通信

在这种方式中,用户层提供一个回调函数,驱动完成操作后调用这个回调函数。回调函数负责处理结果并通知用户层。

用户层代码(使用回调)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <windows.h>
#include <stdio.h>

#define IOCTL_EXCHANGE_MESSAGE 0x800
#define DEVICE_NAME L"\\\\.\\MyMinimalDevice"

typedef struct {
ULONG MsgType;
ULONG PayloadSize;
CHAR Payload[128];
} USER_REQUEST;

void MyCallback(LPVOID context) {
printf("Callback called, operation completed.\n");
}

int main() {
HANDLE hDevice = CreateFile(
DEVICE_NAME, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
0, NULL);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device, error %lu\n", GetLastError());
return 1;
}

USER_REQUEST req = { 0 };
req.MsgType = 1; // 示例消息类型
req.PayloadSize = strlen("Hello from User Layer");
strcpy_s(req.Payload, sizeof(req.Payload), "Hello from User Layer");

DWORD bytesReturned = 0;

// 传递回调函数
BOOL result = DeviceIoControl(
hDevice,
IOCTL_EXCHANGE_MESSAGE,
&req,
sizeof(req),
NULL,
0,
&bytesReturned,
(LPOVERLAPPED)&MyCallback // 传递回调
);

if (!result) {
printf("DeviceIoControl failed, error %lu\n", GetLastError());
CloseHandle(hDevice);
return 1;
}

// 由于是异步回调方式,主线程可以继续做其他工作,回调完成后会输出消息
printf("Waiting for callback...\n");
Sleep(1000); // 模拟等待回调执行
CloseHandle(hDevice);
return 0;
}

3. 使用 I/O 完成端口的异步通信

在这种方式中,用户层使用 I/O 完成端口来接收异步操作的完成通知。驱动在操作完成时将请求提交到 I/O 完成端口,用户层可以通过 GetQueuedCompletionStatus 来获取完成状态。

用户层代码(使用 I/O 完成端口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <windows.h>
#include <stdio.h>

#define IOCTL_EXCHANGE_MESSAGE 0x800
#define DEVICE_NAME L"\\\\.\\MyMinimalDevice"

typedef struct {
ULONG MsgType;
ULONG PayloadSize;
CHAR Payload[128];
} USER_REQUEST;

int main() {
HANDLE hDevice = CreateFile(
DEVICE_NAME, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
0, NULL);

if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device, error %lu\n", GetLastError());
return 1;
}

// 创建 I/O 完成端口
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (hCompletionPort == NULL) {
printf("CreateIoCompletionPort failed, error %lu\n", GetLastError());
CloseHandle(hDevice);
return 1;
}

USER_REQUEST req = { 0 };
req.MsgType = 1; // 示例消息类型
req.PayloadSize = strlen("Hello from User Layer");
strcpy_s(req.Payload, sizeof(req.Payload), "Hello from User Layer");

DWORD bytesReturned = 0;

// 向 I/O 完成端口提交请求
BOOL result = DeviceIoControl(
hDevice,
IOCTL_EXCHANGE_MESSAGE,
&req,
sizeof(req),
NULL,
0,
&bytesReturned,
NULL // NULL 作为 I/O 完成端口,实际中会传递完成端口句柄
);

if (!result) {
printf("DeviceIoControl failed, error %lu\n", GetLastError());
CloseHandle(hCompletionPort);
CloseHandle(hDevice);
return 1;
}

// 等待完成事件
DWORD dwWaitResult;
ULONG_PTR completionKey;
LPOVERLAPPED overlapped;
dwWaitResult = GetQueuedCompletionStatus(
hCompletionPort,
&bytesReturned,
&completionKey,
&overlapped,
INFINITE // 等待直到操作完成
);

if (dwWaitResult) {
printf("Asynchronous operation completed\n");
} else {
printf("Failed to wait for completion, error %lu\n", GetLastError());
}

CloseHandle(hCompletionPort);
CloseHandle(hDevice);
return 0;
}

总结

  1. 事件机制:通过事件通知用户层异步操作的完成。
  2. 回调机制:驱动在完成操作后调用用户层提供的回调函数。
  3. I/O 完成端口:通过完成端口机制,用户层可以异步接收操作结果。

每种方式的选择依据实际场景而定,通常 I/O 完成端口用于更复杂的异步模型,特别是在高并发或高性能要求的场景中。事件和回调则适用于较为简单的异步需求。

2.驱动层使用异步通信有几种方法?

是的,为了支持以上三种异步通信方式,驱动代码需要做一些修改。以下是每种方式所需的驱动端修改:

1.使用事件的异步通信

为了支持事件机制,驱动需要通过 IoMarkIrpPending 和 IoSetCompletionRoutine 来异步处理 I/O 操作。这意味着用户层传递的事件句柄将在操作完成时被触发。

驱动端代码修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
ULONG info = 0;
NTSTATUS status = STATUS_SUCCESS;
// 触发事件通知
if (stack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
if (stack->Parameters.DeviceIoControl.IoControlCode == IOCTL_EXCHANGE_MESSAGE) {
// 设置异步操作标志
IoMarkIrpPending(irp);

// 获取事件句柄并在完成时设置
HANDLE hEvent = stack->Parameters.DeviceIoControl.Event;
if (hEvent) {
IoSetCompletionRoutine(irp,
(PIO_COMPLETION_ROUTINE)IoCompleteRequest,
hEvent,
TRUE,
TRUE,
TRUE);
}
}
}

return status;
}

2.使用回调的异步通信

要支持回调机制,驱动代码需要使用 IoSetCompletionRoutine 来指定回调函数。当 I/O 操作完成时,驱动会调用回调函数。

驱动端代码修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
ULONG info = 0;
NTSTATUS status = STATUS_SUCCESS;
// 触发回调
if (stack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
if (stack->Parameters.DeviceIoControl.IoControlCode == IOCTL_EXCHANGE_MESSAGE) {
// 设置回调
PIO_COMPLETION_ROUTINE completionRoutine = (PIO_COMPLETION_ROUTINE)irp->AssociatedIrp.SystemBuffer;

if (completionRoutine) {
IoSetCompletionRoutine(irp,
completionRoutine,
NULL,
TRUE,
TRUE,
TRUE);
}
}
}

return status;
}

3.使用 I/O 完成端口的异步通信

驱动需要使用 IoCreateCompletionPort 和 IoQueueDpc 来支持 I/O 完成端口。完成端口允许用户层通过 GetQueuedCompletionStatus 来异步获取操作结果。

驱动端代码修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NTSTATUS DispatchIoctl(PDEVICE_OBJECT dev, PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
ULONG info = 0;
NTSTATUS status = STATUS_SUCCESS;
// 设置异步处理
if (stack->MajorFunction == IRP_MJ_DEVICE_CONTROL) {
if (stack->Parameters.DeviceIoControl.IoControlCode == IOCTL_EXCHANGE_MESSAGE) {
PIO_COMPLETION_ROUTINE completionRoutine = (PIO_COMPLETION_ROUTINE)irp->AssociatedIrp.SystemBuffer;

// 完成端口处理
if (completionRoutine) {
// 这里将请求提交到完成端口
IoQueueDpc(dev, irp, completionRoutine);
}
}
}

return status;
}

总结

事件机制:驱动代码需使用 IoMarkIrpPending 和 IoSetCompletionRoutine 来支持事件驱动的异步操作。

回调机制:通过 IoSetCompletionRoutine 在驱动完成操作时调用回调函数。

I/O 完成端口:驱动需要使用 IoQueueDpc 或直接将请求提交到完成端口,用户层通过 GetQueuedCompletionStatus 来获取操作完成的通知。

每种方式都需要对驱动的 I/O 处理流程进行相应的调整,以便正确支持异步操作并通知用户层。