深度剖析WinPcap之(九)——數據包的發送過程(11)

NPF_Write函數中主要調用NdisSend函數完成數據包的底層發送。NdisSend函數的原型如下:

VOID  NdisSend(  

OUT PNDIS_STATUS  Status,    

IN NDIS_HANDLE  NdisBindingHandle,    

IN PNDIS_PACKET  Packet
    );

參數Status指向一個調用着提供的變量,儲存函數返回的狀態。底層驅動決定所返回的NDIS_STATUS_XXX,通常爲下列值。

NDIS_STATUS_SUCCESS

給定的數據包已在網絡上傳輸。

NDIS_STATUS_PENDING

數據包的請求被異步操作,傳輸結束後調用者的ProtocolSendComplete函數將被調用

NDIS_STATUS_INVALID_PACKET

請求傳輸的大小對NIC太大,或者可能NIC指出一個錯誤數據包傳輸給了驅動程序

NDIS_STATUS_CLOSING

底層驅動程序已關閉

NDIS_STATUS_RESET_IN_PROGRESS

底層驅動當前正在復位NIC

NDIS_STATUS_FAILURE

返回一個不是特定描述的失敗,如果不是上述NDIS_STATUS_XXX的狀態,則返回該狀態。

 

特定的NDIS_STATUS_XXX返回在一個傳輸操作中設備的I/O錯誤, 依賴於NIC的特性與NIC驅動程序寫函數的判斷力。例如,一個微端口驅動程序可能返回NDIS_STATUS_NO_CABLE,如果它的NIC爲驅動程序指明瞭這種情況。

參數NdisBindingHandle 描述了NdisOpenAdapter返回的句柄,描述了目標NIC或與調用者所綁定的下一底層驅動程序的虛擬適配器。

參數Packet指向調用者所提供的數據包描述,由NdisAllocatePacket分配, 其把底層NIC驅動應該傳輸到網線上的數據進行鏈裝。

1.8.1.2             NPF_SendComplete函數

只要NdisSend函數返回NDIS_STATUS_PENDING狀態,驅動程序的ProtocolSendComplete函數(此處爲NPF_SendComplete函數)在數據包被髮送後就被調用。ProtocolSendComplete 對一個完成的傳輸操作執行任何必要的後處理,諸如提示最初的請求發送已經完成。NPF_SendComplete函數原型如下:

VOID NPF_SendComplete(

IN NDIS_HANDLE   ProtocolBindingContext,

IN PNDIS_PACKET  pPacket,

IN NDIS_STATUS   Status

)

參數ProtocolBindingContext 描述一個協議驅動分配的上下文的句柄,驅動程序調用NdisOpenAdapter獲得該句柄。參數pPacket 指向協議驅動提供的已完成發送的數據包的描述符。參數Status描述了發送操作的最終狀態。

NPF_SendComplete函數針對NPF_BufferedWriteNPF_Write()作不同的處理。針對NPF_Write操作,如果發送數據包緩衝池中空閒數據包不少於一半,就給出數據包可寫入的事件通知,同時檢測如果待發數據包爲0 ,那麼產生數據包發送完畢的事件通知。並釋放各種必要的資源、遞減掛起待發數據包的數目。針對NPF_BufferedWrite操作,就給出數據包可寫入的事件通知,並釋放各種必要的資源、遞減掛起待發數據包的數目。
VOID NPF_SendComplete(

                 IN NDIS_HANDLE   ProtocolBindingContext,

                 IN PNDIS_PACKET  pPacket,

                 IN NDIS_STATUS   Status

                 )                

{

    POPEN_INSTANCE  Open;

    PMDL TmpMdl; 

   

    Open= (POPEN_INSTANCE)ProtocolBindingContext;

 

    if( RESERVED(pPacket)->FreeBufAfterWrite )

    {

       /* 數據包由NPF_BufferedWrite函數發送*/

      

       //釋放與數據包關聯的MDL

       NdisUnchainBufferAtFront(pPacket, &TmpMdl);

       IoFreeMdl(TmpMdl);

       NdisFreePacket(pPacket);

 

       //遞減掛起待發數據包的數目

       InterlockedDecrement(&Open->Multiple_Write_Counter);

 

       NdisSetEvent(&Open->WriteEvent);

      

       return;

    }

    else

    {

       /*數據包由NPF_Write函數發送*/

 

       //遞減掛起待發數據包的數目

       ULONG stillPendingPackets =

InterlockedDecrement(&Open->TransmitPendingPackets);

       //把該數據包劃回釋放列表中

       NdisFreePacket(pPacket);

       //如果提交給NdisSend並且未得到確認的數據包個數

//低於TX數據包緩衝池中數據包的一半,

//將喚醒任意正在等待TX數據包緩衝池中具有可用數據包空間的發送者。

 

       if (stillPendingPackets < TRANSMIT_PACKETS/2)

       {

           NdisSetEvent(&Open->WriteEvent);

       }

       else

       {

           //否則,復位該事件,因此我們確認

//NPF_Write最終將阻塞去等待TX數據包緩衝池中數據包的可用性

           NdisResetEvent(&Open->WriteEvent);

       }

 

       if(stillPendingPackets == 0)

       {

//當所有數據包被NdisSend成功發送後

//產生NdisWriteCompleteEvent事件通知

           NdisSetEvent(&Open->NdisWriteCompleteEvent);

       }

 

       return;

    }

   

}

NPF_WriteNPF_SendComplete兩函數中注意Open->TransmitPendingPacketsOpen->WriteEventOpen->NdisWriteCompleteEvent的改變。

    NPF_Write函數在進入重複發送的while循環前,代碼行Open->TransmitPendingPackets = 0設置掛起並等待發送完成的數據包個數爲0,進入循環後,在調用NdisSend函數發送數據包前,代碼行InterlockedIncrement(&Open->TransmitPendingPackets)原子增加掛起待發數據包的個數;在NPF_SendComplete函數中代碼行ULONG stillPendingPackets = InterlockedDecrement(&Open->TransmitPendingPackets)原子減少掛起待發數據包的個數。

NPF_Write函數在進入重複發送的while循環前,代碼行NdisResetEvent(&Open->WriteEvent)復位WriteEvent 事件,進入循環後,調用NdisAllocatePacket函數,如果發送數據包緩衝池中沒有空閒數據包可用,需要等待一段時間,通過NdisWaitEvent(&Open->WriteEvent,1)代碼行等待NPF_SendComplete完成句柄函數中發送的事件通知。在NPF_SendComplete中,如果發送數據包緩衝池中空閒數據包不少於一半,將通過NdisSetEvent(&Open->WriteEvent)代碼行發送事件通知,否則代碼行NdisResetEvent(&Open->WriteEvent)復位該事件。

NPF_Write函數檢查如果可以進行寫操作,代碼行NdisResetEvent(&Open->NdisWriteCompleteEvent)復位NdisWriteCompleteEvent事件;進入循環後,在每次調用NdisSend函數發送數據包前,代碼行NdisResetEvent(&Open->NdisWriteCompleteEvent)復位該事件;循環結束後代碼行NdisWaitEvent(&Open->NdisWriteCompleteEvent, 0)等待數據包發送完畢。在NPF_SendComplete函數中代碼

if(stillPendingPackets == 0)

    {

       NdisSetEvent(&Open->NdisWriteCompleteEvent);

    }

檢測如果待發數據包爲0 ,那麼產生NdisWriteCompleteEvent事件通知數據包發送完畢。NPF_Write函數等待結束。

    注意自旋鎖的使用,先看NPF_Write後面的簡單使用

NdisAcquireSpinLock(&Open->WriteLock);

    Open->WriteInProgress = FALSE;

    NdisReleaseSpinLock(&Open->WriteLock);

自旋鎖Open->WriteLock保護Open->WriteInProgress變量訪問的唯一性,自選所得使用必須配對使用,否則導致死鎖,下面是較複雜的使用:

NdisAcquireSpinLock(&Open->WriteLock);

    if(Open->WriteInProgress)

    {

       NdisReleaseSpinLock(&Open->WriteLock);

      

       return STATUS_UNSUCCESSFUL;

    }

    else

    {

       Open->WriteInProgress = TRUE;

       NdisResetEvent(&Open->NdisWriteCompleteEvent);

    }

    NdisReleaseSpinLock(&Open->WriteLock);

 

本文出自 “千江月” 博客,請務必保留此出處http://eslxf.blog.51cto.com/918801/217016

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章