IRP的串行化处理

StartIO例程能够保证各个并行的IRP顺序执行,即串行化。
操作系统为程序员提供了一个IRP队列来实现串行,这个队列用KDEVICE QUEUE
数据结构表示。

typedef struct _KDEVICE_QUEUE {                                        //IRP队列来实现串行  
	CSHORT Type;  
	CSHORT Size;  
	LIST_ENTRY devicelisthead;  	//该队列头保存在DeviceObject->DeviceQueue中,
									//插入和删除该队列中的元素都是由操作系统完成的。
									//在使用这个队列的时候需要向系统提供一个StartIo函数,并将函数名传给系统
	KSPIN_LOCK Lock;  
	BOOLEAN Busy;  
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *RESTRICTED_POINTER PRKDEVICE_QUEUE;

#pragma LOCKEDCODE
VOID MyStartIo(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
	
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path)
{
	
	pDriver->DriverStartIo=MyStartIo;
}

这个StartIO例程运行在DISPATCH LEVEL级别,因此这个例程是不会被线程所打断
的。StartIO 例程的参数类似于派遣函数,只不过没有返回值。注意StartIO 是执行在
DISPATCH_ LEVEL 级别上,因此在声明时要加上#pragma LOCKEDCODE修饰符。

IRP串行处理步骤:

在使用StartIO例程时,需要IRP的派遣例函数返回挂起状态,然后调用loStartPacket
内核函数。下面的代码演示了如何编写这样的派遣函数。

第一步:将需要串行处理的IRP插入队列,在对应的IRP派遣函数中调用IoStartPacket

NTSTATUS IrpReadProc(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)
{

	
	//将IRO设置为挂起
	IoMarkIrpPending(pIrp);

	//如果驱动程序已忙于处理目标设备对象的请求,则数据包将排队在设备队列中。 
	//否则,此例程使用指定的 IRP 调用驱动程序 的 StartIo 例程。
	IoStartPacket(	pDeviceObject,
					pIrp, 
					0, //指向一个值的指针,该值确定将数据包插入到设备队列的位置。如果为零,则将数据包插入到设备队列的尾部
					OnCancelIRP //IRP的取消函数
					);

	//返回这个值 IRP才不会被释放掉.如果返回的是成功,IRP就会在内存中被释放掉.
	return STATUS_PENDING;
}

第二步:IRP的取消函数
取消函数在什么时候调用呢?
在应用程序显示的调用IoCancelIrp 或者是在关闭设备(CloseHandle)的时候,取消函数会被调用.


VOID OnCancelIRP(IN PDEVICE_OBJECT pDeviceObject,
	IN PIRP pIrp
					
	)
{
	if (pIrp==pDeviceObject->CurrentIrp)
	{
		//当前IRP正由StartIo处理
		KIRQL oldIrql = pIrp->CancelIrql;

		//释放Cancel自旋锁
		IoReleaseCancelSpinLock(pIrp->CancelIrql);

		//继续下一个Irp
		IoStartNextPacket(pDeviceObject,TRUE);

		//降低IRQL
		KeLowerIrql(oldIrql);
	}
	else
	{
		//从设备队列中将该IRP抽取出来
		KeRemoveEntryDeviceQueue(&pDeviceObject->DeviceQueue,&pIrp->Tail.Overlay.DeviceQueueEntry);

		//释放Cancel自旋锁
		IoReleaseCancelSpinLock(pIrp->CancelIrql);
	}

	//设置完成状态为STATUS_CANCELLED
	pIrp->IoStatus.Status = STATUS_CANCELLED;

	//设置IRP操作字节数
	pIrp->IoStatus.Information = 0;

	//结束IRP请求
	IoCompleteRequest(pIrp,IO_NO_INCREMENT);

}

第三步:

#pragma LOCKEDCODE
VOID DriverStartIo(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)
{
	KIRQL oldIrql;

	//获取cancel自旋锁
	IoAcquireCancelSpinLock(&oldIrql);

	if (pIrp!=pDeviceObject->CurrentIrp || pIrp->Cancel)
	{
		//如果当前有正在处理的IRP 则简单的入队列,并直接返回
		//入队列的工作由系统完成,在StartIo中不用负责

		IoReleaseCancelSpinLock(oldIrql);
		return;
	}
	else
	{
		//由于正在处理该IRP,所以不允许调用取消函数,
		//因此将此IRP的取消函数设置为NULL
		IoSetCancelRoutine(pIrp,NULL);

		//释放自旋锁
		IoReleaseCancelSpinLock(oldIrql);
	}

	KEVENT event;
	KeInitializeEvent(&event,NotificationEvent,FALSE);

	//等待1秒
	LARGE_INTEGER timeOut;
	timeOut.QuadPart = -1 * 1000 * 1000 * 10;
	KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeOut);

	//设置IRP状态
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;

	//结束IRP请求
	IoCompleteRequest(pIrp,IO_NO_INCREMENT);

	//在队列中读取一个IRP,并进行StartIo
	IoStartNextPacket(pDeviceObject,TRUE);
}

3环 并发IRP测试代码

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
DWORD ThreadProc(LPVOID context)
{
	printf("enter thread");

	//等待3S
	OVERLAPPED overlap={0};

	//创建同步事件
	overlap.hEvent=CreateEvent(0,0,0,0);
	UCHAR buffer[10];
	DWORD dwRead;

	//读取设备
	BOOL bRead=ReadFile(*(PHANDLE)context,buffer,10,&dwRead,&overlap);

	//可以试验取消例程
	CancelIo(*(PHANDLE)context);

	//等待事件
	WaitForSingleObject(overlap.hEvent,INFINITE);
	return 0;
}

int main()
{
	HANDLE hDevice = 
		CreateFile(L"\\\\.\\MyTestDriver",
		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;
	}


	HANDLE hThread[2];

	//开启两个线程
	hThread[0]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadProc,&hDevice,0,0);
	hThread[1]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadProc,&hDevice,0,0);

	//主线程等待两个子线程结束
	WaitForMultipleObjects(2,hThread,TRUE,INFINITE);

	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);

	return 0;
}

总结:在以上的IRP处理程序中,当接收到3环ReadFIle的IRP时,处理流程是这样的:
1:在IRP_MJ_READ处理函数里调用IoStartPacket 把IRP挂入一个等待队列中,把IRP的状态设置为STATUS_PENDING
2:如果没有取消函数 IoStartPacket 调用StartIo处理函数来处理IRP,如果有取消函数,就调用取消回调函数来取消掉IRP。

自定义StartIo

使用系统提供的StartIo时,是吧所有的IRP都集中在一个队列里面处理。
当不同的IRP使用不同的处理时,这就需要多个队列来保存不同的IRP请求。
使用自定义StartIo就可以实现这一需求。

typedef struct _KDEVICE_QUEUE {                                        //IRP队列来实现串行  
	CSHORT Type;  
	CSHORT Size;  
	LIST_ENTRY devicelisthead;  	//该队列头保存在DeviceObject->DeviceQueue中,
									//插入和删除该队列中的元素都是由操作系统完成的。
									//在使用这个队列的时候需要向系统提供一个StartIo函数,并将函数名传给系统
	KSPIN_LOCK Lock;  
	BOOLEAN Busy;  
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *RESTRICTED_POINTER PRKDEVICE_QUEUE;

队列中每个元素用KDEVICE_QUEUE_ENTRY类型的数据表示。

typedef struct _KDEVICE_QUEUE_ENTRY{
    LIST_ENTRY DeviceListEntry;
    ULONG SortKey;
    BOOLEAN Inserted;
}KDEVICE_QUEUE_ENTRY, *PKDEVICE_QUEUE_ENTRY, *RESTRICTED_POINTER 

队列的初始化
队列应该在设备扩展中。

void KeInitializeDeviceQueue(
  PKDEVICE_QUEUE DeviceQueue	设备队列。
);

插入队列

BOOLEAN
KeInsertDeviceQueue (
	IN PKDEVICE_QUEUE DeviceQueue,	//需要被插入的队列
	IN PKDEVICE_QUEUE_ENTRY DeviceQueueEntry	//要被插入的元素
);

返回值:返回值为BOOL值,如果当前设备不忙,则可以直接处理该IRP,因此
这时候不需要插入队列,返回FALSE。如果设备正在处理,这时候需要将IRP插
入队列,这时候返回TRUE。

从队列删除元素

PKDEVICE QUEUE ENTRY
KeRemoveDeviceQueue (
	IN PKDEVICE QUEUJE Devicegueue //指定从哪个队列中取出元素
):

返回值:返回从队列中取出的元素指针

示例代码

第一步:
在DriverEntry中初始化队列,定义一个设备扩展结构体如下:

#pragma once
#include <ntddk.h>
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
#define arraysize(p) (sizeof(p)/sizeof((p)[0]))
typedef struct _DEVICE_EXTENSION {
	PDEVICE_OBJECT pDevice;
	UNICODE_STRING ustrDeviceName;	//设备名称
	UNICODE_STRING ustrSymLinkName;	//符号链接名

	KDEVICE_QUEUE readQueue;
	ULONG Count;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

需要多少个队列就创建多少个队列,接着初始化队列

	PDEVICE_EXTENSION pDevExt = pDeviceObj->DeviceExtension;
	//初始化ReadFile WriteFile  Irp队列
	KeInitializeDeviceQueue(&pDevExt->ReadDeviceQueue);
	KeInitializeDeviceQueue(&pDevExt->WriteDeviceQueue);

第二步:
在IIRP_MJ_READ和IIRP_MJ_WRITE处理函数中调用KeInsertDeviceQueue将IRP加入队列中

#pragma PAGEDCODE
NTSTATUS IrpReadProc(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)
{
	//把IRP给挂起来.
	IoMarkIrpPending(pIrp);

	//获取扩展设备对象
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

	if (!KeInsertDeviceQueue(&pDevExt->readQueue,&pIrp->Tail.Overlay.DeviceQueueEntry))
	{
		//如果插入失败,代表设备不处于忙的状态  可以直接处理该Irp
		ReadStartIo(pDeviceObject, pIrp);
	}
	return STATUS_PENDING;
}

第三步:StartIo处理函数。

#pragma LOCKEDCODE
VOID ReadStartIo(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)

{
	


	//获取扩展设备对象
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

	while (1)
	{
		pIrp->IoStatus.Status = STATUS_SUCCESS;//getlasterror()得到的就是这个值
		pIrp->IoStatus.Information = 0;//返回给3环多少数据,没有填0
		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
		InterlockedIncrement(&pDevExt->Count);
		KdPrint(("已经处理完第%d个IRP\n", pDevExt->Count));

		KEVENT event;
		KeInitializeEvent(&event, NotificationEvent, FALSE);

		//等3秒
		LARGE_INTEGER timeout;
		timeout.QuadPart = -3 * 100 * 1000 ;

		//定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右时间
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);
		PKDEVICE_QUEUE_ENTRY pQueueEntry=KeRemoveDeviceQueue(&pDevExt->readQueue);
		if (pQueueEntry==NULL)
		{
			break;
		}
		pIrp = CONTAINING_RECORD(pQueueEntry, IRP, Tail.Overlay.DeviceQueueEntry);
	}

}

完整代码

#include<ntddk.h>
#include<ntstatus.h>
#include "Driver.h"
#define DEVICE_NAME L"\\Device\\dale"
#define SYMBOLICLINE_NAME L"\\??\\MyTestDriver"  //ring3用CreateFile打开设备时,用"\\\\.\\MyTestDriver"//相当于起的别名


//实现卸载函数
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
	PDEVICE_OBJECT	pNextObj;
	KdPrint(("Enter DriverUnload\n"));
	pNextObj = pDriverObject->DeviceObject;
	while (pNextObj != NULL)
	{
		PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pNextObj->DeviceExtension;

		//删除符号链接
		UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
		IoDeleteSymbolicLink(&pLinkName);

		pNextObj = pNextObj->NextDevice;
		IoDeleteDevice(pDevExt->pDevice);
		DbgPrint("删除设备\n");
	}

}


#pragma PAGEDCODE
NTSTATUS IrpDefaultProc(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)
{

	pIrp->IoStatus.Status = STATUS_SUCCESS;//getlasterror()得到的就是这个值
	pIrp->IoStatus.Information = 0;//返回给3环多少数据,没有填0
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

#pragma LOCKEDCODE
VOID ReadStartIo(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)

{
	


	//获取扩展设备对象
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

	while (1)
	{
		pIrp->IoStatus.Status = STATUS_SUCCESS;//getlasterror()得到的就是这个值
		pIrp->IoStatus.Information = 0;//返回给3环多少数据,没有填0
		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
		InterlockedIncrement(&pDevExt->Count);
		KdPrint(("已经处理完第%d个IRP\n", pDevExt->Count));

		KEVENT event;
		KeInitializeEvent(&event, NotificationEvent, FALSE);

		//等3秒
		LARGE_INTEGER timeout;
		timeout.QuadPart = -3 * 100 * 1000 ;

		//定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右时间
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);

		
		PKDEVICE_QUEUE_ENTRY pQueueEntry=KeRemoveDeviceQueue(&pDevExt->readQueue);
		if (pQueueEntry==NULL)
		{
			break;
		}
		pIrp = CONTAINING_RECORD(pQueueEntry, IRP, Tail.Overlay.DeviceQueueEntry);
	}

}




#pragma PAGEDCODE
NTSTATUS IrpReadProc(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)
{



	//把IRP给挂起来.
	IoMarkIrpPending(pIrp);

	//获取扩展设备对象
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

	if (!KeInsertDeviceQueue(&pDevExt->readQueue,&pIrp->Tail.Overlay.DeviceQueueEntry))
	{
		//如果插入失败,代表设备不处于忙的状态  可以直接处理该Irp
		ReadStartIo(pDeviceObject, pIrp);
	}
	return STATUS_PENDING;
}



NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path)
{

	NTSTATUS status = 0;
	ULONG uIndex = 0;
	PDEVICE_OBJECT pDeviceObj = NULL;
	UNICODE_STRING DeviceName;
	UNICODE_STRING SymbolicLinkName;

	//Irp默认处理
	pDriver->MajorFunction[IRP_MJ_CREATE] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_CLOSE] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_WRITE] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_READ] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_CLEANUP] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_SET_INFORMATION] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_SHUTDOWN] = IrpDefaultProc;
	pDriver->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = IrpDefaultProc;

	//设置派遣函数和卸载函数	
	pDriver->DriverUnload = DriverUnload;

	pDriver->MajorFunction[IRP_MJ_READ] = IrpReadProc;

	
	
	
	//创建设备名称
	RtlInitUnicodeString(&DeviceName, DEVICE_NAME);

	//创建设备  让三环的API能够找到,才能实现通信
	status = IoCreateDevice(pDriver,
		sizeof(DEVICE_EXTENSION),	//扩展设备大小 
		&DeviceName,
		FILE_DEVICE_UNKNOWN,
		0,
		TRUE,
		&pDeviceObj);
	if (status != STATUS_SUCCESS)
	{
		DbgPrint("创建设备失败! status=%x\r\n", status);
		return status;
	}

	//设置交互数据方式
	pDeviceObj->Flags |= DO_BUFFERED_IO;

	//创建符号链接名称,就是给该设备在三环起个能用的别名
	RtlInitUnicodeString(&SymbolicLinkName, SYMBOLICLINE_NAME);

	//创建符号链接
	status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("创建符号链接失败!\r\n");
		IoDeleteDevice(pDeviceObj);
		return status;
	}
	DbgPrint("创建符号链接成功!\r\n");
	PDEVICE_EXTENSION pDevExt = pDeviceObj->DeviceExtension;
	pDevExt->pDevice = pDeviceObj;
	pDevExt->ustrDeviceName = DeviceName;
	pDevExt->ustrSymLinkName = SymbolicLinkName;


	/*测试代码*/

	//初始化读的设备队列
	KeInitializeDeviceQueue(&pDevExt->readQueue);
	pDevExt->Count = 0;
	/*测试代码*/

	return STATUS_SUCCESS;

}

3环测试代码

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

DWORD ThreadProc(LPVOID Context)
{
	HANDLE hDevice=(HANDLE)Context;
	OVERLAPPED Overlap={0};
	ULONG ByteToRead=4;

	for (int i=0;i<10;i++)
	{
		ReadFile(hDevice,0,0,&ByteToRead,&Overlap);
	}

	return 0;
}


int main()
{

	HANDLE hDevice=CreateFile(	L"\\\\.\\MyTestDriver",
		GENERIC_READ | GENERIC_WRITE,
		0,
		0,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,//FILE_FLAG_OVERLAPPED表示使用异步打开
		0
		);

	if (hDevice==INVALID_HANDLE_VALUE)
	{
		printf("设备打开失败;错误码:%x\n",GetLastError());
		getchar();
		return false;
	}

	//提供该结构体即可,不需要初始化事件
	OVERLAPPED Overlap={0};
	ULONG ByteToRead=4;
	HANDLE hThread[3]={0};
	//这里不需要OVERLAPPED结构里面的EVENT事件对象
	hThread[0]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadProc,(PVOID)hDevice,0,0);
	hThread[1]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadProc,(PVOID)hDevice,0,0);
	hThread[2]=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadProc,(PVOID)hDevice,0,0);
	WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
	CloseHandle(hDevice);
	
	return 0;
}

总结:
自定义StartIo的处理流程:
在IRP_MJ_READ处理函数中,把IRP加入队列,加入队列的函数会判断当前设备是否空闲。
如果设备时空闲的,此IRP不加入队列,调用自定义StartIo直接处理。如果设备处于忙的状态就加入队列。
在自定义的StartIo中,会把队列中的IRP给处理完才返回。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值