天天看點

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給處理完才傳回。