NDIS ポーリング モードの概要
NDIS ポーリング モードは、ネットワーク インターフェイス データパスを駆動する OS 制御ポーリング実行モデルです。
以前は、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 ドライバーの ミニポートInterrupt 関数 (ISR) を呼び出します。
ドライバーは非常に高い IRQL で動作するため、ISR での作業はほとんどありません。 ドライバーは、ISR からの割り込みを無効にし、 ミニポートInterruptDPC 関数 (DPC) にハードウェア処理を延期します。
NDIS は最終的にドライバーの DPC を呼び出し、ドライバーはハードウェア キューから完了処理を取り出して、OS に通知します。
ドライバーが I/O 操作を DPC に延期すると、次の 2 つの問題点がネットワーク スタックに影響する可能性があります。
ドライバーは、システムが示されているすべてのデータを処理できるかどうかを認識しないため、ドライバーは、ハードウェア キューからできるだけ多くの要素をドレインし、スタックを上に示す以外に選択肢はありません。
ドライバーは DPC を使用して ISR から作業を延期しているため、すべての表示は DISPATCH_LEVEL で行われます。 長いインディケーションチェーンが作成されると、バグ チェック 0x133 DPC_WATCHDOG_VIOLATIONを引き起こし、結果としてシステムが圧倒される可能性があります。
これらの問題点を回避するには、ドライバーに多くの複雑なコードが必要です。 DPC ウォッチドッグが KeQueryDpcWatchdogInformation 関数を使用して制限に近いかどうかを確認し、DPC から抜け出すことができますが、ドライバーでこの周りにインフラストラクチャを構築する必要があります。少しの間一時停止し、パケットを示し続ける何らかの方法が必要です。同時に、データパスの有効期間と同期する必要があります。
Poll オブジェクトの概要
NDIS ポーリング モードでは、DPC に関連付けられている問題点を解決する Poll オブジェクトが導入されています。 Poll オブジェクトは、実行コンテキストコンストラクトです。 ミニポート ドライバーは、データパス操作を処理するときに DPC の代わりに Poll オブジェクトを使用できます。
Poll オブジェクトには、次のものが用意されています。
NDIS がイテレーションごとに作業制限を設定する方法を提供します。
通知メカニズムに密接に関連付けられています。 これにより、作業を処理する必要があるタイミングに関する OS と NIC の同期が維持されます。
反復と割り込みの概念が組み込まれています。 DPC を使用する場合、ドライバーは、DPC を完了するたびに割り込みを再度有効にする必要があります。 ポーリング オブジェクトを使用する場合、ポーリング モードでは、ポーリングが完了し、割り込みを再度有効にする時間がドライバーに通知されるため、ドライバーは各ポーリングイテレーションの割り込みを再度有効にする必要はありません。
スケジューリングの決定を行うとき、システムはDISPATCH_LEVELで実行するか、PASSIVE_LEVELで実行するかをスマートに行うことができます。 これにより、異なる NIC からのトラフィックの優先順位を微調整でき、マシン上でのワークロードの分散が公平になります。
シリアル化の保証があります。 Poll オブジェクトの実行コンテキスト内からコードを実行すると、同じ実行コンテキストに関連する他のコードは実行されません。 これにより、NIC ドライバーは、そのデータパスのロックフリーの実装を持つことができます。
NDISポーリングモードモデル
次のシーケンス図は、同じ架空の PCIe NIC ドライバーが DPC ではなく Poll オブジェクトを使用して Rx パケットのバーストを処理する方法を示しています。
DPC モデルと同様に、Rx パケットが到着すると、ハードウェアは割り込みを生成し、NDIS はドライバーの ISR を呼び出し、ドライバーは ISR からの割り込みを無効にします。 この時点で、ポーリング モード モデルは次の点で分岐します。
ドライバーは、DPC をキューに入れる代わりに、ISR から (以前に作成した) Poll オブジェクトをキューに入れ、新しい作業を処理する準備ができていることを NDIS に通知します。
将来の NDIS のある時点で、ドライバーの ポーリングイテレーション ハンドラー を呼び出して作業を処理します。 DPC とは異なり、ドライバーは、そのハードウェア キューに準備ができている要素がある限り多くの Rx NBLs を示すことはできません。 ドライバーは、代わりにハンドラーのポーリング データ パラメーターをチェックして、指定できる最大数の NBL を取得する必要があります。
ドライバーが最大数のRxパケットを取得した後、NBLを初期化し、ポーリングハンドラが提供するNBLキューに追加して、コールバックを終了します。 ドライバーは、終了する前に割り込みを有効にしないでください。
NDIS は、ドライバーが前進しなくなったと判断されるまで、ドライバーを確認し続けます。 この時点で NDIS はポーリングを停止し、 割り込みを再度有効にするようにドライバーに要求します。
NDIS ポーリング モードの標準化された INF キーワード
NDIS ポーリング モードのサポートを有効または無効にするには、次のキーワードを使用する必要があります。
*NdisPoll 列挙標準化 INF キーワードには、次の属性があります。
SubkeyName
INF ファイルで指定する必要があり、レジストリに表示されるキーワードの名前。
ParamDesc
SubkeyName に関連付けられている表示テキスト。
価値
リスト内の各オプションに関連付けられている列挙整数値。 この値は NDI\params\ SubkeyName\Value に格納されます。
EnumDesc
メニューに表示される各値に関連付けられている表示テキスト。
既定値
メニューの既定値。
| SubkeyName | ParamDesc | 価値 | EnumDesc |
|---|---|---|---|
| *NdisPoll | Ndis ポーリング モード | 0 | 障害者 |
| 1 (既定値) | 有効化済み |
列挙キーワードの使用の詳細については、「列挙キーワードの」を参照してください。
Poll オブジェクトの作成
Poll オブジェクトを作成するには、ミニポート ドライバーは、 ミニポートInitializeEx コールバック関数で次の処理を行います。
- プライベート ミニポート コンテキストを割り当てます。
- NdisPoll および NdisSetPollNotification コールバック関数のエントリ ポイントを指定するNDIS_POLL_CHARACTERISTICS構造体を割り当てます。
- 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構造体の受信パラメーターを調べて、指定できる NBL の最大数を取得します。
- Rx パケットの最大数までフェッチします。
- NBLs を初期化します。
- NDIS_POLL_RECEIVE_DATA 構造体によって提供される NBL キューに追加します (NdisPollPollData パラメーターの NDIS_POLL_DATA 構造体にあります)。
- コールバックを終了します。
送信が完了した場合、ドライバーは NdisPoll で次の操作を行う必要があります。
- NDIS_POLL_DATA構造体の送信パラメーターを調べて、完了できる最大数の NBL を取得します。
- Tx パケットの最大数まで取得します。
- NBLs を完了します。
- NDIS_POLL_TRANSMIT_DATA 構造体によって提供される NBL キューに追加します (NdisPollPollData パラメーターの 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);
}
}
割り込みの管理
ミニポート ドライバーは、Poll オブジェクトに関連付けられている割り込みを有効または無効にする NdisSetPollNotification コールバックを実装します。 NDIS は通常、ミニポート ドライバーが 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;
}
}