驱动开发之五 --- TDI之九 【译文】

驱动开发之五 --- TDI之九 【译文】

排队和悬而未决

你有权选择排队IRP, 在迟些的时间作处理或者在其他的线程处理。既然你拥有IRP, 当它在你的驱动栈层时,这是被允许的。你需要考虑IRP可以被取消的情况。问题是如果IRP被取消,既然结果将会被扔在一边,你真的不想执行任何处理?另一个问题是我们想要解决的是,如果一个有活动的IRP,他们和一个进程或者线程关联,直到活动的IRP完成,这个进程或者线程才能被完全终止。这是非常狡猾的,对于如何实现,这方面的文档非常少,在这里,我们将给你展示如何实现这些。

夺取你的锁

你需要做的第一件事情就是获取自旋锁保护你的IRP链,这将有助于在你的排队逻辑和取消例程之间同步执行。如果你使用某个系统提供的队列机制,有一种系统的取消自旋锁可以被获取,在一些情况下,是必须的。然而既然取消自旋锁是系统范围的,你所想的也是差不多接近的吗?另外的处理器会获取你的自旋锁,或者获取取消自旋锁吗?最有可能的是它会结束获取取消自旋锁,并且这将成为一种性能打击,在单处理器的机器上,很明显不需要关心你使用哪个自旋锁,但是你应该尝试去实现自己的自旋锁。

设置取消例程

你的取消例程需要获取你的自旋锁来同步执行或者从链中删除IRP. 设置取消例程,如果这个IRP被取消,你会知道并且可以从你的IRP列表中删除。记住,你仍然必须完成这个IRP。

没有其他办法。如果一个IRP被取消,他不会刚好从外面在你的脚下消失。如果是那样,当你在处理这个IRP时,如果它被取消,你会处于巨大的麻烦中。取消例程的作用是当IRP在队列中时,如果取消IRP时没有任何争抢,在任何时候你都可以把它从队列中删除。

检查取消标志

然后你必须检查IRP中的取消标志,如果它没有被取消,你需要调用IoMarkIrpPending以及将IRP排队到你的链表,无论哪种办法,你必须确信从你的驱动中返回了STATUS_PENDING。

如果它已经被取消,我们需要知道它是否调用了你的取消例程。通过把取消例程设置为NULL我们可以做到这点。如果返回值是NULL,那么取消例程被调用。如果返回值非NULL,那么取消例程没有被调用。这意味着在我们设置取消例程之前,它已经被取消。

你现在有两个选择,记住,仅有一个位置可以完成这个IRP. 如果取消例程被调用,那么在取消例程没有完成这个IRP时, 如果它不在你的IRP链中,那么你就可以释放它。如果取消例程已经完成了它,那么你一定不能完成它。如果取消例程没有被调用,那么你必须显式的完成它。不管发生什么事,你一定要记住两件事。第一件事是在你的驱动中某个地方,你必须要完成这个IRP. 第二件事情是记得一定不要完成两次。

同样的事情,当你从IRP链中删除一个IRP时,你需要检查这个IRP是否已经被取消。在删除IRP之前,你也要设置取消例程为NULL. 看下面的代码。

Irp->Tail.Overlay.DriverContext[0] =

         (PVOID)pTdiExampleContext->pWriteIrpListHead;

NtStatus = HandleIrp_AddIrp(pTdiExampleContext->pWriteIrpListHead,

         Irp, TdiExample_CancelRoutine, TdiExample_IrpCleanUp, NULL);

if(NT_SUCCESS(NtStatus))

{

    KeSetEvent(&pTdiExampleContext->kWriteIrpReady,

                                  IO_NO_INCREMENT, FALSE);

    NtStatus = STATUS_PENDING;

}

...


/**********************************************************************

*

* HandleIrp_AddIrp

*

*    This function adds an IRP to the IRP List.

*

**********************************************************************/

NTSTATUS HandleIrp_AddIrp(PIRPLISTHEAD pIrpListHead,

                          PIRP pIrp,

                          PDRIVER_CANCEL pDriverCancelRoutine,

                          PFNCLEANUPIRP pfnCleanUpIrp,

                          PVOID pContext)

{

    NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;

    KIRQL kOldIrql;

    PDRIVER_CANCEL pCancelRoutine;

    PIRPLIST pIrpList;

    pIrpList = (PIRPLIST)KMem_AllocateNonPagedMemory(sizeof(IRPLIST),

                                            pIrpListHead->ulPoolTag);

    if(pIrpList)

    {

        DbgPrint("HandleIrp_AddIrp Allocate Memory = 0x%0x \r\n", pIrpList);

        pIrpList->pContext      = pContext;

        pIrpList->pfnCleanUpIrp = pfnCleanUpIrp;

        pIrpList->pIrp          = pIrp;

        pIrpList->pfnCancelRoutine = pDriverCancelRoutine;

        /*

         * The first thing we need to to is acquire our spin lock.

         *

         * The reason for this is a few things.

         *

         *     1. All access to this list is synchronized, the obvious reason

         *     2. This will synchronize adding this IRP to the

         *             list with the cancel routine.

         */

    

        KeAcquireSpinLock(&pIrpListHead->kspIrpListLock, &kOldIrql);

    

        /*

         * We will now attempt to set the cancel routine which will be called

         * when (if) the IRP is ever canceled. This allows us to remove an IRP

         * from the queue that is no longer valid.

         *

         * A potential misconception is that if the IRP is canceled it is no

         * longer valid. This is not true the IRP does not self-destruct.  

         * The IRP is valid as long as it has not been completed. Once it

         * has been completed this is when it is no longer valid (while we

         * own it). So, while we own the IRP we need to complete it at some

         * point. The reason for setting a cancel routine is to realize

         * that the IRP has been canceled and complete it immediately and

         * get rid of it. We don't want to do processing for an IRP that

         * has been canceled as the result will just be thrown away.

         *

         * So, if we remove an IRP from this list for processing and

         * it's canceled the only problem is that we did processing on it.  

         * We complete it at the end and there's no problem.

         *

         * There is a problem however if your code is written in a way

         * that allows your cancel routine to complete the IRP unconditionally.

         * This is fine as long as you have some type of synchronization

         * since you DO NOT WANT TO COMPLETE AN IRP TWICE!!!!!!

         */

    

        IoSetCancelRoutine(pIrp, pIrpList->pfnCancelRoutine);

    

        /*

         * We have set our cancel routine. Now, check if the IRP has

         *                         already been canceled.

         * We must set the cancel routine before checking this to ensure

         * that once we queue the IRP it will definately be called if the

         * IRP is ever canceled.

         */

    

        if(pIrp->Cancel)

        {

            /*

             * If the IRP has been canceled we can then check if our

             * cancel routine has been called.

             */

            pCancelRoutine = IoSetCancelRoutine(pIrp, NULL);

    

            /*

             * if pCancelRoutine ==

             *             NULL then our cancel routine has been called.

             * if pCancelRoutine !=

             *              NULL then our cancel routine has not been called.

             *

             * The I/O Manager will set the cancel routine to NULL

             * before calling the cancel routine.

             * We have a decision to make here, we need to write the code

             * in a way that we only complete and clean up the IRP once.  

             * We either allow the cancel routine to do it or we do it here.

             * Now, we will already have to clean up the IRP here if the

             * pCancelRoutine != NULL.

             *

             * The solution we are going with here is that we will only clean

             * up IRP's in the cancel routine if the are in the list.  

             * So, we will not add any IRP to the list if it has

             * already been canceled once we get to this location.

             *

             */

            KeReleaseSpinLock(&pIrpListHead->kspIrpListLock, kOldIrql);

    

            /*

             * We are going to allow the clean up function to complete the IRP.

             */

            pfnCleanUpIrp(pIrp, pContext);

            DbgPrint("HandleIrp_AddIrp Complete Free Memory = 0x%0x \r\n",

                                                          pIrpList);

            KMem_FreeNonPagedMemory(pIrpList);

        }

        else

        {

            /*

             * The IRP has not been canceled, so we can simply queue it!

             */

            pIrpList->pNextIrp      = NULL;

            

            IoMarkIrpPending(pIrp);

            if(pIrpListHead->pListBack)

            {

               pIrpListHead->pListBack->pNextIrp = pIrpList;

               pIrpListHead->pListBack           = pIrpList;

            }

            else

            {

               pIrpListHead->pListFront = pIrpListHead->pListBack =

                                                                pIrpList;

            }

            KeReleaseSpinLock(&pIrpListHead->kspIrpListLock,

                                                            kOldIrql);

            NtStatus = STATUS_SUCCESS;

        }

    }

    else

    {

        /*

         * We are going to allow the clean up function to complete the IRP.

         */

        pfnCleanUpIrp(pIrp, pContext);

    }

    

    return NtStatus;      

}

你可能感兴趣的:(驱动开发学习)