深度剖析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);
 

你可能感兴趣的:(职场,休闲,winpcap)