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

https://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 Function
IoMarkIrpPending(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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值