共用方式為


在 NetAdapterCx 驅動程式中傳輸和接收佇列

概觀

NetAdapterCx 驅動程式中的傳輸和接收佇列可讓用戶端驅動程式在軟體中建立硬體功能的模型,例如硬體傳輸和接收佇列。 本文說明如何實作和管理這些佇列,以優化 NetAdapterCx 中的數據路徑效能。

當用戶端驅動程式 呼叫NET_ADAPTER_DATAPATH_CALLBACKS_INIT時,通常是從其 EVT_WDF_DRIVER_DEVICE_ADD 事件回呼函式,它會提供兩個佇列建立回呼: EVT_NET_ADAPTER_CREATE_TXQUEUEEVT_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 數據路徑是輪詢模型,而一個封包佇列上的輪詢作業與其他佇列無關。 輪詢模型是藉由呼叫用戶端驅動程式的佇列進階回呼來實作,如下圖所示:

顯示 NetAdapterCx 中輪詢流程的圖表。

進階封包佇列

封包佇列上的輪詢作業順序如下:

  1. OS 會為用戶端驅動程式提供緩衝區,以進行傳輸或接收。
  2. 用戶端驅動程式會將封包配置到硬體。
  3. 用戶端驅動程式會將已完成的封包傳回至 OS。

輪詢作業發生在客戶端驅動程式的 EvtPacketQueueAdvance 回呼函式內。 用戶端驅動程式中的每個封包佇列都受到稱為 net 通道的基礎數據結構所支援,其包含或連結至系統記憶體中實際的網路數據緩衝區。 在 EvtPacketQueueAdvance 期間,用戶端驅動程式會藉由控制通道內的索引,在硬體與 OS 之間傳輸緩衝區擁有權,以傳輸或接收數據,在網路通道上執行傳送和接收作業。

如需 net 通道的詳細資訊,請參閱 Net 通道簡介

如需實作傳輸佇列 EvtPacketQueueAdvance 的範例,請參閱 使用 net 通道傳送網路數據。 如需實作接收佇列 EvtPacketQueueAdvance 的範例,請參閱 使用 net 通道接收網路數據

啟用和停用封包佇列通知

當客戶端驅動程式在封包佇列的 net 通道中收到新的封包時,NetAdapterCx 會叫用用戶端驅動程式的 EvtPacketQueueSetNotificationEnabled 回呼函式。 此回呼訊息指示客戶端驅動程式,當輪詢事件 EvtPacketQueueAdvanceEvtPacketQueueCancel 停止時,將會暫停,並且要等到客戶端驅動程式呼叫 NetTxQueueNotifyMoreCompletedPacketsAvailableNetRxQueueNotifyMoreReceivedPacketsAvailable 才能繼續。 一般而言,PCI 裝置會使用此回呼函數來啟用 TX 或 RX 中斷。 收到中斷之後,可以再次停用中斷,而用戶端驅動程式會呼叫 NetTxQueueNotifyMoreCompletedPacketsAvailableNetRxQueueNotifyMoreReceivedPacketsAvailable,以觸發架構重新開始輪詢。

啟用和停用傳輸佇列的通知

針對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 為止。