本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan所有,转载请注明出处:http://blog.csdn.net/zuishikonghuan/article/details/51301922
在之前的博文中,我们对于IRP,都是同步完成的,但是 Windows 对异步操作很友好,我们来看看如何异步完成 IRP 。
在开始之前,我认为有必要提一句异步访问设备。在之前的博文中,与驱动通信的代码都是采用同步访问设备的,其实所谓同步访问,是 Win32 子系统封装了“等待”这一过程。 Win32API 会在内部创建事件,并在向设备发送 I/O 请求后直接等待事件被完成,一旦驱动程序完成 I/O ,那么就会激活事件,从而使 Win32API 中的等待状态结束,线程恢复运行。(关于“事件”对象,见上一篇博文“内核中开启多线程和同步对象”)
其实,这个等待操作我们可以自己来做,就像这样:
#include "stdafx.h"
#include
int _tmain(int argc, _TCHAR* argv[])
{
//如果 CreateFile 的第 6 个参数 dwFlagsAndAttributes 被指定为 FILE_FLAG_OVERLAPPED,
HANDLE handle = CreateFile(TEXT("\\\\.\\D:\\1.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE){
MessageBoxA(0, "打开文件/设备失败", "错误", 0);
return 0;
}
unsigned char buffer[50] = { 0 };
OVERLAPPED over = { 0 };
HANDLE Event = CreateEvent(NULL, FALSE, FALSE, NULL);
over.hEvent = Event;
ReadFile(handle, buffer, 49, NULL, &over);
//Do somethings
WaitForSingleObject(Event, INFINITE);
for (int i = 0; i < sizeof(buffer); i++)
printf("0x%X ", buffer[i]);
CloseHandle(handle);
getchar();
return 0;
}
当然,还有一种方法异步访问设备,即使用ReadFileEx/WriteFileEx,这是通过APC来实现异步访问的,这里不展开了。
为什么要说异步访问设备呢,这是为了避免大家和下面的异步完成IRP产生混淆,同时也有必要让大家明白下 Win32 子系统对同步读写设备的实现,同时,了解异步访问设备有助于下面的对派遣函数和IRP相关内容的理解。
要异步访问设备,需要得到驱动程序的支持,当应用程序调用 I/O 函数时,驱动程序的 Dispatch Function 会被调用,当 Dispatch Function 返回时,应用程序的 I/O 函数才能退出。驱动程序调用 IoMarkIrpPending 并返回 STATUS_PENDING 时,意味着 I/O 请求已经挂起,如果此时应用程序选择了异步访问设备,那么 I/O 函数会退出,当 I/O 真正处理完成时(即调用 IoCompleteRequest ),应用程序创建的 Event 就会被激发!WaitForSingleObject 会返回。而同步访问,其实是在 I/O 函数内部创建并等待了这个事件。
要异步完成IRP,需要在派遣函数中不调用 IoCompleteRequest ,而是调用 IoMarkIrpPending 函数,同时需要派遣函数返回 STATUS_PENDING
通过上面的异步访问设备我们可以发现,应用程序会创建一个 Event 对象,要使应用程序等待完成,需要激活这个事件,这个过程不需要驱动程序自己去做的,完成IRP时如果调用 IoCompleteRequest , IoCompleteRequest 会自动激活此事件。
也就是说,调用 IoMarkIrpPending 后,应用程序创建的事件并没有被激活,即,如果应用程序等待此事件(如同步读写),并不会使其退出等待,他的作用仅仅是使派遣函数返回,以便于驱动程序在其他地方完成 IRP 。
就像这样,之后,此 IRP 被挂起,驱动程序可以保存此 IRP 的指针,在未来某个必要的时刻完成他:
//in a Dispatch Function
IoMarkIrpPending(pIrp);
return STATUS_PENDING;
有些时候,驱动程序需要允许应用程序“取消”某个 I/O 请求。比如,应用程序程序异步读写文件时可以实现一个“终止”按钮。这需要驱动程序的支持。如果我们希望给自己的设备实现这种功能,就需要给 IRP 设置取消例程。应用程序通过 Win32 子系统提供的 CancelIO API来取消一个 I/O 请求,这会调用驱动程序设置的取消例程。
设置取消例程的内核函数是 IoSetCancelRoutine,这个函数的原型如下:
PDRIVER_CANCEL IoSetCancelRoutine(
_In_ PIRP Irp,
_In_ PDRIVER_CANCEL CancelRoutine
);
返回值:当前 IRP 存在取消例程时返回 Irp->CancelRoutine ,否则返回 NULL 。
取消例程的原型如下:
DRIVER_CANCEL Cancel;
VOID Cancel(
_Inout_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
)
{ ... }
IoCancelIrp 会在内部调用 IoAcquireCancelSpinLock 获取cancel自旋锁,因此,在取消例程中,必须调用 IoReleaseCancelSpinLock 函数释放自旋锁,否则会导致蓝屏死机。
最后,我们以一个例程来结束本篇博文,此例中,我们在处理”读”的派遣函数中挂起 IRP 来演示 IRP 异步完成,并设置 CancelRoutine ,并在取消例程中将挂起的 IRP 完成。
应用程序源码:
#include "stdafx.h"
#include
int _tmain(int argc, _TCHAR* argv[])
{
//打开设备
HANDLE handle = CreateFileA("\\\\.\\MyDevice1_link", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE){
MessageBoxA(0, "打开设备失败", "错误", 0);
return 0;
}
unsigned char buffer[50] = { 0 };
DWORD len;
OVERLAPPED over = { 0 };
HANDLE Event = CreateEvent(NULL, FALSE, FALSE, NULL);
over.hEvent = Event;
if (!ReadFile(handle, buffer, 49, &len, &over)){
if (GetLastError() == ERROR_IO_PENDING){
puts("I/O is Pending");
}
}
Sleep(3000);
CancelIo(handle);
CloseHandle(handle);
return 0;
}
驱动程序源码:
#include
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
extern "C" NTSTATUS ReadDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
//我们定义的设备扩展
typedef struct _DEVICE_EXTENSION {
UNICODE_STRING SymLinkName;//符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
#pragma code_seg("INIT")
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
DbgPrint("DriverEntry\r\n");
pDriverObject->DriverUnload = DriverUnload;//注册驱动卸载函数
//注册派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DefDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = ReadDispatchRoutine;
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称的字符串
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDevice1");
//创建设备
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲设备
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\MyDevice1_link");
pDevExt->SymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
return status;
}
return STATUS_SUCCESS;
}
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("DriverUnload\r\n");
PDEVICE_OBJECT pDevObj;
pDevObj = pDriverObject->DeviceObject;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->SymLinkName;
IoDeleteSymbolicLink(&pLinkName);
//删除设备
IoDeleteDevice(pDevObj);
}
extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("DefDispatchRoutine\r\n");
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
VOID Read_CancelIRP(PDEVICE_OBJECT DeviceObject, PIRP pIrp)
{
DbgPrint("Read_CancelIRP pIrp: 0x%X\r\n", pIrp);
//完成状态设置为 STATUS_CANCELLED
pIrp->IoStatus.Status = STATUS_CANCELLED;
//操作字节数
pIrp->IoStatus.Information = 0;
//完成 IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
//释放 Cancel 自旋锁
IoReleaseCancelSpinLock(pIrp->CancelIrql);
}
extern "C" NTSTATUS ReadDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("ReadDispatchRoutine\r\n");
NTSTATUS status = STATUS_SUCCESS;
//得到设备扩展
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//得到I/O堆栈的当前这一层,也就是IO_STACK_LOCATION结构的指针
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//ULONG ReadLength = stack->Parameters.Read.Length;//得到读的长度
//ULONG ReadOffset = (ULONG)stack->Parameters.Read.ByteOffset.QuadPart;//得到读偏移量
//DbgPrint("ReadLength: %d\r\nReadOffset: %d\r\n", ReadLength, ReadOffset);//输出相关信息
//PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到缓冲区指针
//if (ReadOffset + ReadLength > BUFFER_LENGTH){
// //如果要操作的超出了缓冲区,则失败完成IRP,返回无效
// DbgPrint("E: The size of the data is too long.\r\n");
// status = STATUS_FILE_INVALID;//会设置用户模式下的GetLastError
// ReadLength = 0;//设置操作了0字节
//}
//else{
// //没有超出,则进行缓冲区复制
// DbgPrint("OK, I will copy the buffer.\r\n");
// RtlMoveMemory(buffer, pDevExt->buffer + ReadOffset, ReadLength);
// status = STATUS_SUCCESS;
//}
IoSetCancelRoutine(pIrp, Read_CancelIRP);
IoMarkIrpPending(pIrp);
DbgPrint("IoMarkIrpPending pIrp: 0x%X\r\n", pIrp);
return STATUS_PENDING;
//pIrp->IoStatus.Status = status;//设置IRP完成状态,会设置用户模式下的GetLastError
//pIrp->IoStatus.Information = ReadLength;//设置操作字节数
//IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP
//return status;
}