使用网环接收网络数据
当框架为接收队列调用其 EvtPacketQueueAdvance 回调函数时,NetAdapterCx 客户端驱动程序将接收网络数据。 在此回调期间,客户端驱动程序通过将收到的片段和数据包清空到 OS 来指示接收,然后将新缓冲区发布到硬件。
接收 (Rx) 后和排出操作概述
以下动画演示了适用于简单 PCI 网络接口的客户端驱动程序卡 (NIC) 如何对接收 (Rx) 队列执行后和排出操作。 此示例方案中的片段缓冲区由 OS 分配并附加到片段环。 此示例假定硬件接收队列与 OS 接收队列之间存在一对一关系。
在此动画中,客户端驱动程序拥有的数据包以浅蓝色和深蓝色突出显示,客户端驱动程序拥有的片段以黄色和橙色突出显示。 浅色表示驱动程序拥有的元素的 排水子 部分,而较深的颜色表示驱动程序拥有的元素 的后 部分。
按顺序接收数据
下面是按顺序接收数据的驱动程序的典型序列,每个数据包有一个片段。
- 调用 NetRxQueueGetRingCollection 以检索接收队列的环形集合结构。 你可以将此数据存储在队列的上下文空间中,以减少驱动程序发出的调用。 使用环形集合检索接收队列的片段环和数据包环的排出迭代器。
- 通过清空网环指示接收的数据到 OS:
- 分配 UINT32 变量以跟踪片段环的当前索引和数据包环的当前索引。 将这些变量设置为其各自净环的 BeginIndex ,即环的漏极子节的开头。 通过将 UINT32 变量设置为片段环的 NextIndex,为片段环的排出部分的末尾分配一个 UINT32 变量。
- 在循环中执行以下操作:
- 检查硬件是否已收到片段。 如果没有,则中断循环。
- 调用 NetRingGetFragmentAtIndex 以获取片段。
- 根据匹配的硬件描述符填充片段的信息,例如其 ValidLength。
- 通过调用 NetRingGetPacketAtIndex 获取此片段的数据包。
- 通过将数据包的 FragmentIndex 设置为片段环中片段的当前索引并适当地设置片段数, (在此示例中,将片段设置为 1) ,从而将片段绑定到数据包。
- (可选)填写任何其他数据包信息,例如校验和信息。
- 通过调用 NetRingIncrementIndex 推进片段索引。
- 通过调用 NetRingIncrementIndex 来推进数据包索引。
- 将片段环的 BeginIndex 更新为当前片段索引变量,并将数据包环的 BeginIndex 更新为当前数据包索引,以最终确定接收的数据包及其片段到 OS。
- 将片段缓冲区发布到硬件以供下一个接收:
- 将当前片段索引设置为片段环的 NextIndex,该索引是环形后部分的开头。 将片段结束索引设置为片段环的 EndIndex。
- 在循环中执行以下操作:
- 将片段的信息发布到匹配的硬件描述符。
- 通过调用 NetRingIncrementIndex 推进片段索引。
- 将片段环的 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 接收队列,则必须同步对接收缓冲区的访问。 这样做的范围不在本主题之外,取决于硬件的设计。