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给处理完才返回。