Winoows内核设计思想之IRP
+ -

IRP完成APC执行函数IopCompleteRequest

2021-07-16 158 1

IRP在完成时调用IoCompleteRequest,其最终会执行一个APC调用,该调用的函数名为IopCompleteRequest。

其调用APC调用时的代码如下:

KeInitializeApc(&Irp->Tail.Apc,
                         &Thread->Tcb,
                         Irp->ApcEnvironment,
                         IopCompleteRequest,
                         NULL,
                         NULL,
                         KernelMode,
                         NULL);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
                 FileObject,
                 DataBuffer,
                 PriorityBoost);

IopCompleteRequest对应的函数原型为

VOID
NTAPI
IopCompleteRequest(IN PKAPC Apc,
                   IN PKNORMAL_ROUTINE* NormalRoutine,
                   IN PVOID* NormalContext,
                   IN PVOID* SystemArgument1,
                   IN PVOID* SystemArgument2)
 {
     PFILE_OBJECT FileObject;
     PIRP Irp;
     PMDL Mdl, NextMdl;
     PVOID Port = NULL, Key = NULL;
     BOOLEAN SignaledCreateRequest = FALSE;

     /* Get data from the APC */
     FileObject = (PFILE_OBJECT)*SystemArgument1;
     Irp = CONTAINING_RECORD(Apc, IRP, Tail.Apc);
     IOTRACE(IO_IRP_DEBUG,
             "%s - Completing IRP %p for %p\n",
             __FUNCTION__,
             Irp,
             FileObject);

     /* Sanity check */
     ASSERT(Irp->IoStatus.Status != (NTSTATUS)0xFFFFFFFF);

     /* Check if we have a file object */
     if (*SystemArgument2)
     {
         /* Check if we're reparsing */
         if ((Irp->IoStatus.Status == STATUS_REPARSE) &&
             (Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT))
         {
             PREPARSE_DATA_BUFFER ReparseData;

             ReparseData = (PREPARSE_DATA_BUFFER)*SystemArgument2;

             ASSERT(ReparseData->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT);
             ASSERT(ReparseData->ReparseDataLength < MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
             ASSERT(ReparseData->Reserved < MAXIMUM_REPARSE_DATA_BUFFER_SIZE);

             IopDoNameTransmogrify(Irp, FileObject, ReparseData);
         }
     }

     /* Handle Buffered case first */
     if (Irp->Flags & IRP_BUFFERED_IO)
     {
         /* Check if we have an input buffer and if we succeeded */
         if ((Irp->Flags & IRP_INPUT_OPERATION) &&
             (Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) &&
             !(NT_ERROR(Irp->IoStatus.Status)))
         {
             _SEH2_TRY
             {
                 /* Copy the buffer back to the user */
                 RtlCopyMemory(Irp->UserBuffer,
                               Irp->AssociatedIrp.SystemBuffer,
                               Irp->IoStatus.Information);
             }
             _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
             {
                 /* Fail the IRP */
                 Irp->IoStatus.Status = _SEH2_GetExceptionCode();
             }
             _SEH2_END;
         }

         /* Also check if we should de-allocate it */
         if (Irp->Flags & IRP_DEALLOCATE_BUFFER)
         {
             /* Deallocate it */
             ExFreePool(Irp->AssociatedIrp.SystemBuffer);
         }
     }

     /* Now we got rid of these two... */
     Irp->Flags &= ~(IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER);

     /* Check if there's an MDL */
     for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
     {
         /* Free it */
         NextMdl = Mdl->Next;
         IoFreeMdl(Mdl);
     }

     /* No MDLs left */
     Irp->MdlAddress = NULL;

     /*
      * Check if either the request was completed without any errors
      * (but warnings are OK!), or if it was completed with an error, but
      * did return from a pending I/O Operation and is not synchronous.
      */
     if (!NT_ERROR(Irp->IoStatus.Status) ||
         (Irp->PendingReturned &&
          !IsIrpSynchronous(Irp, FileObject)))
     {
         /* Get any information we need from the FO before we kill it */
         if ((FileObject) && (FileObject->CompletionContext))
         {
             /* Save Completion Data */
             Port = FileObject->CompletionContext->Port;
             Key = FileObject->CompletionContext->Key;
         }

         /* Check for UserIos */
         if (Irp->UserIosb != NULL)
         {
             /* Use SEH to make sure we don't write somewhere invalid */
             _SEH2_TRY
             {
                 /*  Save the IOSB Information */
                 *Irp->UserIosb = Irp->IoStatus;
             }
             _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
             {
                 /* Ignore any error */
             }
             _SEH2_END;
         }

         /* Check if we have an event or a file object */
         if (Irp->UserEvent)
         {
             /* At the very least, this is a PKEVENT, so signal it always */
             KeSetEvent(Irp->UserEvent, 0, FALSE);

             /* Check if we also have a File Object */
             if (FileObject)
             {
                 /* Check if this is an Asynch API */
                 if (!(Irp->Flags & IRP_SYNCHRONOUS_API))
                 {
                     /* Dereference the event */
                     ObDereferenceObject(Irp->UserEvent);
                 }

                 /*
                  * Now, if this is a Synch I/O File Object, then this event is
                  * NOT an actual Executive Event, so we won't dereference it,
                  * and instead, we will signal the File Object
                  */
                 if ((FileObject->Flags & FO_SYNCHRONOUS_IO) &&
                     !(Irp->Flags & IRP_OB_QUERY_NAME))
                 {
                     /* Signal the file object and set the status */
                     KeSetEvent(&FileObject->Event, 0, FALSE);
                     FileObject->FinalStatus = Irp->IoStatus.Status;
                 }

                 /*
                  * This could also be a create operation, in which case we want
                  * to make sure there's no APC fired.
                  */
                 if (Irp->Flags & IRP_CREATE_OPERATION)
                 {
                     /* Clear the APC Routine and remember this */
                     Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
                     SignaledCreateRequest = TRUE;
                 }
             }
         }
         else if (FileObject)
         {
             /* Signal the file object and set the status */
             KeSetEvent(&FileObject->Event, 0, FALSE);
             FileObject->FinalStatus = Irp->IoStatus.Status;

             /*
             * This could also be a create operation, in which case we want
             * to make sure there's no APC fired.
             */
             if (Irp->Flags & IRP_CREATE_OPERATION)
             {
                 /* Clear the APC Routine and remember this */
                 Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
                 SignaledCreateRequest = TRUE;
             }
         }

         /* Update transfer count for everything but create operation */
         if (!(Irp->Flags & IRP_CREATE_OPERATION))
         {
             if (Irp->Flags & IRP_WRITE_OPERATION)
             {
                 /* Update write transfer count */
                 IopUpdateTransferCount(IopWriteTransfer,
                                        (ULONG)Irp->IoStatus.Information);
             }
             else if (Irp->Flags & IRP_READ_OPERATION)
             {
                 /* Update read transfer count */
                 IopUpdateTransferCount(IopReadTransfer,
                                        (ULONG)Irp->IoStatus.Information);
             }
             else
             {
                 /* Update other transfer count */
                 IopUpdateTransferCount(IopOtherTransfer,
                                        (ULONG)Irp->IoStatus.Information);
             }
         }

         /* Now that we've signaled the events, de-associate the IRP */
         IopUnQueueIrpFromThread(Irp);

         /* Now check if a User APC Routine was requested */
         if (Irp->Overlay.AsynchronousParameters.UserApcRoutine)
         {
             /* Initialize it */
             KeInitializeApc(&Irp->Tail.Apc,
                             KeGetCurrentThread(),
                             CurrentApcEnvironment,
                             IopFreeIrpKernelApc,
                             IopAbortIrpKernelApc,
                             (PKNORMAL_ROUTINE)Irp->
                             Overlay.AsynchronousParameters.UserApcRoutine,
                             Irp->RequestorMode,
                             Irp->
                             Overlay.AsynchronousParameters.UserApcContext);

             /* Queue it */
             KeInsertQueueApc(&Irp->Tail.Apc, Irp->UserIosb, NULL, 2);
         }
         else if ((Port) &&
                  (Irp->Overlay.AsynchronousParameters.UserApcContext))
         {
             /* We have an I/O Completion setup... create the special Overlay */
             Irp->Tail.CompletionKey = Key;
             Irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;
             KeInsertQueue(Port, &Irp->Tail.Overlay.ListEntry);
         }
         else
         {
             /* Free the IRP since we don't need it anymore */
             IoFreeIrp(Irp);
         }

         /* Check if we have a file object that wasn't part of a create */
         if ((FileObject) && !(SignaledCreateRequest))
         {
             /* Dereference it, since it's not needed anymore either */
             ObDereferenceObjectDeferDelete(FileObject);
         }
     }
     else
     {
         /*
          * Either we didn't return from the request, or we did return but this
          * request was synchronous.
          */
         if ((Irp->PendingReturned) && (FileObject))
         {
             /* So we did return with a synch operation, was it the IRP? */
             if (Irp->Flags & IRP_SYNCHRONOUS_API)
             {
                 /* Yes, this IRP was synchronous, so return the I/O Status */
                 *Irp->UserIosb = Irp->IoStatus;

                 /* Now check if the user gave an event */
                 if (Irp->UserEvent)
                 {
                     /* Signal it */
                     KeSetEvent(Irp->UserEvent, 0, FALSE);
                 }
                 else
                 {
                     /* No event was given, so signal the FO instead */
                     KeSetEvent(&FileObject->Event, 0, FALSE);
                 }
             }
             else
             {
                 /*
                  * It's not the IRP that was synchronous, it was the FO
                  * that was opened this way. Signal its event.
                  */
                 FileObject->FinalStatus = Irp->IoStatus.Status;
                 KeSetEvent(&FileObject->Event, 0, FALSE);
             }
         }

         /* Now that we got here, we do this for incomplete I/Os as well */
         if ((FileObject) && !(Irp->Flags & IRP_CREATE_OPERATION))
         {
             /* Dereference the File Object unless this was a create */
             ObDereferenceObjectDeferDelete(FileObject);
         }

         /*
          * Check if this was an Executive Event (remember that we know this
          * by checking if the IRP is synchronous)
          */
         if ((Irp->UserEvent) &&
             (FileObject) &&
             !(Irp->Flags & IRP_SYNCHRONOUS_API))
         {
             /* This isn't a PKEVENT, so dereference it */
             ObDereferenceObject(Irp->UserEvent);
         }

         /* Now that we've signaled the events, de-associate the IRP */
         IopUnQueueIrpFromThread(Irp);

         /* Free the IRP as well */
         IoFreeIrp(Irp);
     }
 }

该函数IopCompleteRequest首先判断是否含有文件对象,如果有则执行相应的操作。

后续判断该IRP是否是IRP_BUFFERED_IO操作,即缓存操作,这里由于已经在当前进程中,可以直接进行内存拷贝,将内核中的数据拷贝到应用层缓冲区中。对应的代码为:

  RtlCopyMemory(Irp->UserBuffer,
                Irp->AssociatedIrp.SystemBuffer,
                Irp->IoStatus.Information);

所以我们在IRP操作时可以根据Irp->UserBuffer知道期用户层的内存地址。

后续紧跟着的是判断该IRP的IO操作是否为异步操作,如果是,获取用户层绑定的完成端口操作。
获取用户层的等待事件,并置事件有效。

 if (Irp->UserEvent)
{
    /* Signal it */
    KeSetEvent(Irp->UserEvent, 0, FALSE);
}
else
{
    /* No event was given, so signal the FO instead */
    KeSetEvent(&FileObject->Event, 0, FALSE);
}

后面有一个关于文件读写操作的操作,见代码:

  if (!(Irp->Flags & IRP_CREATE_OPERATION))
 {
     if (Irp->Flags & IRP_WRITE_OPERATION)
     {
         /* Update write transfer count */
         IopUpdateTransferCount(IopWriteTransfer,
                                (ULONG)Irp->IoStatus.Information);
     }
     else if (Irp->Flags & IRP_READ_OPERATION)
     {
         /* Update read transfer count */
         IopUpdateTransferCount(IopReadTransfer,
                                (ULONG)Irp->IoStatus.Information);
     }
     else
     {
         /* Update other transfer count */
         IopUpdateTransferCount(IopOtherTransfer,
                                (ULONG)Irp->IoStatus.Information);
     }
 }

我们可以分析一下IopUpdateTransferCount的代码,这里统计了当前进程和整个系统数据的读写数量。

VOID
IopUpdateTransferCount(IN IOP_TRANSFER_TYPE Type, IN ULONG TransferCount)
{
    PLARGE_INTEGER CountToChange;
    PLARGE_INTEGER TransferToChange;

    /* Make sure I/O operations are being counted */
    if (IoCountOperations)
    {
        if (Type == IopReadTransfer)
        {
            /* Increase read count */
            CountToChange = &PsGetCurrentProcess()->ReadTransferCount;
            TransferToChange = &IoReadTransferCount;
        }
        else if (Type == IopWriteTransfer)
        {
            /* Increase write count */
            CountToChange = &PsGetCurrentProcess()->WriteTransferCount;
            TransferToChange = &IoWriteTransferCount;
        }
        else
        {
            /* Increase other count */
            CountToChange = &PsGetCurrentProcess()->OtherTransferCount;
            TransferToChange = &IoOtherTransferCount;
        }

        /* Increase the process-wide count */
        ExInterlockedAddLargeStatistic(CountToChange, TransferCount);

        /* Increase global count */
        ExInterlockedAddLargeStatistic(TransferToChange, TransferCount);
    }
}

后续又是根据该操作是否绑有相应的用户层APC操作而执行。
当然,以上都是异步的,对于同步的和异步的类似,只是代码相对比较简单而已。

回过头来看,对于IRP的完成,如果仅是设备层,相对比较简单,但如果是文件或内存分页倒换的操作,就会变地比较复杂很多。该IRP完成中,后续大部分都是处理关于文件操作的处理。

0 篇笔记 写笔记

IRP的完成IoCompleteRequest
每当一个IRP在下层设备层完成时,是需要调用IoCompleteRequest来实现IRP的完成,这个完成其实是实现对执行的IRP的善后操作,这个操作其实是一个宏,真实函数数是IofCompleteRequest。#define IoCompleteRequest IofCompleteReque......
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, &......
作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

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

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