驱动开发(13)IRP 的异步完成和 CancelRoutine

来源:互联网 发布:刷机救砖软件哪个好 编辑:程序博客网 时间:2024/05/17 23:05

本博文由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<Windows.h>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

要异步完成IRP,需要在派遣函数中不调用 IoCompleteRequest ,而是调用 IoMarkIrpPending 函数,同时需要派遣函数返回 STATUS_PENDING

通过上面的异步访问设备我们可以发现,应用程序会创建一个 Event 对象,要使应用程序等待完成,需要激活这个事件,这个过程不需要驱动程序自己去做的,完成IRP时如果调用 IoCompleteRequest , IoCompleteRequest 会自动激活此事件。

也就是说,调用 IoMarkIrpPending 后,应用程序创建的事件并没有被激活,即,如果应用程序等待此事件(如同步读写),并不会使其退出等待,他的作用仅仅是使派遣函数返回,以便于驱动程序在其他地方完成 IRP 。

就像这样,之后,此 IRP 被挂起,驱动程序可以保存此 IRP 的指针,在未来某个必要的时刻完成他:

//in a Dispatch FunctionIoMarkIrpPending(pIrp);return STATUS_PENDING;

CancelRoutine 取消 I/O 例程

有些时候,驱动程序需要允许应用程序“取消”某个 I/O 请求。比如,应用程序程序异步读写文件时可以实现一个“终止”按钮。这需要驱动程序的支持。如果我们希望给自己的设备实现这种功能,就需要给 IRP 设置取消例程。应用程序通过 Win32 子系统提供的 CancelIO API来取消一个 I/O 请求,这会调用驱动程序设置的取消例程。

设置取消例程的内核函数是 IoSetCancelRoutine,这个函数的原型如下:

PDRIVER_CANCEL IoSetCancelRoutine(    _In_ PIRP           Irp,    _In_ PDRIVER_CANCEL CancelRoutine);
  1. 参数1是要设置取消例程的 IRP 指针。
  2. 参数2是取消例程指针。如果此参数为 NULL ,则删除取消例程

返回值:当前 IRP 存在取消例程时返回 Irp->CancelRoutine ,否则返回 NULL 。

取消例程的原型如下:

DRIVER_CANCEL Cancel;VOID Cancel(  _Inout_ struct _DEVICE_OBJECT *DeviceObject,  _Inout_ struct _IRP           *Irp){ ... }

IoCancelIrp 会在内部调用 IoAcquireCancelSpinLock 获取cancel自旋锁,因此,在取消例程中,必须调用 IoReleaseCancelSpinLock 函数释放自旋锁,否则会导致蓝屏死机。

IRP 异步完成和 CancelRoutine 例程代码

最后,我们以一个例程来结束本篇博文,此例中,我们在处理”读”的派遣函数中挂起 IRP 来演示 IRP 异步完成,并设置 CancelRoutine ,并在取消例程中将挂起的 IRP 完成。

应用程序源码:

#include "stdafx.h"#include<Windows.h>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 <ntddk.h>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;}

效果图:

0 0
原创粉丝点击