Winoows内核设计思想之IRP
+ -

IoCopyCurrentIrpStackLocationToNext和IoSkipCurrentIrpStackLocation操作的IO_STACK_LOCATION有什么区别

2021-07-14 337 0

在Windows驱动中,传递IPR一般有两种操作:
一种是调用IoSkipCurrentIrpStackLocation,表示跳过本层驱动的操作,直接转发至下层:

 IoSkipCurrentIrpStackLocation(Irp);
 return IoCallDriver(FDODeviceExtension->NextDeviceObject, Irp);

另一种是我们对IPR完成时,对完成情况感兴趣,所以需要本层的驱动栈保留,使用IoSetCompletionRoutine设置完成例程(可选择)。这个保留的驱动栈一般用于当IRP完成时回调函数的调用。
如我们需要知道下层驱动已经正确的完成了IPR。

    KEVENT event;
    KeInitializeEvent(&event, NotificationEvent, FALSE);

    IoCopyCurrentIrpStackLocationToNext(Irp);
    IoSetCompletionRoutine(Irp,
        (PIO_COMPLETION_ROUTINE)IrpCompletionRoutine,
        (PVOID) & event,
        TRUE,
        TRUE,
        TRUE);

    status = IoCallDriver(DeviceObject, Irp);
    if (status == STATUS_PENDING)
    {
        KeWaitForSingleObject(&event,
            Executive,
            KernelMode,
            FALSE,
            NULL);

        status = Irp->IoStatus.Status;
    }

上面的说法都是比较笼统,对于明白的人是真的明白,但对于不明白的人是看了很多编都不清楚。这里们进行详细的解释.

我们知道在创建一个设备时,特别是PDO时,是需要设置一个叫做栈大小的参数。

 PDODeviceObject->StackSize = DeviceObject->StackSize + 1;

这个参数一般应用于需要IPR调用时申请IRP的IoAllocateIrp函数的使用

 Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
 if (!Irp)
 {
     /* No memory */
     return STATUS_INSUFFICIENT_RESOURCES;
 }

IoAllocateIr会调用IoInitializeIrp进行IRP的分初始化。初始化的方法为:

VOID NTAPI IoInitializeIrp    (    IN PIRP     Irp,
IN USHORT     PacketSize,
IN CCHAR     StackSize 
)    
{
     /* Clear it */
     IOTRACE(IO_IRP_DEBUG,
             "%s - Initializing IRP %p\n",
             __FUNCTION__,
             Irp);
     RtlZeroMemory(Irp, PacketSize);

     /* Set the Header and other data */
     Irp->Type = IO_TYPE_IRP;
     Irp->Size = PacketSize;
     Irp->StackCount = StackSize;
     Irp->CurrentLocation = StackSize + 1;
     Irp->ApcEnvironment =  KeGetCurrentThread()->ApcStateIndex;
     Irp->Tail.Overlay.CurrentStackLocation = (PIO_STACK_LOCATION)(Irp + 1) + StackSize;

     /* Initialize the Thread List */
     InitializeListHead(&Irp->ThreadListEntry);
 }

这里看到最顶层的设备对应的栈顶,和栈的概念类似。

这样,创建的IRP会有StackSize个IO_STACK_LOCATION,该设备(DEVICE_OBJECT)和一层的设备都有一个私有的IO_STACK_LOCATION。但这种对应关系不是惟一确定的,是可以通过相关的IRP STATCK_LOCATION函数操作跳过该设备对应的STACK_LOCATION的,其中一个典型的函数就是IoSkipCurrentIrpStackLocation

FORCEINLINE
VOID
IoSkipCurrentIrpStackLocation (
    _Inout_ PIRP Irp
)
{
   NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount);
    Irp->CurrentLocation++;
    Irp->Tail.Overlay.CurrentStackLocation++;
}

使用此函数将放弃本层STATCK_LOCATION,这样先将STATCK_LOCATION+1,然后立即调用IoCallDriver让其减1。这样一加一减实现利当前驱动栈层给下层设备应用。
以下为ReactOS提供的IoCallDriver源代码

#define IoCallDriver(a,b)   \
        IofCallDriver(a,b)


NTSTATUS FASTCALL IofCallDriver    (    
IN PDEVICE_OBJECT     DeviceObject,
IN PIRP     Irp 
)    
{
     PDRIVER_OBJECT DriverObject;
     PIO_STACK_LOCATION StackPtr;

     /* Make sure this is a valid IRP */
     ASSERT(Irp->Type == IO_TYPE_IRP);

     /* Get the Driver Object */
     DriverObject = DeviceObject->DriverObject;

     /* Decrease the current location and check if */
     Irp->CurrentLocation--;
     if (Irp->CurrentLocation <= 0)
     {
         /* This IRP ran out of stack, bugcheck */
         KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0);
     }

     /* Now update the stack location */
     StackPtr = IoGetNextIrpStackLocation(Irp);
     Irp->Tail.Overlay.CurrentStackLocation = StackPtr;

     /* Get the Device Object */
     StackPtr->DeviceObject = DeviceObject;

     /* Call it */
     return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject,
                                                                 Irp);
 }

而我们一般在FDO中,是需要调用当前的STACK_LOCATION的,如以下代码

    PFDO_DEVICE_EXTENSION FDODeviceExtension;
    IoStack = IoGetCurrentIrpStackLocation(Irp);

其中IoGetCurrentIrpStackLocation的源代码如下:

FORCEINLINE
__drv_aliasesMem
PIO_STACK_LOCATION
IoGetCurrentIrpStackLocation(
    _In_ PIRP Irp
)
{
    NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount + 1);
    return Irp->Tail.Overlay.CurrentStackLocation;
}

回过头来我们再说IoCopyCurrentIrpStackLocationToNext函数。

FORCEINLINE
VOID
IoCopyCurrentIrpStackLocationToNext(
    _Inout_ PIRP Irp
)
{
    PIO_STACK_LOCATION irpSp;
    PIO_STACK_LOCATION nextIrpSp;
    irpSp = IoGetCurrentIrpStackLocation(Irp);
    nextIrpSp = IoGetNextIrpStackLocation(Irp);
    RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
    nextIrpSp->Control = 0;
}

这个函数是将当前STACK_LOCATION中除完成例程CompletionRoutine以上的所有参数复制到下层STACK_LOCATION中,这样就实现了STACK_LOCATION的值传递。
由此可见,要进行IPR的下发调用IoCallDriver,是需要IoSkipCurrentIrpStackLocation和IoSkipCurrentIrpStackLocation操作的,只是一个不再保留该设备层的驱动栈,一个保留该层的设备栈(主要是用于完成例程)

最后,我们再看一下IoCompleteRequest的实现。

#define IoCompleteRequest(a,b)  \
        IofCompleteRequest(a,b)

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);
         }
     }
 }

该函数IoCompleteRequest是实现的的是IRP完成,所以这里需要对所有的设备栈进行全部回溯,并调用其完成例程。这里 的代码是从函数地第50行开始的。
这里我们注意99行的代码:

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;

当完成返回STATUS_MORE_PROCESSING_REQUIRED时,将不再调用后续的完成例程,并直接返回。有兴趣的同学可详见STATUS_MORE_PROCESSING_REQUIRED和STATUS_CONTINUE_COMPLETION的区别。

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, &......
IRP完成例程IoSetCompletionRoutine的设计和实现原理
在进行IRP下层传递时,通过上一节可知道,一种中使用IoCopyCurrentIrpStackLocationToNext,另一种是IoSkipCurrentIrpStackLocation。其中在使用IoCopyCurrentIrpStackLocationToNext表示的是对当前的IRP当留当......
IRP调用IoSkipCurrentIrpStackLocationIoCopyCurrentIrpStackLocationToNext的区别
当IPR需要传递给下层设备时,一般的操作有两种。第一种是直接下传: IoCopyCurrentIrpStackLocationToNext(irp); status = IoCallDriver(parentFdoExt->fdo, irp);这种情况下是保留当前驱动栈对该IRP的IO_......
作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

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

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