概述
NetAdapterCx 驱动程序中的传输和接收队列允许客户端驱动程序在软件中为硬件功能(如硬件传输和接收队列)建模。 本文介绍如何实现和管理这些队列,以优化 NetAdapterCx 中的数据路径性能。
当您的客户端驱动程序调用 NET_ADAPTER_DATAPATH_CALLBACKS_INIT(通常是从其 EVT_WDF_DRIVER_DEVICE_ADD 事件回调函数)时,它提供两个队列创建回调函数:EVT_NET_ADAPTER_CREATE_TXQUEUE 和 EVT_NET_ADAPTER_CREATE_RXQUEUE。 客户端分别在这些回调中创建传输和接收队列。
在转换为低功率状态之前,框架会清空队列,并在删除适配器之前将其删除。
创建数据包队列
创建数据包队列(传输队列或接收队列)时,客户端必须提供指向以下三个回调函数的指针:
此外,客户端可以在初始化队列配置结构后提供这些可选的回调函数:
创建传输队列
NetAdapterCx 在启动序列的末尾调用EVT_NET_ADAPTER_CREATE_TXQUEUE。 在此回调期间,客户端驱动程序通常执行以下作:
- (可选)注册队列的启动和停止回调。
- 调用 NetTxQueueInitGetQueueId 以检索要设置的传输队列的标识符。
- 调用 NetTxQueueCreate 以分配队列。
- 如果 NetTxQueueCreate 失败,EvtNetAdapterCreateTxQueue 回调函数应返回错误代码。
- 查询数据包扩展偏移量。
以下示例演示了这些步骤在代码中的外观。 为清楚起见,将省略示例中的错误处理代码。
NTSTATUS
EvtAdapterCreateTxQueue(
_In_ NETADAPTER Adapter,
_Inout_ NETTXQUEUE_INIT * TxQueueInit
)
{
NTSTATUS status = STATUS_SUCCESS;
// Prepare the configuration structure
NET_PACKET_QUEUE_CONFIG txConfig;
NET_PACKET_QUEUE_CONFIG_INIT(
&txConfig,
EvtTxQueueAdvance,
EvtTxQueueSetNotificationEnabled,
EvtTxQueueCancel);
// Optional: register the queue's start and stop callbacks
txConfig.EvtStart = EvtTxQueueStart;
txConfig.EvtStop = EvtTxQueueStop;
// Get the queue ID
const ULONG queueId = NetTxQueueInitGetQueueId(TxQueueInit);
// Create the transmit queue
NETPACKETQUEUE txQueue;
status = NetTxQueueCreate(
TxQueueInit,
&txAttributes,
&txConfig,
&txQueue);
// Get the queue context for storing the queue ID and packet extension offset info
PMY_TX_QUEUE_CONTEXT queueContext = GetMyTxQueueContext(txQueue);
// Store the queue ID in the context
queueContext->QueueId = queueId;
// Query checksum packet extension offset and store it in the context
NET_EXTENSION_QUERY extension;
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_CHECKSUM_NAME,
NET_PACKET_EXTENSION_CHECKSUM_VERSION_1);
NetTxQueueGetExtension(txQueue, &extension, &queueContext->ChecksumExtension);
// Query Large Send Offload packet extension offset and store it in the context
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_LSO_NAME,
NET_PACKET_EXTENSION_LSO_VERSION_1);
NetTxQueueGetExtension(txQueue, &extension, &queueContext->LsoExtension);
return status;
}
创建接收队列
若要从 EVT_NET_ADAPTER_CREATE_RXQUEUE创建接收队列,请使用与传输队列相同的模式并调用 NetRxQueueCreate。
以下示例演示如何在代码中创建接收队列。 为清楚起见,将省略示例中的错误处理代码。
NTSTATUS
EvtAdapterCreateRxQueue(
_In_ NETADAPTER NetAdapter,
_Inout_ PNETRXQUEUE_INIT RxQueueInit
)
{
NTSTATUS status = STATUS_SUCCESS;
// Prepare the configuration structure
NET_PACKET_QUEUE_CONFIG rxConfig;
NET_PACKET_QUEUE_CONFIG_INIT(
&rxConfig,
EvtRxQueueAdvance,
EvtRxQueueSetNotificationEnabled,
EvtRxQueueCancel);
// Optional: register the queue's start and stop callbacks
rxConfig.EvtStart = EvtRxQueueStart;
rxConfig.EvtStop = EvtRxQueueStop;
// Get the queue ID
const ULONG queueId = NetRxQueueInitGetQueueId(RxQueueInit);
// Create the receive queue
NETPACKETQUEUE rxQueue;
status = NetRxQueueCreate(
RxQueueInit,
&rxAttributes,
&rxConfig,
&rxQueue);
// Get the queue context for storing the queue ID and packet extension offset info
PMY_RX_QUEUE_CONTEXT queueContext = GetMyRxQueueContext(rxQueue);
// Store the queue ID in the context
queueContext->QueueId = queueId;
// Query the checksum packet extension offset and store it in the context
NET_EXTENSION_QUERY extension;
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_CHECKSUM_NAME,
NET_PACKET_EXTENSION_CHECKSUM_VERSION_1);
NetRxQueueGetExtension(rxQueue, &extension, &queueContext->ChecksumExtension);
return status;
}
轮询模型
NetAdapter 数据路径采用轮询模型,一个数据包队列上的轮询操作与其他队列是相互独立的。 轮询模型通过调用客户端驱动程序的队列提前回调来实现,如下图所示:
高级数据包队列
在数据包队列上执行轮询操作的顺序如下所示:
- OS 为客户端驱动程序提供用于传输或接收的缓冲区。
- 客户端驱动程序将数据包配置到硬件。
- 客户端驱动程序将已完成的数据包返回到 OS。
轮询作发生在客户端驱动程序的 EvtPacketQueueAdvance 回调函数中。 客户端驱动程序中的每个数据包队列都由称为 net 通道的基础数据结构提供支持,这些结构包含或链接到系统内存中的实际网络数据缓冲区。 在 EvtPacketQueueAdvance 期间,客户端驱动程序通过控制通道内的索引,在硬件和 OS 之间传输缓冲区所有权,在传输或接收数据时对网络圈执行发送和接收作。
有关 net 通道的详细信息,请参阅 net 通道简介。
有关为传输队列实现 EvtPacketQueueAdvance 的示例,请参阅 使用网络环发送网络数据。 有关为接收队列实现 EvtPacketQueueAdvance 的示例,请参阅 使用 net rings 接收网络数据。
启用和禁用数据包队列通知
当客户端驱动程序在数据包队列的 net 通道中收到新数据包时,NetAdapterCx 将调用客户端驱动程序的 EvtPacketQueueSetNotificationEnabled 回调函数。 此回调向客户端驱动程序指示,轮询(EvtPacketQueueAdvance 或 EvtPacketQueueCancel)将停止,并且在客户端驱动程序调用 NetTxQueueNotifyMoreCompletedPacketsAvailable 或 NetRxQueueNotifyMoreReceivedPacketsAvailable 之前将不会继续。 通常,PCI 设备使用此回调来启用 Tx 或 Rx 中断。 收到中断后,可以再次禁用中断,客户端驱动程序调用 NetTxQueueNotifyMoreCompletedPacketsAvailable 或 NetRxQueueNotifyMoreReceivedPacketsAvailable 以触发框架重新开始轮询更多的已完成或接收的数据包。
为传输队列启用和禁用通知
对于 PCI NIC,启用传输队列通知通常意味着启用传输队列的硬件中断。 当硬件中断触发时,客户端从其 DPC 调用 NetTxQueueNotifyMoreCompletedPacketsAvailable 。
同样,对于 PCI NIC,禁用队列通知意味着禁用与队列关联的中断。
对于具有异步 I/O 模型的设备,客户端通常使用内部标志来跟踪启用的状态。 一个异步操作完成后,完成处理程序会检查此标志,如果标志已设置,则调用 NetTxQueueNotifyMoreCompletedPacketsAvailable。
如果 NetAdapterCx 调用 EvtPacketQueueSetNotificationEnabled 且 NotificationEnabled 设置为 FALSE,则客户端不得调用 NetTxQueueNotifyMoreCompletedPacketsAvailable ,直到 NetAdapterCx 下次调用此回调函数并将 NotificationEnabled 设置为 TRUE。
例如:
VOID
MyEvtTxQueueSetNotificationEnabled(
_In_ NETPACKETQUEUE TxQueue,
_In_ BOOLEAN NotificationEnabled
)
{
// Optional: retrieve queue's WDF context
MY_TX_QUEUE_CONTEXT *txContext = GetTxQueueContext(TxQueue);
// If NotificationEnabled is TRUE, enable transmit queue's hardware interrupt
...
}
VOID
MyEvtTxInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT AssociatedObject
)
{
MY_INTERRUPT_CONTEXT *interruptContext = GetInterruptContext(Interrupt);
NetTxQueueNotifyMoreCompletedPacketsAvailable(interruptContext->TxQueue);
}
启用或禁用接收队列的通知
对于 PCI NIC,启用接收队列通知看起来与 Tx 队列非常相似。 这通常意味着启用接收队列的硬件中断。 当硬件中断触发时,客户端从其 DPC 调用 NetRxQueueNotifyMoreReceivedPacketsAvailable 。
例如:
VOID
MyEvtRxQueueSetNotificationEnabled(
_In_ NETPACKETQUEUE RxQueue,
_In_ BOOLEAN NotificationEnabled
)
{
// optional: retrieve queue's WDF Context
MY_RX_QUEUE_CONTEXT *rxContext = GetRxQueueContext(RxQueue);
// If NotificationEnabled is TRUE, enable receive queue's hardware interrupt
...
}
VOID
MyEvtRxInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT AssociatedObject
)
{
MY_INTERRUPT_CONTEXT *interruptContext = GetInterruptContext(Interrupt);
NetRxQueueNotifyMoreReceivedPacketsAvailable(interruptContext->RxQueue);
}
对于 USB 设备或任何其他具有软件接收完成机制的队列,客户端驱动程序应在其自己的上下文中跟踪队列的通知是否已启用。 从完成例程(例如,当 USB 连续读取器中的消息可用时触发)中,如果通知已启用,请调用 NetRxQueueNotifyMoreReceivedPacketsAvailable。 以下示例演示如何做到这一点。
VOID
UsbEvtReaderCompletionRoutine(
_In_ WDFUSBPIPE Pipe,
_In_ WDFMEMORY Buffer,
_In_ size_t NumBytesTransferred,
_In_ WDFCONTEXT Context
)
{
UNREFERENCED_PARAMETER(Pipe);
PUSB_RCB_POOL pRcbPool = *((PUSB_RCB_POOL*) Context);
PUSB_RCB pRcb = (PUSB_RCB) WdfMemoryGetBuffer(Buffer, NULL);
pRcb->DataOffsetCurrent = 0;
pRcb->DataWdfMemory = Buffer;
pRcb->DataValidSize = NumBytesTransferred;
WdfObjectReference(pRcb->DataWdfMemory);
ExInterlockedInsertTailList(&pRcbPool->ListHead,
&pRcb->Link,
&pRcbPool->ListSpinLock);
if (InterlockedExchange(&pRcbPool->NotificationEnabled, FALSE) == TRUE)
{
NetRxQueueNotifyMoreReceivedPacketsAvailable(pRcbPool->RxQueue);
}
}
取消数据包队列
当 OS 停止数据路径时,它首先调用客户端驱动程序的 EvtPacketQueueCancel 回调函数。 此回调是客户端驱动程序在框架删除数据包队列之前执行所需的任何处理的位置。 取消传输队列是可选的,具体取决于硬件是否支持正在进行的传输取消,而取消接收队列则是必需的。
在 EvtPacketQueueCancel 期间,驱动程序会根据需要将数据包返回到 OS。 有关传输队列和接收队列取消的代码示例,请参阅 使用网络环取消网络数据。
调用驱动程序的 EvtPacketQueueCancel 回调后,框架将继续轮询驱动程序的 EvtPacketQueueAdvance 回调,直到所有数据包和缓冲区都返回到 OS。