NDIS 輪詢模式
NDIS 輪詢模式概觀
NDIS 輪詢模式是操作系統控制的輪詢執行模型,可驅動網路介面數據路徑。
先前,NDIS 沒有對數據路徑執行內容進行正式定義。 NDIS 驅動程式通常依賴延遲過程調用 (DPC) 來實作其執行模型。 不過,使用 DPC 在進行長時間指示鏈結並避免此問題時,可能會讓系統不知所措,這需要許多難以正確操作的程式代碼。 NDIS 輪詢模式提供 DPC 和類似執行工具的替代方案。
NDIS 輪詢模式會將排程決策的複雜性從 NIC 驅動程式移開,並移至 NDIS,其中 NDIS 會設定每次反覆專案的工作限制。 若要達到此輪詢模式,可提供:
OS 對 NIC 施加回壓的機制。
OS 可精細控制中斷的機制。
NDIS 輪詢模式適用於 NDIS 6.85 和更新版本的迷你埠驅動程式。
DPC 模型的問題
下列循序圖說明 NDIS 迷你埠驅動程式如何使用 DPC 處理 Rx 封包的高載範例。 在此範例中,硬體是PCIe NIC的標準。 它有接收硬體佇列和該佇列的中斷遮罩。
沒有網路活動時,硬體會啟用 Rx 中斷。 當 Rx 封包送達時:
硬體會產生中斷,NDIS 會呼叫驅動程式的 MiniportInterrupt 函式 (ISR)。
驅動程式在ISR中執行的工作很少,因為它們是在非常高的IRQL執行。 驅動程式會停用ISR的中斷,並將硬體處理延遲至 MiniportInterruptDPC 函式 (DPC)。
NDIS 最終會呼叫驅動程式的 DPC,而驅動程式會從硬體佇列清空任何完成,並將其指示給 OS。
當驅動程式將 I/O 作業延遲至 DPC 時,兩個痛點可能會影響網路堆疊:
驅動程式不知道系統是否能夠處理所指示的所有數據,因此驅動程式別無選擇,而是要從其硬體佇列中清空盡可能多的元素,並指示它們向上堆疊。
由於驅動程式使用 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 封包的高載。
如同 DPC 模型,當 Rx 封包到達硬體時會產生中斷,NDIS 會呼叫驅動程式的 ISR,而驅動程式會停用 ISR 的中斷。 此時,輪詢模式模型會發散:
驅動程式不會將 DPC 排入佇列,而是從 ISR 將 Poll 物件排入佇列,以通知 NDIS 已準備好處理新工作。
在未來的某個時間點,NDIS 會呼叫驅動程式的 輪詢反覆運算處理程式 來處理工作。 與 DPC 不同,驅動程式不允許指出其硬體佇列中已備妥元素的 Rx NBL 數目。 驅動程式應該改為檢查處理程式的輪詢數據參數,以取得它可以指出的 NBL 數目上限。
一旦驅動程式擷取到其應該初始化 NBL 的最大 Rx 封包數目,請將它們新增至輪詢處理程式所提供的 NBL 佇列,然後結束回呼。 驅動程式不應該在結束之前啟用中斷。
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 回呼函式中執行下列動作:
- 配置私人迷你埠內容。
- 配置NDIS_POLL_CHARACTERISTICS結構,以指定 NdisPoll 和 NdisSetPollNotification 回呼函式的進入點。
- 呼叫 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 中執行下列動作:
- 檢查 NDIS_POLL_DATA 結構的 receive 參數,以取得可指出的 NBL 數目上限。
- 最多擷取 Rx 封包數目上限。
- 初始化 NBL。
- 將它們新增至 NDIS_POLL_RECEIVE_DATA 結構所提供的 NBL 佇列(位於 NdisPoll PollData 參數NDIS_POLL_DATA結構中)。
- 結束回呼。
針對傳輸完成,驅動程式應該在 NdisPoll 中執行下列動作:
- 檢查NDIS_POLL_DATA結構的傳輸參數,以取得其可完成的 NBL 數目上限。
- 最多擷取 Tx 封包數目上限。
- 完成 NBL。
- 將它們新增至 NDIS_POLL_TRANSMIT_DATA 結構所提供的 NBL 佇列(位於 NdisPoll PollData 參數的 NDIS_POLL_DATA 結構中)。
- 結束回呼。
驅動程式不應該在結束 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;
}
}