Winoows内核设计思想之IRP
+ -

IRP的完成IoCompleteRequest

2021-07-16 501 0

每当一个IRP在下层设备层完成时,是需要调用IoCompleteRequest来实现IRP的完成,这个完成其实是实现对执行的IRP的善后操作,这个操作其实是一个宏,真实函数数是IofCompleteRequest。

#define IoCompleteRequest IofCompleteRequest

Windows内核对设备的IO操作在Windows驱动层中表现为IRP,Windows IO的操作分为同步操作和异步操作,所以表现在IRP上为IRP同步等待完成和挂起返回,具体何时完成由根据实际情况选择时机完成。这个时机的载体一般就是DPC,异步线程等。

我们知道,当设备在创建一个IRP时,其中一个参数就是设备的stackSize,使用这个参数,系统创建的IRP将为其设备对应的各栈分配一个IO_STACK_LOCATION,下层设备可用也可不用这个STACK_LOCATION。如果要用,是需要调用函数IoCopyCurrentIrpStackLocationToNext,如果不需要可使用IoSkipCurrentIrpStackLocation函数跳当前设备栈。当IRP通过一层层的IoCallDriver到达底层设备时,这个底层设备是需要最终实现对该IRP的完成。
当IRP完成时,是需要调用IoCompleteRequest函数执行善后操作的,同时我们也知道,当执行了该操作后,中间的各层设备由于设置了相应的完成例程,有一次机会再次获取当前IRP的所有权进行再次操作。

Windows并未提供该函数的代码,不过ReactOS提供了相应的代码:

VOID FASTCALL IofCompleteRequest    (    IN PIRP     Irp,
IN CCHAR     PriorityBoost 
)    
{
     PIO_STACK_LOCATION StackPtr, LastStackPtr;
     PDEVICE_OBJECT DeviceObject;
     PFILE_OBJECT FileObject;
     PETHREAD Thread;
     NTSTATUS Status;
     PMDL Mdl, NextMdl;
     ULONG MasterCount;
     PIRP MasterIrp;
     ULONG Flags;
     NTSTATUS ErrorCode = STATUS_SUCCESS;
     PREPARSE_DATA_BUFFER DataBuffer = NULL;
     IOTRACE(IO_IRP_DEBUG,
             "%s - Completing IRP %p\n",
             __FUNCTION__,
             Irp);
     /* Make sure this IRP isn't getting completed twice or is invalid */
     if ((Irp->CurrentLocation) > (Irp->StackCount + 1))
     {
         /* Bugcheck */
         KeBugCheckEx(MULTIPLE_IRP_COMPLETE_REQUESTS, (ULONG_PTR)Irp, 0, 0, 0);
     }
     /* Some sanity checks */
     ASSERT(Irp->Type == IO_TYPE_IRP);
     ASSERT(!Irp->CancelRoutine);
     ASSERT(Irp->IoStatus.Status != STATUS_PENDING);
     ASSERT(Irp->IoStatus.Status != (NTSTATUS)0xFFFFFFFF);
     /* Get the last stack */
     LastStackPtr = (PIO_STACK_LOCATION)(Irp + 1);
     if (LastStackPtr->Control & SL_ERROR_RETURNED)
     {
         /* Get the error code */
         ErrorCode = PtrToUlong(LastStackPtr->Parameters.Others.Argument4);
     }
     /*
      * Start the loop with the current stack and point the IRP to the next stack
      * and then keep incrementing the stack as we loop through. The IRP should
      * always point to the next stack location w.r.t the one currently being
      * analyzed, so completion routine code will see the appropriate value.
      * Because of this, we must loop until the current stack location is +1 of
      * the stack count, because when StackPtr is at the end, CurrentLocation is +1.
      */
     for (StackPtr = IoGetCurrentIrpStackLocation(Irp),
          Irp->CurrentLocation++,
          Irp->Tail.Overlay.CurrentStackLocation++;
          Irp->CurrentLocation <= (Irp->StackCount + 1);
          StackPtr++,
          Irp->CurrentLocation++,
          Irp->Tail.Overlay.CurrentStackLocation++)
     {
         /* Set Pending Returned */
         Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
         /* Check if we failed */
         if (!NT_SUCCESS(Irp->IoStatus.Status))
         {
             /* Check if it was changed by a completion routine */
             if (Irp->IoStatus.Status != ErrorCode)
             {
                 /* Update the error for the current stack */
                 ErrorCode = Irp->IoStatus.Status;
                 StackPtr->Control |= SL_ERROR_RETURNED;
                 LastStackPtr->Parameters.Others.Argument4 = UlongToPtr(ErrorCode);
                 LastStackPtr->Control |= SL_ERROR_RETURNED;
             }
         }
         /* Check if there is a Completion Routine to Call */
         if ((NT_SUCCESS(Irp->IoStatus.Status) &&
              (StackPtr->Control & SL_INVOKE_ON_SUCCESS)) ||
             (!NT_SUCCESS(Irp->IoStatus.Status) &&
              (StackPtr->Control & SL_INVOKE_ON_ERROR)) ||
             (Irp->Cancel &&
              (StackPtr->Control & SL_INVOKE_ON_CANCEL)))
         {
             /* Clear the stack location */
             IopClearStackLocation(StackPtr);
             /* Check for highest-level device completion routines */
             if (Irp->CurrentLocation == (Irp->StackCount + 1))
             {
                 /* Clear the DO, since the current stack location is invalid */
                 DeviceObject = NULL;
             }
             else
             {
                 /* Otherwise, return the real one */
                 DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject;
             }
             /* Call the completion routine */
             Status = StackPtr->CompletionRoutine(DeviceObject,
                                                  Irp,
                                                  StackPtr->Context);
             /* Don't touch the Packet in this case, since it might be gone! */
             if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;
         }
         else
         {
             /* Otherwise, check if this is a completed IRP */
             if ((Irp->CurrentLocation <= Irp->StackCount) &&
                 (Irp->PendingReturned))
             {
                 /* Mark it as pending */
                 IoMarkIrpPending(Irp);
             }
             /* Clear the stack location */
             IopClearStackLocation(StackPtr);
         }
     }
     /* Check if the IRP is an associated IRP */
     if (Irp->Flags & IRP_ASSOCIATED_IRP)
     {
         /* Get the master IRP and count */
         MasterIrp = Irp->AssociatedIrp.MasterIrp;
         MasterCount = InterlockedDecrement(&MasterIrp->AssociatedIrp.IrpCount);
         /* Free the MDLs */
         for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
         {
             /* Go to the next one */
             NextMdl = Mdl->Next;
             IoFreeMdl(Mdl);
         }
         /* Free the IRP itself */
         IoFreeIrp(Irp);
         /* Complete the Master IRP */
         if (!MasterCount) IofCompleteRequest(MasterIrp, PriorityBoost);
         return;
     }
     /* Check whether we have to reparse */
     if (Irp->IoStatus.Status == STATUS_REPARSE)
     {
         if (Irp->IoStatus.Information > IO_REMOUNT)
         {
             /* If that's a reparse tag we understand, save the buffer from deletion */
             if (Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)
             {
                 ASSERT(Irp->Tail.Overlay.AuxiliaryBuffer != NULL);
                 DataBuffer = (PREPARSE_DATA_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
                 Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
             }
             else
             {
                 Irp->IoStatus.Status = STATUS_IO_REPARSE_TAG_NOT_HANDLED;
             }
         }
     }
     /* Check if we have an auxiliary buffer */
     if (Irp->Tail.Overlay.AuxiliaryBuffer)
     {
         /* Free it */
         ExFreePool(Irp->Tail.Overlay.AuxiliaryBuffer);
         Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
     }
     /* Check if this is a Paging I/O or Close Operation */
     if (Irp->Flags & (IRP_PAGING_IO | IRP_CLOSE_OPERATION))
     {
         /* Handle a Close Operation or Sync Paging I/O */
         if (Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_CLOSE_OPERATION))
         {
             /* Set the I/O Status and Signal the Event */
             Flags = Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_PAGING_IO);
             *Irp->UserIosb = Irp->IoStatus;
             KeSetEvent(Irp->UserEvent, PriorityBoost, FALSE);
             /* Free the IRP for a Paging I/O Only, Close is handled by us */
             if (Flags)
             {
                 /* If we were using the reserve IRP, then call the appropriate
                  * free function (to make the IRP available again)
                  */
                 if (Irp == IopReserveIrpAllocator.ReserveIrp)
                 {
                     IopFreeReserveIrp(PriorityBoost);
                 }
                 /* Otherwise, free for real! */
                 else
                 {
                     IoFreeIrp(Irp);
                 }
             }
         }
         else
         {
 #if 0
             /* Page 166 */
             KeInitializeApc(&Irp->Tail.Apc
                             &Irp->Tail.Overlay.Thread->Tcb,
                             Irp->ApcEnvironment,
                             IopCompletePageWrite,
                             NULL,
                             NULL,
                             KernelMode,
                             NULL);
             KeInsertQueueApc(&Irp->Tail.Apc,
                              NULL,
                              NULL,
                              PriorityBoost);
 #else
             /* Not implemented yet. */
             UNIMPLEMENTED_DBGBREAK("Not supported!\n");
 #endif
         }
         /* Get out of here */
         return;
     }
     /* Unlock MDL Pages, page 167. */
     Mdl = Irp->MdlAddress;
     while (Mdl)
     {
         MmUnlockPages(Mdl);
         Mdl = Mdl->Next;
     }
     /* Check if we should exit because of a Deferred I/O (page 168) */
     if ((Irp->Flags & IRP_DEFER_IO_COMPLETION) && !(Irp->PendingReturned))
     {
         /* Restore the saved reparse buffer for the caller */
         if (Irp->IoStatus.Status == STATUS_REPARSE &&
             Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)
         {
             Irp->Tail.Overlay.AuxiliaryBuffer = (PCHAR)DataBuffer;
         }
         /*
          * Return without queuing the completion APC, since the caller will
          * take care of doing its own optimized completion at PASSIVE_LEVEL.
          */
         return;
     }
     /* Get the thread and file object */
     Thread = Irp->Tail.Overlay.Thread;
     FileObject = Irp->Tail.Overlay.OriginalFileObject;
     /* Make sure the IRP isn't canceled */
     if (!Irp->Cancel)
     {
         /* Initialize the APC */
         KeInitializeApc(&Irp->Tail.Apc,
                         &Thread->Tcb,
                         Irp->ApcEnvironment,
                         IopCompleteRequest,
                         NULL,
                         NULL,
                         KernelMode,
                         NULL);
         /* Queue it */
         KeInsertQueueApc(&Irp->Tail.Apc,
                          FileObject,
                          DataBuffer,
                          PriorityBoost);
     }
     else
     {
         /* The IRP just got canceled... does a thread still own it? */
         if (Thread)
         {
             /* Yes! There is still hope! Initialize the APC */
             KeInitializeApc(&Irp->Tail.Apc,
                             &Thread->Tcb,
                             Irp->ApcEnvironment,
                             IopCompleteRequest,
                             NULL,
                             NULL,
                             KernelMode,
                             NULL);
             /* Queue it */
             KeInsertQueueApc(&Irp->Tail.Apc,
                              FileObject,
                              DataBuffer,
                              PriorityBoost);
         }
         else
         {
             /* Nothing left for us to do, kill it */
             ASSERT(Irp->Cancel);
             IopCleanupIrp(Irp, FileObject);
         }
     }
 }

IofCompleteRequest该函数在最前面首先进行必有的参数校验,注意,这里有一次系统中断蓝屏的错误报告:MULTIPLE_IRP_COMPLETE_REQUESTS,从这里来看,该蓝屏操作只有一个有效的参数即该IRP,其余参数都为0。
从这个错误的条件来看,多次完成只是其中之一,也有可能IRP数据被破坏,只是这个检测确实有点简单。
具体关于MULTIPLE_IRP_COMPLETE_REQUESTS蓝屏有分析可参见:

参数校验完成后,然后从当前IO_STACK_LOCATION开始,向上回溯。如果该IRP因为错误或失败执行IoCompleteRequest则是分别标识各IO_STACK_LOCATION栈信息。同时根据各栈的IRP完成例程函数执行其对应的IRP完成例程。
在执行格栈的IRP完成例程之彰,得先获取其对应栈的设备对象,当是最顶层也即是最底层时,将设备对象指针设为NULL.
当然如果没有设备完成例程,直接清除当前栈的数据信息,随时准备返回。这种情况对应于IoCopyCurrentIrpStackLocationToNext但并不设置其完成例程即并不执行 IoSetCompletionRoutine函数。

对于IRP上层IRP挂起返回的操作,在完成时需要判断是否已经PendingReturned,如果是则继续IoMarkIrpPending,表示继续续等待返回。

 Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;

Pending返回的IRP都需要执行IoMarkIrpPending函数,表示当前IRP挂起。表示系统不用等待IRP执行返回,这是一个异步操作。

VOID
IoMarkIrpPending(
  _Inout_ PIRP Irp)
{
    IoGetCurrentIrpStackLocation((Irp))->Control |= SL_PENDING_RETURNED;
}

我们接下来看,后面的是根据IRP是否一个关联IRP。如果是关联IRP,还需要对与之相关联的主IRP进行完成操作。

关联IRP一种内核操作,表示当执行IRP执行在设备驱动某层时,该IRP转化为了对多个下层的操作请求。所以这个IRP变成了多个IRP,这时其中的一个IRP变成了主IRP,其余IRP变成了关联IRP,所以在此需对此IRP关联的主IRP进行完成操作。

后续判断返回状态是否执行REPARSE操作,这种操作应该发生在文件路径重解析之内。

该IRP是否有与之对应的辅助内存,如果存在,这里应该进行清理。

后续判断是否内存页倒换的操作。这里我们忽略,在文件系统中我们再分析。

是否分配MDL,进行MDL内存解锁。

最近根据IRP是否是取消操作取完,执行不同的APC完成函数调用,此操作需要该IRP对应的线程之中。所以这里创建对应的APC操作,并将对应的APC函数挂入其对应的线程APC队列中,等待调度执行。
IRP的APC完成例程,见IopCompleteRequest函数解析。

0 篇笔记 写笔记

自定义IRP的回收利用
前面说到,我们有时需要自已生成新的IPR并派发到下层驱动,所以这里的IRP派发一般的情况下是频繁的,所以上节中会出现频敏的IoAllocateIrp和IoFreeIrp。这样会造成性能上的损失,那有没有更好的处理方式了。答案是肯定的:在说答案之前,我们先介绍一个函数:IoReuseIrp,其REAC......
IRP的完成IoCompleteRequest
每当一个IRP在下层设备层完成时,是需要调用IoCompleteRequest来实现IRP的完成,这个完成其实是实现对执行的IRP的善后操作,这个操作其实是一个宏,真实函数数是IofCompleteRequest。#define IoCompleteRequest IofCompleteReque......
Windows IRP结构字段解释
 比如说你开发了一个USB接口的设备, 那么为了在windows上使用它, 你需要开发一个驱动程序,也许还需要一个应用程序.有些硬件是"免驱"的.但事实上不是不需要驱动,而是他所需要的驱动已经存在在windows中了,不需要你另外安装而已.  你使用应用程序可以控制该硬件的行为,过程是应用程序发出......
自定义IRP的完成与处理
一般情况下,当我们在windows驱动中收到IRP时,驱动根据实际情况分为几种情况:第一种是直接完成填充相关的参数后,直接调用IoCompleteRequest完成。第二种情况是该层做相关的设置,如设置完成例程,然后调用IoCallDriver进行下发。在第二种情况下,一般是当在IRP_MN_STA......
IRP与LIST_ENTRY的关联使用
IRP是Windows内核中的一个很重要的概念,代表着应用或内核对设备的请求。LIST_ENTRY又是Windows内核提供的一个链表。IRP挂入LIST_ENTRYVOID RecyleFrame(PFDO_DEVICE_EXTENSION deviceExtension, FrameHead......
IoCopyCurrentIrpStackLocationToNext和IoSkipCurrentIrpStackLocation操作的IO_STACK_LOCATION有什么区别
在Windows驱动中,传递IPR一般有两种操作:一种是调用IoSkipCurrentIrpStackLocation,表示跳过本层驱动的操作,直接转发至下层: IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(FDODeviceEx......
IRP完成APC执行函数IopCompleteRequest
IRP在完成时调用IoCompleteRequest,其最终会执行一个APC调用,该调用的函数名为IopCompleteRequest。其调用APC调用时的代码如下:KeInitializeApc(&Irp->Tail.Apc, &......
IRP完成与IO_STACK_LOCATION
IRP与IO_STACK_LOCATION的关系IPR完成时,有时为了获取数据,我们经常要这样干 IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine( Irp, Comple......
IRP无法取消的无限等待问题
有时有这样一种功能,就是当上层设备或应用和我们开发的驱动的时候,而我们这时根本无法完成本次的IRP,所以根据WINDOWS 驱动开发规范,我们这时需要对该IRP进行MarkPending,然后返回Pending。NTSTATUSMyDispatch( PDEVICE_OBJECT Dev......
IRP完成例程IoSetCompletionRoutine的设计和实现原理
在进行IRP下层传递时,通过上一节可知道,一种中使用IoCopyCurrentIrpStackLocationToNext,另一种是IoSkipCurrentIrpStackLocation。其中在使用IoCopyCurrentIrpStackLocationToNext表示的是对当前的IRP当留当......
IRP的取消机制IoCancelIrp
在驱动层,可以使用取消例程实现对IRP的取消。IRP的取消主要涉及几个函数,分别是:IoSetCancelRoutine 设置取消IRP例程IoCancelIrp函数看起来比较简单,但过程却是并不简单。REACTOS提供的IoCancelIrp的源代码如下:BOOLEAN NTAPI IoC......
Windows x64 IRP结构体成员偏移地址
4: kd> dt _IRPntdll!_IRP +0x000 Type : Int2B +0x002 Size : Uint2B +0x004 AllocationProcessorNumber : Uint2B +0......
PDO设备的动态创建与卸载IRP_MN_SURPRISE_REMOVAL和IRP_MN_REMOVE_DEVICE
总线驱动创建的PDO在卸载时,因卸载方式不同而不同如果是直接卸载总线,会只调用IRP_MN_REMOVE_DEVICE而如果是动态创建与卸载,在卸载的时候调用IoInvalidateDeviceRelations会导致IRP_MN_SURPRISE_REMOVAL的调用,然后调用IRP_MN_R......
挂起的IPR在卸载驱动中的处理
有时有一种情况,说是会将上层如CAMERA,HID等USB设备下发的IPR进行入链表LIST_ENTRY,当然在入之前,会将此IRP 挂起pending,并设置取消例程。不过在驱动卸载的时候,或者某种情况下需要手动清除这些IRP时,会出现和CANCEL例程竞争的情况。故这些需要进行一些判断再处理:......
IPR超时手动取消
手动发一个IRP下底层,可以设置超时时间,当超时后,可以手动取消该IRP.NTSTATUS HidpCallDriverSynchronous(IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp){ KEVENT event; NTST......
作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!