鱼C论坛

 找回密码
 立即注册
查看: 5300|回复: 1

StartIO例程

[复制链接]
发表于 2015-4-2 21:27:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
       有时候,对设备的操作必须是串行化的而不能并行执行。如果是同步IRP的话,用同步对象就可以搞定了,比如在IRP处理函数的开始获取Mutex,结束前释放Mutex。如果是异步IRP的话,就复杂一些了,我们可以在驱动里面自己维护一个状态和一个列表,比如当状态会空闲的时候就可以处理IRP,并把状态置为“忙”,然后又有一个IRP过来的时候,假设状态是“忙”,那么就将IRP放入列表中,等一个IRP处理结束后,就去处理列表里面的IRP,然后依次处理所有IRP。这样完全可以搞定,但是得费时间维护列表和状态。幸运的是,DDK已经提供了一个内部队列,这样就大大简化了程序员的工作,我们只需要将IRP往这个内部队列丢就可以了,然后通过StartIO例程串行化处理这些IRP请求。

StartIO例程

DDK提供的内部队列用KDEVICE_QUEUE数据结构表示,如下(从wdm.h中copy的)

  1. typedef struct _KDEVICE_QUEUE {
  2.     CSHORT Type;
  3.     CSHORT Size;
  4.     LIST_ENTRY DeviceListHead;
  5.     KSPIN_LOCK Lock;

  6. #if defined(_AMD64_)

  7.     union {
  8.         BOOLEAN Busy;
  9.         struct {
  10.             LONG64 Reserved : 8;
  11.             LONG64 Hint : 56;
  12.         };
  13.     };

  14. #else

  15.     BOOLEAN Busy;

  16. #endif

  17. } KDEVICE_QUEUE, *PKDEVICE_QUEUE, *PRKDEVICE_QUEUE;
复制代码
这个队列的列头保存在设备对象的DeviceObject->DeviceQueue里面。插入和删除都是系统搞定,我们无需关心。使用这个队列需要设置StartIo例程,比如:

  1. #pragma INITCODE
  2. extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
  3.                                                                 IN PUNICODE_STRING pRegistryPath)
  4. {
  5.         KdPrint(("Enter DriverEntry\n"));

  6.         pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
  7.         pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
  8.         pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
  9.         pDriverObject->MajorFunction[IRP_MJ_CREATE] =
  10.         pDriverObject->MajorFunction[IRP_MJ_READ] =
  11.         pDriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] =
  12.         pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
  13.         pDriverObject->DriverUnload = HelloWDMUnload;
  14.         pDriverObject->DriverStartIo = HelloWDMStartIO;
  15.        

  16.         KdPrint(("Leave DriverEntry\n"));
  17.         return STATUS_SUCCESS;
  18. }
复制代码

通过pDriverObject->DriverStartIo = HelloWDMStartIO;设置StartIo例程。给出StartIo例程的实现:

  1. void HelloWDMStartIO(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
  2. {
  3.         KdPrint(("++++Enter HelloWDMStartIO, IRP address: 0x%x\n", Irp));

  4.         KIRQL oldirql;

  5.         //自旋锁会将当前运行级别提高到DISPATCH_LEVEL,驱动程序如果想调用IoSetCancelRoutine,那么就得先获取这个lock,具体参考
  6.         //http://msdn.microsoft.com/en-us/library/windows/hardware/ff548196(v=vs.85).aspx
  7.         IoAcquireCancelSpinLock(&oldirql);

  8.         //fdo->CurrentIrp就是驱动当前正在处理的IRP。
  9.         if (Irp != fdo->CurrentIrp || Irp->Cancel)
  10.         {//如果Irp不是当前处理的IRP,或者这个Irp是想取消的,那么直接返回,啥也不做。
  11.                 IoReleaseCancelSpinLock(oldirql);
  12.                 KdPrint(("Do nothing\n"));
  13.                 return;
  14.         }
  15.         else
  16.         {//正在处理该IRP
  17.                 KdPrint(("Forbit to use CancelRoutine\n"));
  18.                 IoSetCancelRoutine(Irp, NULL);//不允许调用取消例程

  19.                 IoReleaseCancelSpinLock(oldirql);
  20.         }

  21.         //可以根据需要处理IRP,这里只处理IOCTL_ENCODE
  22.         Encoding(fdo, Irp);

  23.         //在队列中读取下一个IRP,并且进行StartIO.
  24.         IoStartNextPacket(fdo, TRUE);

  25.         KdPrint(("++++Leave HelloWDMStartIO, IRP address: 0x%x\n", Irp));
  26. }
复制代码
StartIo里面需要处理请求,比如这里调用Encoding函数。当然可以根据需要在StartIo里面处理IRP请求。Encoding函数的实现:

  1. #pragma LOCKEDCODE
  2. NTSTATUS Encoding(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
  3. {
  4.         PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

  5.         //得到输入缓冲区大小
  6.         ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;

  7.         //得到输出缓冲区大小
  8.         ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;

  9.         //获取输入缓冲区,IRP_MJ_DEVICE_CONTROL的输入都是通过buffered io的方式
  10.         char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;

  11.         char temp[100] = {0};
  12.         RtlCopyMemory(temp, inBuf, cbin);

  13.         KdPrint(("----Start to Encode string: %s\n", temp));

  14.         //假如需要将数据放到一个公共资源中,然后再进行操作,比如这里是亦或编码,那么就需要考虑同步的问题。
  15.         //不然在多线程调用的时候,公共资源的访问将会有不可预测的问题。
  16.         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

  17.         RtlCopyMemory(pdx->buffer, inBuf, cbin);

  18.         //模拟延时3秒
  19.         KdPrint(("Wait 3s\n"));
  20.         KEVENT event;
  21.         KeInitializeEvent(&event, NotificationEvent, FALSE);
  22.         LARGE_INTEGER timeout;
  23.         timeout.QuadPart = -3 * 1000 * 1000 * 10;//负数表示从现在开始计数,KeWaitForSingleObject的timeout是100ns为单位的。
  24.         KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);//等待3秒

  25.         for (ULONG i = 0; i < cbin; i++)//将输入缓冲区里面的每个字节和m亦或
  26.         {
  27.                 pdx->buffer[i] = pdx->buffer[i] ^ 'm';
  28.         }

  29.         //获取输出缓冲区,这里使用了直接方式,见CTL_CODE的定义,使用了METHOD_IN_DIRECT。所以需要通过直接方式获取out buffer
  30.         KdPrint(("user address: %x, this address should be same to user mode addess.\n", MmGetMdlVirtualAddress(Irp->MdlAddress)));
  31.         //获取内核模式下的地址,这个地址一定> 0x7FFFFFFF,这个地址和上面的用户模式地址对应同一块物理内存
  32.         char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

  33.         ASSERT(cbout >= cbin);
  34.         RtlCopyMemory(outBuf, pdx->buffer, cbin);


  35.         //完成irp
  36.         Irp->IoStatus.Status = STATUS_SUCCESS;
  37.         Irp->IoStatus.Information = cbin;
  38.         IoCompleteRequest(Irp, IO_NO_INCREMENT);

  39.         KdPrint(("----Encode thread finished, string: %s\n",temp));

  40.         return Irp->IoStatus.Status;
  41. }
复制代码

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的派遣函数:

  1. NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
  2. {
  3.         KdPrint(("Enter HelloWDMIOControl\n"));

  4.         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
  5.         PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);


  6.         //得到IOCTRL码
  7.         ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;

  8.         NTSTATUS status;
  9.         ULONG info = 0;
  10.         switch (code)
  11.         {
  12.         case IOCTL_ENCODE:
  13.                 {
  14.                          
  15.                          status = STATUS_PENDING;
  16.                          Irp->IoStatus.Status = status;
  17.                          Irp->IoStatus.Information = info;
  18.                          IoMarkIrpPending(Irp);//将IRP设置为挂起状态(异步IRP)

  19.                          /*********************************************************************************************************
  20.                           调用IoStartPacket,IoStartPacket会调用驱动的StartIo或者将IRP放入内部队列(假如驱动状态是“忙”).
  21.                           当驱动不是“忙”状态的时候,IoStartPacket将会调用StartIo例程。StartIo例程其实是递归操作,那么假如StartIo例程还没有结束的时候,
  22.                           而这个时候又有caller请求过来,那么StartIo例程将进入递归调用。也就是说需要等Device Queue里面所有的请求全部处理完,StartIo例程
  23.                           才返回。假如有3个请求,第一个请求将直接被执行,第二个第三个请求在第一个请求执行期间到达,也就是说第二个第三个请求将会被放在queue
  24.                           里面,当第一个请求执行完毕的时候,StartIo例程会调用IoStartNextPacket处理队列,也就是说StartIo例程会递归2次来处理#2,#3请求。
  25.                           等队列里面所有请求处理完毕,StartIo例程才一个个返回。那么第一个请求的StartIo例程相当于需要3x时间,第二个请求对应的StartIo例程需要
  26.                           2x时间,第三个请求的StartIo需要1x时间。
  27.                         ***********************************************************************************************************/
  28.                          KdPrint(("start to call IoStartPacket, IRP: 0x%x\n", Irp));
  29.                          IoStartPacket(fdo, Irp, 0, HelloWDMOnCancelIRP);
  30.                          KdPrint(("end call IoStartPacket, IRP: 0x%x\n", Irp));
  31.                 }
  32.                 break;
  33.         default:
  34.                 status = STATUS_INVALID_VARIANT;
  35.                 Irp->IoStatus.Status = status;
  36.                 Irp->IoStatus.Information = info;
  37.                 IoCompleteRequest(Irp, IO_NO_INCREMENT);
  38.                 break;
  39.         }

  40.         KdPrint(("Leave HelloWDMIOControl\n"));
  41.         return status;
  42. }
复制代码





小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2016-2-6 13:07:38 | 显示全部楼层
学习,感谢分享!!!!
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-6-16 20:42

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表