马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
有时候,对设备的操作必须是串行化的而不能并行执行。如果是同步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[IRP_MJ_PNP] = HelloWDMPnp;
- pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
- pDriverObject->MajorFunction[IRP_MJ_CREATE] =
- pDriverObject->MajorFunction[IRP_MJ_READ] =
- pDriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] =
- pDriverObject->MajorFunction[IRP_MJ_WRITE] = 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[100] = {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[i] = pdx->buffer[i] ^ '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).aspx If 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将会被递归调用。为了搞清楚这个递归过程,我画了个流程图: 从上图可以看到StartIo是个递归调用,直到队列里面所有的请求都处理完才返回。比方说:有3个IRP请求,每个请求会花费3秒钟。驱动在1秒内收到了这3个请求(假设是A,B,C三个请求,先后顺序是A,B,C)。那么StartIo的调用过程就是: A的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;
- }
复制代码
|