共用方式為


NDIS 輪詢模式

NDIS 輪詢模式概觀

NDIS 輪詢模式是操作系統控制的輪詢執行模型,可驅動網路介面數據路徑。

先前,NDIS 沒有對數據路徑執行內容進行正式定義。 NDIS 驅動程式通常依賴延遲過程調用 (DPC) 來實作其執行模型。 不過,使用 DPC 在進行長時間指示鏈結並避免此問題時,可能會讓系統不知所措,這需要許多難以正確操作的程式代碼。 NDIS 輪詢模式提供 DPC 和類似執行工具的替代方案。

NDIS 輪詢模式會將排程決策的複雜性從 NIC 驅動程式移開,並移至 NDIS,其中 NDIS 會設定每次反覆專案的工作限制。 若要達到此輪詢模式,可提供:

  1. OS 對 NIC 施加回壓的機制。

  2. OS 可精細控制中斷的機制。

NDIS 輪詢模式適用於 NDIS 6.85 和更新版本的迷你埠驅動程式。

DPC 模型的問題

下列循序圖說明 NDIS 迷你埠驅動程式如何使用 DPC 處理 Rx 封包的高載範例。 在此範例中,硬體是PCIe NIC的標準。 它有接收硬體佇列和該佇列的中斷遮罩。

此圖顯示具有 Rx 封包和接收硬體佇列的 NDIS DPC 模型。

沒有網路活動時,硬體會啟用 Rx 中斷。 當 Rx 封包送達時:

  1. 硬體會產生中斷,NDIS 會呼叫驅動程式的 MiniportInterrupt 函式 (ISR)。

  2. 驅動程式在ISR中執行的工作很少,因為它們是在非常高的IRQL執行。 驅動程式會停用ISR的中斷,並將硬體處理延遲至 MiniportInterruptDPC 函式 (DPC)。

  3. NDIS 最終會呼叫驅動程式的 DPC,而驅動程式會從硬體佇列清空任何完成,並將其指示給 OS。

當驅動程式將 I/O 作業延遲至 DPC 時,兩個痛點可能會影響網路堆疊:

  1. 驅動程式不知道系統是否能夠處理所指示的所有數據,因此驅動程式別無選擇,而是要從其硬體佇列中清空盡可能多的元素,並指示它們向上堆疊。

  2. 由於驅動程式使用 DPC 從 ISR 延遲工作,因此所有指示都會在DISPATCH_LEVEL進行。 當進行長時間的指示鏈結並導致 錯誤檢查0x133 DPC_WATCHDOG_VIOLATION時,這可能會使系統不知所措。

避免這些痛點需要驅動程式中的許多棘手程序代碼。 雖然您可以使用 KeQueryDpcWatchdogInformation 函式來檢查 DPC 監視程式是否接近限制,並中斷 DPC,但仍需要在驅動程式中建立此基礎結構:您需要一些方法來暫停一點,然後繼續指出封包,同時您需要將這一切與數據路徑的存留期同步處理。

Poll 對象的簡介

NDIS 輪詢模式引進 Poll 物件,以解決與 DPC 相關聯的痛點。 Poll 對象是執行內容建構。 迷你埠驅動程式可以在處理資料路徑作業時,使用Poll物件取代 DPC。

Poll 物件提供下列專案:

  • 它提供一種方式,讓 NDIS 為每個反覆項目設定工作限制。

  • 它與通知機制緊密相連。 這會讓 OS 和 NIC 在需要處理工作時保持同步。

  • 它具有內建反覆專案和中斷的概念。 使用 DPC 時,驅動程式會在每次完成 DPC 時,強制重新啟用中斷。 使用Poll物件時,驅動程式不需要重新啟用每個輪詢反覆運算的中斷,因為輪詢模式會讓驅動程式知道何時完成輪詢,而是時候重新啟用中斷。

  • 進行排程決策時,系統可以聰明地瞭解如何在DISPATCH_LEVEL或PASSIVE_LEVEL執行。 這可以允許微調來自不同 NIC 的流量優先順序,並導致計算機上的工作負載分佈更公平。

  • 它具有串行化保證。 一旦您從 Poll 物件的執行內容內執行程式碼,您保證不會執行與相同執行內容相關的其他程式代碼。 這可讓 NIC 驅動程式具有其 datapath 的無鎖定實作。

NDIS 輪詢模式模型

下列循序圖說明相同的假設PCIe NIC驅動程式如何使用Poll物件而非 DPC 來處理 Rx 封包的高載。

顯示 NDIS 輪詢模式與 Rx 封包和接收硬體佇列的圖表。

如同 DPC 模型,當 Rx 封包到達硬體時會產生中斷,NDIS 會呼叫驅動程式的 ISR,而驅動程式會停用 ISR 的中斷。 此時,輪詢模式模型會發散:

  1. 驅動程式不會將 DPC 排入佇列,而是從 ISR 將 Poll 物件排入佇列,以通知 NDIS 已準備好處理新工作。

  2. 在未來的某個時間點,NDIS 會呼叫驅動程式的 輪詢反覆運算處理程式 來處理工作。 與 DPC 不同,驅動程式不允許指出其硬體佇列中已備妥元素的 Rx NBL 數目。 驅動程式應該改為檢查處理程式的輪詢數據參數,以取得它可以指出的 NBL 數目上限。

    一旦驅動程式擷取到其應該初始化 NBL 的最大 Rx 封包數目,請將它們新增至輪詢處理程式所提供的 NBL 佇列,然後結束回呼。 驅動程式不應該在結束之前啟用中斷。

  3. NDIS 會繼續輪詢驅動程式,直到評估驅動程式不再取得進展為止。 此時,NDIS 會停止輪詢,並要求驅動程式 重新啟用中斷

NDIS 輪詢模式的標準化 INF 關鍵詞

下列關鍵詞必須用來啟用或停用對 NDIS 輪詢模式的支援:

*NdisPoll 列舉標準化 INF 關鍵詞具有下列屬性:

SubkeyName
您必須在 INF 檔案中指定且出現在登錄中的關鍵字名稱。

ParamDesc
與 SubkeyName 相關聯的顯示文字。


與清單中每個選項相關聯的列舉整數值。 此值會儲存在 NDI\params\ SubkeyName\中。

EnumDesc
與功能表中顯示的每個值相關聯的顯示文字。

預設
功能表的預設值。

SubkeyName ParamDesc EnumDesc
*NdisPoll Ndis 投票模式 0 已停用
1 (預設值) 已啟用

如需使用列舉關鍵詞的詳細資訊,請參閱 列舉關鍵詞

建立Poll物件

若要建立 Poll 物件,迷你埠驅動程式會在其 MiniportInitializeEx 回呼函式中執行下列動作:

  1. 配置私人迷你埠內容。
  2. 配置NDIS_POLL_CHARACTERISTICS結構,以指定 NdisPoll 和 NdisSetPollNotification 回呼函式的進入點。
  3. 呼叫 NdisRegisterPoll 來建立 Poll 物件,並將其儲存在迷你埠內容中。

下列範例示範迷你埠驅動程式如何建立接收佇列流程的Poll物件。 為了簡單起見,會省略錯誤處理。

NDIS_SET_POLL_NOTIFICATION NdisSetPollNotification; 
NDIS_POLL NdisPoll; 

NDIS_STATUS 
MiniportInitialize( 
    _In_ NDIS_HANDLE NdisAdapterHandle, 
    _In_ NDIS_HANDLE MiniportDriverContext, 
    _In_ NDIS_MINIPORT_INIT_PARAMETERS * MiniportInitParameters 
) 
{ 
    // Allocate a private miniport context 
    MINIPORT_CONTEXT * miniportContext = ...;
 
    NDIS_POLL_CHARACTERISTICS pollCharacteristics; 
    pollCharacteristics.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; 
    pollCharacteristics.Header.Revision = NDIS_POLL_CHARACTERISTICS_REVISION_1; 
    pollCharacteristics.Header.Size = NDIS_SIZEOF_NDIS_POLL_CHARACTERISTICS_REVISION_1; 
    pollCharacteristics.SetPollNotificationHandler = NdisSetPollNotification; 
    pollCharacteristics.PollHandler = NdisPoll; 

    // Create a Poll object and store it in the miniport context 
    NdisRegisterPoll( 
        NdisAdapterHandle, 
        miniportContext, 
        &pollCharacteristics, 
        &miniportContext->RxPoll); 
 
    return NDIS_STATUS_SUCCESS; 
} 

將 Poll 物件排入佇列以執行

從ISR,迷你埠驅動程式會呼叫 NdisRequestPoll ,將 Poll 物件排入佇列以執行。 下列範例顯示接收處理,但為了簡單起見,忽略中斷線的共用。

BOOLEAN 
MiniportIsr( 
  KINTERRUPT * Interrupt, 
  void * Context 
) 
{ 
    auto miniportContext = static_cast<MINIPORT_CONTEXT *>(Context); 
    auto hardwareContext = miniportContext->HardwareContext; 

    // Check if this interrupt is due to a received packet 
    if (hardwareContext->ISR & RX_OK) 
    { 
        // Disable the receive interrupt and queue the Poll 
        hardwareContext->IMR &= ~RX_OK; 
        NdisRequestPoll(miniportContext->RxPoll, nullptr); 
    }

    return TRUE; 
} 

實作輪詢反覆運算處理程式

NDIS 會叫用迷你埠驅動程式的 NdisPoll 回呼,以輪詢接收指示並傳送完成。 當驅動程式呼叫 NdisRequestPoll 以將 Poll 物件排入佇列時,NDIS 會先叫用 NdisPoll。 NDIS 將繼續叫用 NdisPoll ,而司機正在進行接收指示或傳輸完成的前進進度。

針對接收指示,驅動程式應在 NdisPoll執行下列動作:

  1. 檢查 NDIS_POLL_DATA 結構的 receive 參數,以取得可指出的 NBL 數目上限。
  2. 最多擷取 Rx 封包數目上限。
  3. 初始化 NBL。
  4. 將它們新增至 NDIS_POLL_RECEIVE_DATA 結構所提供的 NBL 佇列(位於 NdisPoll PollData 參數NDIS_POLL_DATA結構中)。
  5. 結束回呼。

針對傳輸完成,驅動程式應該在 NdisPoll執行下列動作:

  1. 檢查NDIS_POLL_DATA結構的傳輸參數,以取得其可完成的 NBL 數目上限。
  2. 最多擷取 Tx 封包數目上限。
  3. 完成 NBL。
  4. 將它們新增至 NDIS_POLL_TRANSMIT_DATA 結構所提供的 NBL 佇列(位於 NdisPoll PollData 參數的 NDIS_POLL_DATA 結構中)。
  5. 結束回呼。

驅動程式不應該在結束 NdisPoll 函式之前啟用 Poll 物件的中斷。 NDIS 將繼續輪詢驅動程式,直到評估沒有取得進展。 此時,NDIS 會停止輪詢,並要求驅動程式 重新啟用中斷

以下是驅動程式如何針對接收佇列流程實 作 NdisPoll

_Use_decl_annotations_ 
void 
NdisPoll( 
    void * Context, 
    NDIS_POLL_DATA * PollData 
) 
{ 
    auto miniportContext = static_cast<MINIPORT_CONTEXT *>(Context); 
    auto hardwareContext = miniportContext->HardwareContext; 

    // Drain received frames 
    auto & receive = PollData->Receive; 
    receive.NumberOfRemainingNbls = NDIS_ANY_NUMBER_OF_NBLS; 
    receive.Flags = NDIS_RECEIVE_FLAGS_SHARED_MEMORY_VALID; 

    while (receive.NumberOfIndicatedNbls < receive.MaxNblsToIndicate) 
    { 
        auto rxDescriptor = HardwareQueueGetNextDescriptorToCheck(hardwareContext->RxQueue); 

        // If this descriptor is still owned by hardware stop draining packets 
        if ((rxDescriptor->Status & HW_OWN) != 0) 
            break; 

        auto nbl = MakeNblFromRxDescriptor(miniportContext->NblPool, rxDescriptor); 

        AppendNbl(&receive.IndicatedNblChain, nbl); 
        receive.NumberOfIndicatedNbls++; 

        // Move to next descriptor 
        HardwareQueueAdvanceNextDescriptorToCheck(hardwareContext->RxQueue); 
    } 
} 

管理中斷

迷你埠驅動程式會實作 NdisSetPollNotification 回呼,以啟用或停用與 Poll 對象相關聯的中斷。 NDIS 通常會在 NdisPoll 中偵測到迷你埠驅動程式未在 NdisPoll向前推進時叫用 NdisSetPollNotification 回呼。 NDIS 使用 NdisSetPollNotification 告訴驅動程式,它會停止叫 用 NdisPoll。 當新的工作準備好處理時,驅動程式應該叫用 NdisRequestPoll

以下是驅動程式如何實 作接收佇列流程的 NdisSetPollNotification

_Use_decl_annotations_ 
void 
NdisSetPollNotification( 
    void * Context, 
    NDIS_POLL_NOTIFICATION * Notification 
) 
{ 
    auto miniportContext = static_cast<MINIPORT_CONTEXT *>(Context); 
    auto hardwareContext = miniportContext->HardwareContext; 

    if (Notification->Enabled) 
    { 
        hardwareContext->IMR |= RX_OK; 
    } 
    else 
    { 
        hardwareContext->IMR &= ~RX_OK; 
    } 
}