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