使用网环接收网络数据

当框架为接收队列调用其 EvtPacketQueueAdvance 回调函数时,NetAdapterCx 客户端驱动程序将接收网络数据。 在此回调期间,客户端驱动程序通过将收到的片段和数据包清空到 OS 来指示接收,然后将新缓冲区发布到硬件。

接收 (Rx) 后和排出操作概述

以下动画演示了适用于简单 PCI 网络接口的客户端驱动程序卡 (NIC) 如何对接收 (Rx) 队列执行后和排出操作。 此示例方案中的片段缓冲区由 OS 分配并附加到片段环。 此示例假定硬件接收队列与 OS 接收队列之间存在一对一关系。

演示 PCI 网络接口卡接收的网环后和排出操作的动画。

在此动画中,客户端驱动程序拥有的数据包以浅蓝色和深蓝色突出显示,客户端驱动程序拥有的片段以黄色和橙色突出显示。 浅色表示驱动程序拥有的元素的 排水子 部分,而较深的颜色表示驱动程序拥有的元素 的后 部分。

按顺序接收数据

下面是按顺序接收数据的驱动程序的典型序列,每个数据包有一个片段。

  1. 调用 NetRxQueueGetRingCollection 以检索接收队列的环形集合结构。 你可以将此数据存储在队列的上下文空间中,以减少驱动程序发出的调用。 使用环形集合检索接收队列的片段环和数据包环的排出迭代器。
  2. 通过清空网环指示接收的数据到 OS:
    1. 分配 UINT32 变量以跟踪片段环的当前索引和数据包环的当前索引。 将这些变量设置为其各自净环的 BeginIndex ,即环的漏极子节的开头。 通过将 UINT32 变量设置为片段环的 NextIndex,为片段环的排出部分的末尾分配一个 UINT32 变量。
    2. 在循环中执行以下操作:
      1. 检查硬件是否已收到片段。 如果没有,则中断循环。
      2. 调用 NetRingGetFragmentAtIndex 以获取片段。
      3. 根据匹配的硬件描述符填充片段的信息,例如其 ValidLength
      4. 通过调用 NetRingGetPacketAtIndex 获取此片段的数据包。
      5. 通过将数据包的 FragmentIndex 设置为片段环中片段的当前索引并适当地设置片段数, (在此示例中,将片段设置为 1) ,从而将片段绑定到数据包。
      6. (可选)填写任何其他数据包信息,例如校验和信息。
      7. 通过调用 NetRingIncrementIndex 推进片段索引。
      8. 通过调用 NetRingIncrementIndex 来推进数据包索引。
    3. 将片段环的 BeginIndex 更新为当前片段索引变量,并将数据包环的 BeginIndex 更新为当前数据包索引,以最终确定接收的数据包及其片段到 OS。
  3. 将片段缓冲区发布到硬件以供下一个接收:
    1. 将当前片段索引设置为片段环的 NextIndex,该索引是环形后部分的开头。 将片段结束索引设置为片段环的 EndIndex
    2. 在循环中执行以下操作:
      1. 将片段的信息发布到匹配的硬件描述符。
      2. 通过调用 NetRingIncrementIndex 推进片段索引。
    3. 将片段环的 NextIndex 更新为当前片段索引变量,以最终将片段发布到硬件。

以下步骤在代码中可能如下所示:

void
MyEvtRxQueueAdvance(
    NETPACKETQUEUE RxQueue
)
{
    //
    // Retrieve the receive queue's ring collection and net rings. 
    // This example stores the Rx queue's ring collection in its queue context space.
    //
    PMY_RX_QUEUE_CONTEXT rxQueueContext = MyGetRxQueueContext(RxQueue);
    NET_RING_COLLECTION const * ringCollection = rxQueueContext->RingCollection;
    NET_RING * packetRing = ringCollection->Rings[NET_RING_TYPE_PACKET];
    NET_RING * fragmentRing = ringCollection->Rings[NET_RING_TYPE_FRAGMENT];
    UINT32 currentPacketIndex = 0;
    UINT32 currentFragmentIndex = 0;
    UINT32 fragmentEndIndex = 0;

    //
    // Indicate receives by draining the rings
    //
    currentPacketIndex = packetRing->BeginIndex;
    currentFragmentIndex = fragmentRing->BeginIndex;
    fragmentEndIndex = fragmentRing->NextIndex;
    while(currentFragmentIndex != fragmentEndIndex)
    {
        // Test for fragment reception. Break if fragment has not been received.
        ...
        //

        NET_FRAGMENT * fragment = NetRingGetFragmentAtIndex(fragmentRing, currentFragmentIndex);
        fragment->ValidLength = ... ;
        NET_PACKET * packet = NetRingGetPacketAtIndex(packetRing, currentPacketIndex);
        packet->FragmentIndex = currentFragmentIndex;
        packet->FragmentCount = 1;

        if(rxQueueContext->IsChecksumExtensionEnabled)
        {
            // Fill in checksum info
            ...
            //
        }        

        currentFragmentIndex = NetRingIncrementIndex(fragmentRing, currentFragmentIndex);
        currentPacketIndex = NetRingIncrementIndex(packetRing, currentPacketIndex);
    }
    fragmentRing->BeginIndex = currentFragmentIndex;
    packetRing->BeginIndex = currentPacketIndex;

    //
    // Post fragment buffers to hardware
    //
    currentFragmentIndex = fragmentRing->NextIndex;
    fragmentEndIndex = fragmentRing->EndIndex;
    while(currentFragmentIndex != fragmentEndIndex)
    {
        // Post fragment information to hardware descriptor
        ...
        //

        currentFragmentIndex = NetRingIncrementIndex(fragmentRing, currentFragmentIndex);
    }
    fragmentRing->NextIndex = currentFragmentIndex;
}

无序接收数据

Tx 队列不同,如果每个硬件接收队列有一个 OS 接收队列,客户端驱动程序通常不会无序接收数据。 这与客户端驱动程序的 NIC 的类型无关。 无论设备是基于 PCI 的,操作系统分配并拥有接收缓冲区,还是设备基于 USB 且 USB 堆栈拥有接收缓冲区,客户端驱动程序都会为接收的每个片段初始化数据包并将其指示给 OS。 在这种情况下,顺序并不重要。

如果硬件支持每个硬件接收队列多个 OS 接收队列,则必须同步对接收缓冲区的访问。 这样做的范围不在本主题之外,取决于硬件的设计。