[Win32驱动11] 驱动中的IRP同步异步以及串行化处理方式(Part 1)

1. 异步IO读写的处理方式

这段代码是驱动异步处理应用层发来的IRP的一种方式,其基本作用原理是这样的:

应用层进程以异步的方式多次发来Read IRP请求。由于设备一次性无法立即处理完所有IRP,所以设备在收到IRP后首先将其挂载到一个链表中并挂起该IRP,返回STATUS_PENDING。这些挂在链表上的IRP会在设备被关闭时被处理掉。即当CloseHandle被调用后,设备收到CLEANUP类型的IRP时处理。其处理方式也非常简单,这里仅仅是完成IRP的处理什么也不做。

接下去看一下代码:

#include <ntddk.h>

typedef struct _MY_IRP_ENTRY {
	PIRP pIrp; 
	LIST_ENTRY listEntry;
} MY_IRP_ENTRY, *PMY_IRP_ENTRY;

typedef struct _DEVICE_EXTENSION {
	PDEVICE_OBJECT pDevice;
	UNICODE_STRING ustrDevName;	//设备名称
	UNICODE_STRING ustrSymName;	//符号链接名
	PLIST_ENTRY pIRPLinkListHead;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
	KdPrint(("卸载驱动!\n"));
	PDEVICE_OBJECT pDev = pDriverObject->DeviceObject;
	while (pDev) {
		PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension;
		ExFreePool(pDevExt->pIRPLinkListHead);
		IoDeleteSymbolicLink(&pDevExt->ustrSymName);
		IoDeleteDevice(pDevExt->pDevice);
		pDev = pDev->NextDevice;
	}
}

NTSTATUS ReadRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	KdPrint(("进入ReadRoutine\n"));
	NTSTATUS status;
	PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension;
	
	PMY_IRP_ENTRY pMyIrp = (PMY_IRP_ENTRY)ExAllocatePool(PagedPool, sizeof(MY_IRP_ENTRY));
	if (NULL != pMyIrp) { // 如果分配成功就插入链表并挂起
		KdPrint(("即将挂起将IRP插入队列后挂起IRP!\n"));
		status = STATUS_PENDING;
		pMyIrp->pIrp = pIrp;
		InsertHeadList(pDevExt->pIRPLinkListHead, &pMyIrp->listEntry);
		IoMarkIrpPending(pIrp);
	}
	else { // 分配失败直接完成
		KdPrint(("即将完成IRP\n"));
		status = STATUS_SUCCESS;
		pIrp->IoStatus.Status = status;
		pIrp->IoStatus.Information = 0;
		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	}
	
	KdPrint(("离开ReadRoutine\n"));

	return(status);
}

NTSTATUS CleanupRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	KdPrint(("进入CleanupRoutine\n"));
	PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension;
	PMY_IRP_ENTRY pMyIrp = pDevExt->pIRPLinkListHead;

	while (!IsListEmpty(pMyIrp)) {
		PLIST_ENTRY pList = RemoveHeadList(pMyIrp);
		PMY_IRP_ENTRY pPtr = CONTAINING_RECORD(pList, MY_IRP_ENTRY, listEntry);
		pPtr->pIrp->IoStatus.Status = STATUS_SUCCESS;
		pPtr->pIrp->IoStatus.Information = 0;
		IoCompleteRequest(pPtr->pIrp, IO_NO_INCREMENT);
		ExFreePool(pPtr);
	}
	KdPrint(("离开CleanupRoutine\n"));

	return(STATUS_SUCCESS);
}

NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	return(STATUS_SUCCESS);
}

NTSTATUS CreateDevice(PDRIVER_OBJECT pDrvObj) {
	UNICODE_STRING DevName;
	UNICODE_STRING SymName;
	PDEVICE_OBJECT pDevObj = NULL;
	PDEVICE_EXTENSION pDevExt = NULL;
	NTSTATUS status;

	RtlInitUnicodeString(&DevName, L"\\Device\\Dev");
	status = IoCreateDevice(pDrvObj, sizeof(DEVICE_EXTENSION), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
	if (!NT_SUCCESS(status)) {
		KdPrint(("IoCreateDevice失败%08X\n", status));
		return(status);
	}
	pDevObj->Flags |= DO_BUFFERED_IO;
	pDevObj->Flags &= ~DO_DEVICE_INITIALIZING;

	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
	pDevExt->pDevice = pDevObj;
	pDevExt->ustrDevName = DevName;
	pDevExt->pIRPLinkListHead = (PLIST_ENTRY)ExAllocatePool(PagedPool, sizeof(LIST_ENTRY));
	InitializeListHead(pDevExt->pIRPLinkListHead);
	RtlInitUnicodeString(&SymName, L"\\??\\Dev");
	status = IoCreateSymbolicLink(&SymName, &DevName);
	if (!NT_SUCCESS(status)) {
		KdPrint(("IoCreateSymbolicLink失败%08X\n", status));
		return(status);
	}
	pDevExt->ustrSymName = SymName;

	return(status);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
	pDriverObject->DriverUnload = Unload;
	for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
		pDriverObject->MajorFunction[i] = DispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;
	pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = CleanupRoutine;
	NTSTATUS status = CreateDevice(pDriverObject);
	if (!NT_SUCCESS(status)) 
		return(status);

	KdPrint(("加载驱动!\n"));

	return(STATUS_SUCCESS);
}

这里是应用层代码:

#include <windows.h>
#include <stdio.h>

int main() {
	OVERLAPPED overlap1 = { 0 };
	OVERLAPPED overlap2 = { 0 };
	UCHAR buffer[10];
	ULONG ulRead;
	HANDLE hEvent1, hEvent2;

	HANDLE hDevice =
		CreateFile("\\\\.\\Dev",
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
		NULL);

	if (hDevice == INVALID_HANDLE_VALUE) {
		printf("Open Device failed!");
		return(-1);
	}
	hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);
	hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);
	overlap1.hEvent = hEvent1;
	overlap2.hEvent = hEvent2;
	BOOL bRead = ReadFile(hDevice, buffer, 10, &ulRead, &overlap1);
	if (!bRead && GetLastError() == ERROR_IO_PENDING) 
		printf("The operation is pending\n");
	bRead = ReadFile(hDevice, buffer, 10, &ulRead, &overlap2);
	if (!bRead && GetLastError() == ERROR_IO_PENDING)
		printf("The operation is pending\n");

	WaitForSingleObject(hEvent1, INFINITE);
	WaitForSingleObject(hEvent2, INFINITE);

	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);

	return 0;
}

实际上,这种方式对应的是ReadFile API携带FILE_FLAG_OVERLAPPED标志时的异步处理方式。当IoCompleteRequest被调用后,overlap结构中的同步事件会被触发,就会顺利通过WaitForSingleObject的阻塞。但是这里有个问题,CLEANUP类型的IRP只有CloseHandle被调用后才会被触发,而挂起链表的处理,即IoCompleteRequest是在CLEANUP中才会有,如果在应用层程序中先调用WaitForSingleObject, 在调用CloseHandle就会导致死锁,程序会被永远阻塞在WaitForSingleObject处。

正确的处理方式是如下:

#include <windows.h>
#include <stdio.h>

int main() {
	OVERLAPPED overlap1 = { 0 };
	OVERLAPPED overlap2 = { 0 };
	UCHAR buffer[10];
	ULONG ulRead;
	HANDLE hEvent1, hEvent2;

	HANDLE hDevice =
		CreateFile("\\\\.\\Dev",
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
		NULL);

	//.....内容全部与之前一样

	if (!bRead && GetLastError() == ERROR_IO_PENDING)
		printf("The operation is pending\n");

        //创建IRP_MJ_CLEANUP IRP, 让阻塞链表中的IRP被处理完,同步事件被触发
	CloseHandle(hDevice);

	WaitForSingleObject(hEvent1, INFINITE);
	WaitForSingleObject(hEvent2, INFINITE);

	return 0;
}

上面这段代码与之前那段一样,只是最后CloseHandle与WaitForSingleObject顺序发生了变化,运行结果如下:

2. 取消例程

来看一下设置取消例程,取消例程顾名思义就是取消IRP的处理,该例程的触发方式有直接调用IoCancelIrp,这将直接触发取消例程(如果有的话)或者在应用层直接调用CloseHandle关闭设备会导致CancelIo被调用,CancelIo会使得所有内部未完成的IRP会次调用IoCancelIrp,又或者应用层直接调用CancelIo。

上面那些都是触发取消例程的方法,那如何能够设置IRP与对应取消例程的联系? 这是通过IoSetCancelRoutine来实现。但是要注意的一点无论通过什么方法触发的取消例程,其内部都是由IoCancelIrp来实现的,而IoCancelIrp内部会获取自旋锁(通过IoAcquireCancelSpinLock),有获取就有释放。释放的过程就必须在取消例程内部进行了。

来看一下代码:

#include <windows.h>
#include <stdio.h>

int main() {
	OVERLAPPED overlap1 = { 0 };
	OVERLAPPED overlap2 = { 0 };
	UCHAR buffer[10];
	ULONG ulRead;
	HANDLE hEvent1, hEvent2;

	HANDLE hDevice =
		CreateFile("\\\\.\\Dev",
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
		NULL);

	if (hDevice == INVALID_HANDLE_VALUE) {
		printf("Open Device failed!");
		return(-1);
	}
	hEvent1 = CreateEvent(NULL, FALSE, FALSE, NULL);
	hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);
	overlap1.hEvent = hEvent1;
	overlap2.hEvent = hEvent2;
	BOOL bRead = ReadFile(hDevice, buffer, 10, &ulRead, &overlap1);
	if (!bRead && GetLastError() == ERROR_IO_PENDING)
		printf("The operation is pending\n");
	bRead = ReadFile(hDevice, buffer, 10, &ulRead, &overlap2);
	if (!bRead && GetLastError() == ERROR_IO_PENDING)
		printf("The operation is pending\n");
	Sleep(2000);
	CancelIo(hDevice); // 会检查所有挂起的IRP,如果带有取消例程则被触发

	CloseHandle(hDevice);

	return 0;
}

下面是驱动代码:

#include <ntddk.h>

VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
	KdPrint(("卸载驱动!\n"));
}

NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	return(STATUS_SUCCESS);
}

VOID CancelReadIRP(
	PDEVICE_OBJECT DeviceObject, 
	PIRP pIrp) {
	KdPrint(("进入CancelReadIRP例程\n"));
	pIrp->IoStatus.Status = STATUS_CANCELLED;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	IoReleaseCancelSpinLock(pIrp->CancelIrql); // 在取消例程中释放自旋锁
	KdPrint(("离开CancelReadIRP例程\n"));
	return;
}

NTSTATUS ReadRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	KdPrint(("进入ReadRoutine\n"));

	IoSetCancelRoutine(pIrp, CancelReadIRP); // 内部会调用IoAcquireCancelSpinLock获取自选锁
	IoMarkIrpPending(pIrp);

	KdPrint(("离开ReadRoutine\n"));

	return(STATUS_PENDING);
}

NTSTATUS CreateDevice(PDRIVER_OBJECT pDrvObj) {
	UNICODE_STRING DevName;
	UNICODE_STRING SymName;
	PDEVICE_OBJECT pDevObj = NULL;
	NTSTATUS status;

	RtlInitUnicodeString(&DevName, L"\\Device\\Dev");
	status = IoCreateDevice(pDrvObj, 0, &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
	if (!NT_SUCCESS(status)) {
		KdPrint(("IoCreateDevice失败%08X\n", status));
		return(status);
	}
	pDevObj->Flags |= DO_BUFFERED_IO;
	pDevObj->Flags &= ~DO_DEVICE_INITIALIZING;

	RtlInitUnicodeString(&SymName, L"\\??\\Dev");
	status = IoCreateSymbolicLink(&SymName, &DevName);
	if (!NT_SUCCESS(status)) {
		KdPrint(("IoCreateSymbolicLink失败%08X\n", status));
		return(status);
	}

	return(status);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
	pDriverObject->DriverUnload = Unload;
	for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
		pDriverObject->MajorFunction[i] = DispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;
	NTSTATUS status = CreateDevice(pDriverObject);
	if (!NT_SUCCESS(status)) 
		return(STATUS_UNSUCCESSFUL);

	KdPrint(("加载驱动!\n"));

	return(STATUS_SUCCESS);
}

最后, 由于cancel自旋锁是全局的所以获取时间不宜过长

3. StartIO例程

来看一下Windows源码中,IoStackPacket是如何实现的, 这里我会用中文加上自己的注释

VOID
IoStartPacket(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PULONG Key OPTIONAL,
    IN PDRIVER_CANCEL CancelFunction OPTIONAL
    )

/*++

Routine Description:

    This routine attempts to start the specified packet request (IRP) on the
    specified device.  If the device is already busy, then the packet is
    simply queued to the device queue. If a non-NULL CancelFunction is
    supplied, it will be put in the IRP.  If the IRP has been canceled, the
    CancelFunction will be called after the IRP has been inserted into the
    queue or made the current packet.

Arguments:

    DeviceObject - Pointer to device object itself.

    Irp - I/O Request Packet which should be started on the device.

    Key - Key to be used in inserting packet into device queue;  optional
        (if not specified, then packet is inserted at the tail).

    CancelFunction - Pointer to an optional cancel routine.

Return Value:

    None.

--*/

{
    KIRQL oldIrql;
    KIRQL cancelIrql = PASSIVE_LEVEL;
    BOOLEAN i;

    //
    // Raise the IRQL of the processor to dispatch level for synchronization.
    //
    /* 这里可以看到,DDK文档中说StartIO例程必须运行在DISPATCH_LEVEL上
       这里调用了KeRaiseIrql提升了IRQL级别
    */

    KeRaiseIrql( DISPATCH_LEVEL, &oldIrql );

    //
    // If the driver has indicated that packets are cancelable, then acquire
    // the cancel spinlock and set the address of the cancel function to
    // indicate that the packet is not only cancelable, but indicates what
    // routine to invoke should it be cancelled.
    //
    /*
        如果传入了取消例程,则这边要获取cancel自旋锁,并且设置CancelRoutine为传入的指针
    */
    if (CancelFunction) {
        IoAcquireCancelSpinLock( &cancelIrql );
        Irp->CancelRoutine = CancelFunction;
    }

    //
    // If a key parameter was specified, then insert the request into the
    // work queue according to the key;  otherwise, simply insert it at the
    // tail.
    //
    /*
        文档中只提及了关于IRP被插入到队列中,实际上Key也会被插入到特定队列(前提是有传入的话)
        这里的KeInsertDeviceQueue进行的操作是获取自旋锁并且查询设备状态,如果设备状态是空闲
        则返回FALSE,那IRP就必须立马被处理。如果设备忙,那么会被插入到队列中,并返回TRUE
        最终会释放自旋锁
    */
    // 这里Key和IRP似乎只能有一个被插入队列中
    if (Key) {
        i = KeInsertByKeyDeviceQueue( &DeviceObject->DeviceQueue,
                                      &Irp->Tail.Overlay.DeviceQueueEntry,
                                      *Key );
    } else {
        i = KeInsertDeviceQueue( &DeviceObject->DeviceQueue,
                                 &Irp->Tail.Overlay.DeviceQueueEntry );
    }

    //
    // If the packet was not inserted into the queue, then this request is
    // now the current packet for this device.  Indicate so by storing its
    // address in the current IRP field, and begin processing the request.
    //
    /*
        如果是FALSE的话代表设备空闲,IRP必须马上被处理即为当前IRP
    */
    if (!i) {

        DeviceObject->CurrentIrp = Irp;
        // 首先取消例程存在的情况下会检查设备拓展中的StartIoFlags是否需要取消例程的触发
        // 如果不,则直接把CancelRoutine置空
        if (CancelFunction) {

            //
            // If the driver does not want the IRP in the cancelable state
            // then set the routine to NULL
            //

            if (DeviceObject->DeviceObjectExtension->StartIoFlags & DOE_STARTIO_NO_CANCEL) {
                Irp->CancelRoutine = NULL;
            }
            // cancel lock只是在设置与取消例程相关的事件上
            IoReleaseCancelSpinLock( cancelIrql );
        }

        //
        // Invoke the driver's start I/O routine to get the request going on the device.
        // The StartIo routine should handle the cancellation.
        //
        // 这里调用了StartIo例程
        DeviceObject->DriverObject->DriverStartIo( DeviceObject, Irp );

    } else {

        //
        // The packet was successfully inserted into the device's work queue.
        // Make one last check to determine whether or not the packet has
        // already been marked cancelled.  If it has, then invoke the
        // driver's cancel routine now.  Note that because the cancel
        // spinlock is currently being held, an attempt to cancel the request
        // from another processor at this point will simply wait until this
        // routine is finished, and then get it cancelled.
        //
        /* 如果把IRP插入到队列成功了的话,就简单的检查cancel是否为TRUE(前提是有取消例程)
           如果是就调用一次取消例程并置空CancelRoutine。否则不用管取消例程直接释放cancel自旋锁
           所以这里可以看出, Cancel这个boolean变量的作用是判断是否需要调用取消例程
        */
        if (CancelFunction) {
            if (Irp->Cancel) {
                Irp->CancelIrql = cancelIrql;
                Irp->CancelRoutine = (PDRIVER_CANCEL) NULL;
                CancelFunction( DeviceObject, Irp );
            } else {
                IoReleaseCancelSpinLock( cancelIrql );
            }
        }
    }

    //
    // Restore the IRQL back to its value upon entry to this function before
    // returning to the caller.
    //
    /*
        这里降低了IRQL级别
    */
    KeLowerIrql( oldIrql );
}

接下去看一下KeInsertDeviceQueue的实现, 同样我加了注释在上面:

BOOLEAN
KeInsertDeviceQueue (
    IN PKDEVICE_QUEUE DeviceQueue,
    IN PKDEVICE_QUEUE_ENTRY DeviceQueueEntry
    )

/*++

Routine Description:

    This function inserts a device queue entry at the tail of the specified
    device queue. If the device is not busy, then it is set busy and the entry
    is not placed in the device queue. Otherwise the specified entry is placed
    at the end of the device queue.

Arguments:

    DeviceQueue - Supplies a pointer to a control object of type device queue.

    DeviceQueueEntry - Supplies a pointer to a device queue entry.

Return Value:

    If the device is not busy, then a value of FALSE is returned. Otherwise a
    value of TRUE is returned.

--*/

{

    BOOLEAN Busy;
    BOOLEAN Inserted;
    KLOCK_QUEUE_HANDLE LockHandle;

    ASSERT_DEVICE_QUEUE(DeviceQueue);

    //
    // Set inserted to FALSE and lock specified device queue.
    //
    /*
        首先初始化Inserted变量为FALSE代表未插入队列状态
        并且锁住设备队列, 保持独占
    */
    Inserted = FALSE;
    KiAcquireInStackQueuedSpinLockForDpc(&DeviceQueue->Lock, &LockHandle);

    //
    // Insert the specified device queue entry at the end of the device queue
    // if the device queue is busy. Otherwise set the device queue busy and
    // don't insert the device queue entry.
    //
    // 保存之前的busy状态,并设置成busy
    Busy = DeviceQueue->Busy;
    DeviceQueue->Busy = TRUE;
    // 判断之前是不是已经是busy了, 如果是的话那就直接把IRP插入队列后,置inserted为TRUE
    // 代表已经插入了队列
    if (Busy == TRUE) {
        InsertTailList(&DeviceQueue->DeviceListHead,
                       &DeviceQueueEntry->DeviceListEntry);

        Inserted = TRUE;
    }
    // 把插入状态保存
    DeviceQueueEntry->Inserted = Inserted;

    //
    // Unlock specified device queue.
    //
    // 释放设备队列
    KiReleaseInStackQueuedSpinLockForDpc(&LockHandle);
    // 返回插入状态
    return Inserted;
}

看到这里,来确认一些关于IoStartPacket中的事实:

  1. pIrp->Cancel这个bool值确认的是是否需要调用CancelRoutine, 如果这个为TRUE则会先判断CancelRoutine是否存在,存在的情况下则会调用CancelRoutine并清空该指针,如果为FALSE或者CancelRoutine不存在则什么也不做

  2. pIrp->CurrentIrp是当前要处理的IRP,该参数如果不调用IoStartPacket或者IoStartNextPacket是不会有的,一般默认是0,如果调用后就会被置为通过IoStartPacket传进来的IRP。

  3. 关于CancelRoutine,由于调用IoStartPacket或者IoStartNextPacket会获取cancel锁(其原因也是在于要设置CancelRoutine),或者进入StartIO会需要获取cancel锁,所以如果有StartIO的情况下需要在进入Cancel例程后先判断当前IRP以及pIrp->Cancel这个变量,因为如果当前IRP存在则代表StartIO正要或者正在处理该IRP。此时Cancel例程不能抢夺cancel锁,需要释放并且安静退出即可(由于IoCancelIrp中会获取cancel锁,所以需要在取消例程中释放)。

  4. 已经看到IoStartPacket内部会对pIrp->Cancel进行判断,如果为TRUE,则会直接调用取消例程,所以该IRP已经被取消,在这种情况下,进入StartIO也没有意义。已经已经被取消的IRP没必要在处理。在这种情况下只需要简单返回即可。插入队列的工作IoStartPacket已经就帮我们完成了。

了解这些后来看下面的代码:

#include <ntddk.h>

typedef struct _DEVICE_EXTENSION {
	PDEVICE_OBJECT pDevice;
	UNICODE_STRING ustrDevName;	//设备名称
	UNICODE_STRING ustrSymName;	//符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

#pragma LOCKEDCODE
VOID StartIORoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	KIRQL oldirql;
	KdPrint(("进入StartIO例程\n"));

	// 获取cancel自旋锁的目的仅仅是为了禁止取消例程
	IoAcquireCancelSpinLock(&oldirql);
	if (pIrp != pDevObj->CurrentIrp || pIrp->Cancel) {
		// 如果pIrp不是当前StartIO要处理的IRP或者cancel自旋锁已经被获取
		IoReleaseCancelSpinLock(oldirql);
		KdPrint(("离开StartIO例程\n"));
		return;
	}
	else {
		// 不允许使用取消例程,因为StartIO正在处理,调用IoCancelIrp就可以触发取消例程,这里禁止
		IoSetCancelRoutine(pIrp, NULL);
		IoReleaseCancelSpinLock(oldirql);
	}

	KEVENT event;
	KeInitializeEvent(&event, NotificationEvent, FALSE);
	LARGE_INTEGER timeout;
	timeout.QuadPart = -3 * 1000 * 1000 * 10;

	// 该操作是模拟处理操作
	KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout); // 等待3秒

	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	IoStartNextPacket(pDevObj, TRUE);

	KdPrint(("离开StartIO例程\n"));
}

VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
	KdPrint(("卸载驱动!\n"));
	PDEVICE_OBJECT	pNextObj;
	KdPrint(("Enter DriverUnload\n"));
	pNextObj = pDriverObject->DeviceObject;
	while (pNextObj != NULL) {
		PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pNextObj->DeviceExtension;
		UNICODE_STRING pLinkName = pDevExt->ustrSymName;
		IoDeleteSymbolicLink(&pLinkName);
		pNextObj = pNextObj->NextDevice;
		IoDeleteDevice(pDevExt->pDevice);
	}
}

VOID OnCancelIRP(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	KdPrint(("进入OnCancelIRP\n"));

	if (pIrp == pDevObj->CurrentIrp) {
		// 该IRP是当前IRP会或者将会由StartIO处理
		KIRQL oldirql = pIrp->CancelIrql;
		// 取消例程被调用是因为IoCancelIrp调用了,由于IoCancelIrp内部会获取cancel自旋锁而IoStartPacket也会所以这里释放掉
		IoReleaseCancelSpinLock(pIrp->CancelIrql);
		// 如果调用IoStartPacket时传递了取消例程则cancelable参数需为TRUE,代表可以调用取消例程
		// 其内部会通过该参数来判断是否获取cancel自旋锁,因为cancel自旋锁可能会被IoCancelIrp获取
		IoStartNextPacket(pDevObj, TRUE);
		KeLowerIrql(oldirql);
	}
	else {
		// 从设备队列中提取IRP
		KeRemoveEntryDeviceQueue(&pDevObj->DeviceQueue, &pIrp->Tail.Overlay.DeviceQueueEntry);
		// 释放cancel自选锁
		IoReleaseCancelSpinLock(pIrp->CancelIrql);
	}
	KdPrint(("1. pIrp: %08X\n", pIrp));
	KdPrint(("Cancel: %d, CancelRoutine: %08X, CurrentIrp: %08X\n", pIrp->Cancel, pIrp->CancelRoutine, pDevObj->CurrentIrp));

	pIrp->IoStatus.Status = STATUS_CANCELLED;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	KdPrint(("离开OnCancelIRP\n"));

}

NTSTATUS ReadRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	KdPrint(("进入ReadRoutine\n"));
	NTSTATUS status;
	PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension;

	IoMarkIrpPending(pIrp); // 挂起IRP

	/* 其内部主要做了以下几件事:
	 * 1. 获取cancel自旋锁
	 * 2. 把pIrp设置成当前Irp
	 * 3. 设置OnCancelIRP为当前IRP的取消例程
	 * 4. 释放cancel自旋锁
	 */
	KdPrint(("1. pIrp: %08X\n", pIrp));
	KdPrint(("Cancel: %d, CancelRoutine: %08X, CurrentIrp: %08X\n", pIrp->Cancel, pIrp->CancelRoutine, pDevObj->CurrentIrp));

	IoStartPacket(pDevObj, pIrp, 0, OnCancelIRP); // 把IRP插入系统队列

	KdPrint(("1. pIrp: %08X\n", pIrp));
	KdPrint(("Cancel: %d, CancelRoutine: %08X, CurrentIrp: %08X\n", pIrp->Cancel, pIrp->CancelRoutine, pDevObj->CurrentIrp));


	KdPrint(("离开ReadRoutine\n"));

	return(STATUS_PENDING);
}

NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) {
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);

	return(STATUS_SUCCESS);
}

NTSTATUS CreateDevice(PDRIVER_OBJECT pDrvObj) {
	UNICODE_STRING DevName;
	UNICODE_STRING SymName;
	PDEVICE_OBJECT pDevObj = NULL;
	PDEVICE_EXTENSION pDevExt = NULL;
	NTSTATUS status;

	RtlInitUnicodeString(&DevName, L"\\Device\\Dev");
	status = IoCreateDevice(pDrvObj, sizeof(DEVICE_EXTENSION), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
	if (!NT_SUCCESS(status)) {
		KdPrint(("IoCreateDevice失败%08X\n", status));
		return(status);
	}
	pDevObj->Flags |= DO_BUFFERED_IO;
	pDevObj->Flags &= ~DO_DEVICE_INITIALIZING;

	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
	pDevExt->pDevice = pDevObj;
	pDevExt->ustrDevName = DevName;
	RtlInitUnicodeString(&SymName, L"\\??\\Dev");
	status = IoCreateSymbolicLink(&SymName, &DevName);
	if (!NT_SUCCESS(status)) {
		KdPrint(("IoCreateSymbolicLink失败%08X\n", status));
		return(status);
	}
	pDevExt->ustrSymName = SymName;

	return(status);
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
	pDriverObject->DriverUnload = Unload;
	for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
		pDriverObject->MajorFunction[i] = DispatchRoutine;
	pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;
	pDriverObject->DriverStartIo = StartIORoutine;

	NTSTATUS status = CreateDevice(pDriverObject);
	if (!NT_SUCCESS(status))
		return(status);

	KdPrint(("加载驱动!\n"));

	return(STATUS_SUCCESS);
}

看一下应用层的实现:

#include <windows.h>
#include <stdio.h>
#include <process.h>

UINT WINAPI Thread(LPVOID context)
{
	printf("Enter Thread\n");
	//等待5秒
	OVERLAPPED overlap = { 0 };
	overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	UCHAR buffer[10];
	ULONG ulRead;

	BOOL bRead = ReadFile(*(PHANDLE)context, buffer, 10, &ulRead, &overlap);

	//可以试验取消例程
	//CancelIo(*(PHANDLE)context);
	WaitForSingleObject(overlap.hEvent, INFINITE);
	return 0;
}

int main() {
	HANDLE hDevice =
		CreateFile("\\\\.\\Dev",
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
		NULL);

	if (hDevice == INVALID_HANDLE_VALUE) {
		printf("Open Device failed!");
		return(-1);
	}

	HANDLE hThread[2];
	hThread[0] = (HANDLE)_beginthreadex(NULL, 0, Thread, &hDevice, 0, NULL);
	hThread[1] = (HANDLE)_beginthreadex(NULL, 0, Thread, &hDevice, 0, NULL);
	CancelIo(hDevice);
	//主线程等待两个子线程结束
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);

	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);

	return 0;
}

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值