Windows驱动开发:利用ReadFile与WriteFile实现应用层通信
在Windows驱动开发中,内核态与应用层处于不同的地址空间,必须借助特定的I/O机制进行数据交换。微软提供了三种主要的缓冲模式,其中缓冲区方式(DO_BUFFERED_IO)是最常用的标准做法。通过实现IRP_MJ_READ与IRP_MJ_WRITE派遣函数,配合应用层的ReadFile和WriteFile API,即可实现稳定、高效的内核数据交互。
通信模式与原理概述
驱动程序与应用程序的交互主要依赖I/O管理器中转数据,常见通信模式包括:
- 缓冲区读写(DO_BUFFERED_IO):系统将用户缓冲区的内容复制到内核分配的
SystemBuffer中,操作完成后由系统负责复制回用户缓冲区。安全且常用,对应应用层的ReadFile/WriteFile。 - 直接读写(DO_DIRECT_IO):通过锁页内存(Mdl)直接访问物理内存,减少一次拷贝,适合大数据量传输。
- 其他方式(NOT_SUPPORTED):通常用于未定义的特殊处理。
本文将详细演示基于缓冲区模式(DO_BUFFERED_IO)的开发流程。
第一步:驱动框架初始化
首先构建标准的驱动入口,完成设备对象的创建、符号链接的建立以及默认派遣函数的配置。务必将标志位设为DO_BUFFERED_IO,这是后续使用ReadFile系列函数的基础。
#include <ntifs.h>
// 卸载驱动执行
VOID UnDriver(PDRIVER_OBJECT pDriver)
{
PDEVICE_OBJECT pDev = pDriver->DeviceObject;
UNICODE_STRING SymLinkName;
IoDeleteDevice(pDev);
RtlInitUnicodeString(&SymLinkName, L"\\??\\LySharkDriver");
IoDeleteSymbolicLink(&SymLinkName);
}
// 创建设备连接
NTSTATUS CreateDriverObject(IN PDRIVER_OBJECT pDriver)
{
NTSTATUS Status;
PDEVICE_OBJECT pDevObj;
UNICODE_STRING DriverName;
UNICODE_STRING SymLinkName;
RtlInitUnicodeString(&DriverName, L"\\Device\\LySharkDriver");
Status = IoCreateDevice(pDriver, 0, &DriverName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
// 指定通信方式为缓冲区
pDevObj->Flags |= DO_BUFFERED_IO;
RtlInitUnicodeString(&SymLinkName, L"\\??\\LySharkDriver");
Status = IoCreateSymbolicLink(&SymLinkName, &DriverName);
return STATUS_SUCCESS;
}
第二步:注册读写派遣函数
在DriverEntry中,除了默认的CREATE和CLOSE处理外,必须显式绑定IRP_MJ_READ和IRP_MJ_WRITE。对于其他未使用的功能号,应指向一个默认处理函数以防止非法访问导致蓝屏。
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath)
{
// ... 省略设备创建部分 ...
// 统一设置默认处理
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriver->MajorFunction[i] = DriverDefaultHandle;
}
pDriver->DriverUnload = UnDriver;
pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
// 增加核心读写派遣处理
pDriver->MajorFunction[IRP_MJ_READ] = DispatchRead;
pDriver->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
return STATUS_SUCCESS;
}
第三步:实现读写逻辑
在缓冲区模式下,pIrp->AssociatedIrp.SystemBuffer是驱动程序读写数据的唯一入口。读取时,需向此缓冲区填充数据;写入时,可从该缓冲区读取用户传入的数据。
- 读取请求:操作系统已准备好
SystemBuffer,开发者负责向其填入数据,并通过IoStatus.Information返回实际字节数。 - 写入请求:操作系统已将应用层数据从用户缓冲区复制到
SystemBuffer,开发者可直接读取并使用。
// 读取回调函数
NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = Stack->Parameters.Read.Length;
char szBuf[128] = "hello lyshark";
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = ulReadLength; // 告知IO管理器传输的字节数
// 安全起见,限制拷贝长度不超过字符串实际长度或缓冲区大小
ULONG copyLen = (ulReadLength > 5) ? 5 : ulReadLength;
memcpy(pIrp->AssociatedIrp.SystemBuffer, szBuf, copyLen);
DbgPrint("读取长度:%d\n", ulReadLength);
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// 接收传入回调函数
NTSTATUS DispatchWrite(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp)
{
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
ULONG ulWriteLength = Stack->Parameters.Write.Length;
PVOID ulWriteData = Irp->AssociatedIrp.SystemBuffer;
DbgPrint("传入长度: %d 传入数据: %s\n", ulWriteLength, (char*)ulWriteData);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = ulWriteLength;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
第四步:应用层调用验证
应用层通过CreateFile打开符号链接获取句柄,随后直接使用标准的Win32 API ReadFile和WriteFile进行交互。当调用这些API时,线程进入内核态触发对应的派遣函数。
#include <iostream>
#include <Windows.h>
int main(int argc, char *argv[])
{
HANDLE hDevice = CreateFileA("\\\\.\\LySharkDriver", GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
CloseHandle(hDevice);
return -1;
}
// 1. 读取数据
char buffer[128] = { 0 };
ULONG length;
ReadFile(hDevice, buffer, 5, &length, 0);
printf("读取字节:");
for (int i = 0; i < (int)length; i++)
{
printf("%c", buffer[i]);
}
printf("\n");
// 2. 写入数据
char write_buffer[] = "hello lyshark";
ULONG write_length;
WriteFile(hDevice, write_buffer, strlen(write_buffer), &write_length, 0);
system("pause");
CloseHandle(hDevice);
return 0;
}
以上就是通过ReadFile与内核层通信的详细内容,更多关于C语言驱动开发及系统级数据交换的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













