StartIO例程
有时候,对设备的操作必须是串行化的而不能并行执行。如果是同步IRP的话,用同步对象就可以搞定了,比如在IRP处理函数的开始获取Mutex,结束前释放Mutex。如果是异步IRP的话,就复杂一些了,我们可以在驱动里面自己维护一个状态和一个列表,比如当状态会空闲的时候就可以处理IRP,并把状态置为“忙”,然后又有一个IRP过来的时候,假设状态是“忙”,那么就将IRP放入列表中,等一个IRP处理结束后,就去处理列表里面的IRP,然后依次处理所有IRP。这样完全可以搞定,但是得费时间维护列表和状态。幸运的是,DDK已经提供了一个内部队列,这样就大大简化了程序员的工作,我们只需要将IRP往这个内部队列丢就可以了,然后通过StartIO例程串行化处理这些IRP请求。StartIO例程DDK提供的内部队列用KDEVICE_QUEUE数据结构表示,如下(从wdm.h中copy的)typedef struct _KDEVICE_QUEUE {
CSHORT Type;
CSHORT Size;
LIST_ENTRY DeviceListHead;
KSPIN_LOCK Lock;
#if defined(_AMD64_)
union {
BOOLEAN Busy;
struct {
LONG64 Reserved : 8;
LONG64 Hint : 56;
};
};
#else
BOOLEAN Busy;
#endif
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *PRKDEVICE_QUEUE;这个队列的列头保存在设备对象的DeviceObject->DeviceQueue里面。插入和删除都是系统搞定,我们无需关心。使用这个队列需要设置StartIo例程,比如:#pragma INITCODE
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
KdPrint(("Enter DriverEntry\n"));
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
pDriverObject->MajorFunction = HelloWDMPnp;
pDriverObject->MajorFunction =
pDriverObject->MajorFunction =
pDriverObject->MajorFunction =
pDriverObject->MajorFunction =
pDriverObject->MajorFunction = HelloWDMDispatchRoutine;
pDriverObject->DriverUnload = HelloWDMUnload;
pDriverObject->DriverStartIo = HelloWDMStartIO;
KdPrint(("Leave DriverEntry\n"));
return STATUS_SUCCESS;
}
通过pDriverObject->DriverStartIo = HelloWDMStartIO;设置StartIo例程。给出StartIo例程的实现:void HelloWDMStartIO(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
KdPrint(("++++Enter HelloWDMStartIO, IRP address: 0x%x\n", Irp));
KIRQL oldirql;
//自旋锁会将当前运行级别提高到DISPATCH_LEVEL,驱动程序如果想调用IoSetCancelRoutine,那么就得先获取这个lock,具体参考
//http://msdn.microsoft.com/en-us/library/windows/hardware/ff548196(v=vs.85).aspx
IoAcquireCancelSpinLock(&oldirql);
//fdo->CurrentIrp就是驱动当前正在处理的IRP。
if (Irp != fdo->CurrentIrp || Irp->Cancel)
{//如果Irp不是当前处理的IRP,或者这个Irp是想取消的,那么直接返回,啥也不做。
IoReleaseCancelSpinLock(oldirql);
KdPrint(("Do nothing\n"));
return;
}
else
{//正在处理该IRP
KdPrint(("Forbit to use CancelRoutine\n"));
IoSetCancelRoutine(Irp, NULL);//不允许调用取消例程
IoReleaseCancelSpinLock(oldirql);
}
//可以根据需要处理IRP,这里只处理IOCTL_ENCODE
Encoding(fdo, Irp);
//在队列中读取下一个IRP,并且进行StartIO.
IoStartNextPacket(fdo, TRUE);
KdPrint(("++++Leave HelloWDMStartIO, IRP address: 0x%x\n", Irp));
}StartIo里面需要处理请求,比如这里调用Encoding函数。当然可以根据需要在StartIo里面处理IRP请求。Encoding函数的实现:#pragma LOCKEDCODE
NTSTATUS Encoding(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
//得到输入缓冲区大小
ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;
//得到输出缓冲区大小
ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;
//获取输入缓冲区,IRP_MJ_DEVICE_CONTROL的输入都是通过buffered io的方式
char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;
char temp = {0};
RtlCopyMemory(temp, inBuf, cbin);
KdPrint(("----Start to Encode string: %s\n", temp));
//假如需要将数据放到一个公共资源中,然后再进行操作,比如这里是亦或编码,那么就需要考虑同步的问题。
//不然在多线程调用的时候,公共资源的访问将会有不可预测的问题。
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
RtlCopyMemory(pdx->buffer, inBuf, cbin);
//模拟延时3秒
KdPrint(("Wait 3s\n"));
KEVENT event;
KeInitializeEvent(&event, NotificationEvent, FALSE);
LARGE_INTEGER timeout;
timeout.QuadPart = -3 * 1000 * 1000 * 10;//负数表示从现在开始计数,KeWaitForSingleObject的timeout是100ns为单位的。
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);//等待3秒
for (ULONG i = 0; i < cbin; i++)//将输入缓冲区里面的每个字节和m亦或
{
pdx->buffer = pdx->buffer ^ 'm';
}
//获取输出缓冲区,这里使用了直接方式,见CTL_CODE的定义,使用了METHOD_IN_DIRECT。所以需要通过直接方式获取out buffer
KdPrint(("user address: %x, this address should be same to user mode addess.\n", MmGetMdlVirtualAddress(Irp->MdlAddress)));
//获取内核模式下的地址,这个地址一定> 0x7FFFFFFF,这个地址和上面的用户模式地址对应同一块物理内存
char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
ASSERT(cbout >= cbin);
RtlCopyMemory(outBuf, pdx->buffer, cbin);
//完成irp
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = cbin;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
KdPrint(("----Encode thread finished, string: %s\n",temp));
return Irp->IoStatus.Status;
}StartIo例程的递归使用StartIo例程会接触到2个函数:IoStartPacket和IoStartNextPacket。IoStartPacket:如果驱动不是“忙”状态,那么会调用StartIo例程;如果是“忙”状态那么就放入队列,立刻返回。MSDN:http://msdn.microsoft.com/en-us/library/windows/hardware/ff550370(v=vs.85).aspxIf the driver is already busy processing a request for the target device object, then the packet is queued in the device queue. Otherwise, this routine calls the driver'sStartIo routine with the specified IRP.IoStartNextPacket: 如果队列中有请求需要处理的话,IoStartNextPacket会取出一个请求,调用StartIo例程。如果没有请求了,那么这个函数就直接返回。从StartIo例程的实现里面可以看到,StartIo例程的最后会调用IoStartNextPacket函数,那么当队列里面还有请求的话,StartIo将会被递归调用。为了搞清楚这个递归过程,我画了个流程图:http://img.my.csdn.net/uploads/201212/12/1355295077_7538.png从上图可以看到StartIo是个递归调用,直到队列里面所有的请求都处理完才返回。比方说:有3个IRP请求,每个请求会花费3秒钟。驱动在1秒内收到了这3个请求(假设是A,B,C三个请求,先后顺序是A,B,C)。那么StartIo的调用过程就是:http://img.my.csdn.net/uploads/201212/12/1355296245_2651.pngA的IoStartPacket函数要等处理完A,B,C三个请求才返回。而B和C的IoStartPacket函数将把IRP放入队列,然后马上返回。看一下DeviceIoControl的派遣函数:NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
KdPrint(("Enter HelloWDMIOControl\n"));
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
//得到IOCTRL码
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
NTSTATUS status;
ULONG info = 0;
switch (code)
{
case IOCTL_ENCODE:
{
status = STATUS_PENDING;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = info;
IoMarkIrpPending(Irp);//将IRP设置为挂起状态(异步IRP)
/*********************************************************************************************************
调用IoStartPacket,IoStartPacket会调用驱动的StartIo或者将IRP放入内部队列(假如驱动状态是“忙”).
当驱动不是“忙”状态的时候,IoStartPacket将会调用StartIo例程。StartIo例程其实是递归操作,那么假如StartIo例程还没有结束的时候,
而这个时候又有caller请求过来,那么StartIo例程将进入递归调用。也就是说需要等Device Queue里面所有的请求全部处理完,StartIo例程
才返回。假如有3个请求,第一个请求将直接被执行,第二个第三个请求在第一个请求执行期间到达,也就是说第二个第三个请求将会被放在queue
里面,当第一个请求执行完毕的时候,StartIo例程会调用IoStartNextPacket处理队列,也就是说StartIo例程会递归2次来处理#2,#3请求。
等队列里面所有请求处理完毕,StartIo例程才一个个返回。那么第一个请求的StartIo例程相当于需要3x时间,第二个请求对应的StartIo例程需要
2x时间,第三个请求的StartIo需要1x时间。
***********************************************************************************************************/
KdPrint(("start to call IoStartPacket, IRP: 0x%x\n", Irp));
IoStartPacket(fdo, Irp, 0, HelloWDMOnCancelIRP);
KdPrint(("end call IoStartPacket, IRP: 0x%x\n", Irp));
}
break;
default:
status = STATUS_INVALID_VARIANT;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = info;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
break;
}
KdPrint(("Leave HelloWDMIOControl\n"));
return status;
}
学习,感谢分享!!!!
页:
[1]