KeInsertDeviceQueue()

凡是支持异步操作的设备驱动都要提供一个"I/O启动函数",并使驱动对象中的函数指针DriverStartIo指向这个函数。一般而言,调用了一次启动函数之后,就要等到所启动的过程完成才能再次调用启动函数。但是,在一段时间内,IRP的到来完全有可能快于所启动的过程,使得第一个IRP所启动的过程尚未结束就来了第二个IRP。这并非因为支持异步操作所致,而是因为后面的IRP来自不同的线程,然而却是针对同一个设备对象的。在这样的情况下,显然不能来一个IRP就调用一次启动函数,而要为后来的IRP提供缓冲延迟,将其挂入一个等待队列。设备对象的数据结构中有个缓冲队列DeviceQueue,就是为此而设的。当一个IRP到来时,是否立即就执行目标设备对象的I/O启动函数取决于这个队列的状况。所以,这里通过KeInsertDeviceQueue()检查是否需要将IRP挂入队列,如果需要,即目标设备已经执行了启动函数而尚未完成,就将其挂入队列,否则就不需要挂入。

  
  
  
  
  1. [NtReadFile() > IopPerformSynchronousRequest() >  IopQueueIrpToThread() > IoCallDriver()  
  2.  > ClassRead() > IoStartPacket() > KeInsertDeviceQueue()]  
  3.  
  4. BOOLEAN NTAPI KeInsertDeviceQueue(IN PKDEVICE_QUEUE DeviceQueue,  
  5.                                IN PKDEVICE_QUEUE_ENTRY DeviceQueueEntry)  
  6. {  
  7.     KLOCK_QUEUE_HANDLE DeviceLock;  
  8.     BOOLEAN Inserted;  
  9.     ASSERT_DEVICE_QUEUE(DeviceQueue);  
  10.  
  11.     /* Lock the queue */  
  12.     KiAcquireDeviceQueueLock(DeviceQueue, &DeviceLock);  
  13.     /* Check if it's not busy */  
  14.     if (!DeviceQueue->Busy)  
  15.     {   //目标设备对象空闲,不需要挂入队列,可以直接执行启动函数  
  16.         /* Set it as busy */  
  17.         Inserted = FALSE;  
  18.         DeviceQueue->Busy = TRUE;    //但是现在目标设备对象已经不再空闲  
  19.     }  
  20.     else  
  21.     {   //目标设备对象已经执行过启动函数但尚未完成,需要挂入队列  
  22.         /* Insert it into the list */  
  23.         Inserted = TRUE;  
  24.         InsertTailList(&DeviceQueue->DeviceListHead,  
  25.                                        &DeviceQueueEntry->DeviceListEntry);  
  26.     }  
  27.     /* Set the Insert state into the entry */  
  28.     DeviceQueueEntry->InsertedInserted = Inserted;  
  29.     /* Release the lock */  
  30.     KiReleaseDeviceQueueLock(&DeviceLock);  
  31.     /* Return the state */  
  32.     return Inserted;    //返回TRUE表示已排入队列,FALSE表示可以继续操作  

这样,如果KeInsertDeviceQueue()返回FALSE,就说明目标设备对象正是空闲,可以直接调用其启动函数,所以没有把IRP挂入队列,但是已经把设备对象的状态改成了忙,如果再有IRP到来就要挂入队列了。

而如果返回TRUE,则说明设备对象正在忙,不能马上调用其启动函数,因而已经把IRP挂入了队列。这时候,如果IRP中的Cancel字段为TRUE,并且通过参数CancelFunction给定了一个"取消函数",就要调用这个函数。

至于I/O启动函数,那就是具体设备驱动的事,不是设备驱动框架即I/O管理机制的事了。通常,一旦启动了目标设备的I/O,以后的活动就是受中断驱动,由中断服务程序和DPC函数加以处理了。还有一种可能,就是其中的某些操作由某个内核线程加以处理。不过,记录着操作要求的IRP留在了目标设备对象的IRP队列中。

而当前线程,则从IoStartPacket返回,一直到IopPerformSynchronousRequest()中可能会睡眠等待,或者就一路返回到用户空间,对于设备的操作就交给中断服务程序和DPC函数或许还有内核线程了。实际上,交给DPC函数或内核线程完成的工作还包括对于用来同步的事件执行V操作并递交APC请求(如果需要的话)。

注意中断服务程序和DPC函数都有可能不是在原来这个当前线程的上下文中执行,发生中断时的当前线程很有可能是另一个线程,因而使用另一个线程的系统空间堆栈。而DPC函数的执行,则使用一个专门的DPC堆栈,但是当时的当前线程很有可能是另一个线程,并从而很可能是在使用另一个进程的页面映射表。

异步操作还可能带来一个问题,就是某一层具体的设备驱动可能要求在IRP所要求的操作完成以后进行一些善后的操作,例如释放某些资源、向某个对象发出通知、对某个事件执行V操作等,可是这一层的主功能函数早已经返回了,怎么来执行这些操作呢?显然,把它托付给DPC函数是个办法。为此,IRP中的每个叠位即IO_STACK_LOCATION数据结构中都有个函数指针CompletionRoutine,可以用来指向这一层的善后函数。如果某一层驱动要将一个善后函数托付给DPC函数,就可以通过宏操作IoSetCompletionRoutine()设置好这个指针和表示执行条件的标志位:

  
  
  
  
  1. #define IoSetCompletionRoutine(_Irp, _CompletionRoutine, _Context, \  
  2.                           _InvokeOnSuccess,  _InvokeOnError, _InvokeOnCancel) \  
  3. { \  
  4.   PIO_STACK_LOCATION _IrpSp; \  
  5.   ASSERT(_InvokeOnSuccess || _InvokeOnError ||  _InvokeOnCancel ? \  
  6.                                        _CompletionRoutine != NULL : TRUE); \  
  7.   _IrpSp = IoGetNextIrpStackLocation(_Irp); \  
  8.   _IrpSp->CompletionRoutine = (PIO_COMPLETION_ROUTINE) (_CompletionRoutine); \  
  9.   _IrpSp->Context = (_Context); \  
  10.   _IrpSp->Control = 0; \  
  11.   if (_InvokeOnSuccess) _IrpSp->Control = SL_INVOKE_ON_SUCCESS; \  
  12.   if (_InvokeOnError) _IrpSp->Control |= SL_INVOKE_ON_ERROR; \  
  13.   if (_InvokeOnCancel) _IrpSp->Control |= SL_INVOKE_ON_CANCEL; \  
  14. }

你可能感兴趣的:(KeInsertDeviceQueue())