次の方法で共有


NDIS ポーリング モード

NDIS ポーリング モードの概要

NDIS ポーリング モードは、ネットワーク インターフェイス データパスを駆動する OS 制御ポーリング実行モデルです。

これまで、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. ドライバーは非常に高い IRQL で実行されるため、ISR ではほとんど動作しません。 ドライバーは、ISR からの割り込みを無効にし、ハードウェア処理を MiniportInterruptDPC 関数 (DPC) まで遅延させます。

  3. NDIS は最終的にドライバーの DPC を呼び出し、ドライバーはハードウェア キューから完了をすべてドレインして、OS に指示します。

ドライバーが I/O 操作を DPC まで遅延させると、次の 2 つの問題点がネットワーク スタックに影響を与える可能性があります。

  1. ドライバーは、示されているすべてのデータをシステムが処理できるかどうかを認識できないため、ハードウェア キューからできるだけ多くの要素をドレインしてスタックに指示する以外の選択肢はドライバーにありません。

  2. ドライバーは DPC を使って ISR から作業を遅延させるため、すべての指示は DISPATCH_LEVEL で行われます。 長い指示チェーンが作成され、バグ チェック 0x133 DPC_WATCHDOG_VIOLATION が発生すると、これによりシステムに大きな負荷がかかる可能性があります。

これらの問題点を回避するには、ドライバーに多くの複雑なコードが必要です。 DPC ウォッチドッグが KeQueryDpcWatchdogInformation 関数を使用して制限に近づき、DPC を中断するかどうかをチェックすることはできますが、ドライバーでこれに関するインフラストラクチャを構築する必要があります。少しの間一時停止し、パケットを指示し続ける何らかの方法が必要です。同時に、データパスの有効期間と同期する必要があります。

ポーリング オブジェクトの概要

NDIS ポーリング モードには、DPC に関連する問題点を解決するポーリング オブジェクトが導入されています。 ポーリング オブジェクトは、実行コンテキスト コンストラクトです。 ミニポート ドライバーは、データパス操作を処理するときに DPC の代わりにポーリング オブジェクトを使用できます。

ポーリング オブジェクトには、以下が用意されています。

  • NDIS が反復処理ごとに作業制限を設定する方法が提供されます。

  • 通知メカニズムに密接に関連付けられます。 これにより、作業を処理する必要があるタイミングに関して OS と NIC の同期が維持されます。

  • 反復処理と割り込みの概念が組み込まれています。 DPC を使用するとき、ドライバーは、DPC を完了するたびに割り込みを再度有効にするよう強制されます。 ポーリング オブジェクトを使用するとき、ドライバーはポーリング反復処理ごとに割り込みを再度有効にする必要はありません。ポーリング モードでは、ポーリングを完了し、割り込みを再度有効にするタイミングをドライバーが認識できるためです。

  • スケジュールの決定を下すとき、システムは DISPATCH_LEVEL と PASSIVE_LEVEL のどちらで実行するかをスマートに決めることができます。 これにより、さまざまな NIC からのトラフィックの優先順位を微調整でき、コンピューター上でのワークロードの分散が公平になります。

  • シリアル化の保証があります。 ポーリング オブジェクトの実行コンテキスト内からコードを実行すると、同じ実行コンテキストに関連する他のコードは実行されないことが保証されます。 これにより、NIC ドライバーは、そのデータパスのロックフリー実装を持つことができます。

NDIS ポーリング モード モデル

次のシーケンス図は、同じ架空の PCIe NIC ドライバーが、DPC の代わりにポーリング オブジェクトを使用して Rx パケットのバーストを処理する方法を示しています。

Rx パケットと受信ハードウェア キューを含む NDIS ポーリング モードを示す図。

DPC モデルと同様、Rx パケットが到着すると、ハードウェアは割り込みを生成して、NDIS はドライバーの ISR を呼び出し、ドライバーは ISR からの割り込みを無効にします。 この時点で、ポーリング モード モデルは次のように枝分かれします。

  1. ドライバーは、DPC をキューに入れる代わりに、ISR から (以前に作成した) ポーリング オブジェクトをキューに入れ、新しい作業を処理する準備ができたことを NDIS に通知します。

  2. 将来のある時点で、NDIS はドライバーのポーリング反復処理ハンドラーを呼び出して作業を処理します。 DPC とは異なり、ドライバーは、そのハードウェア キューに準備が完了している要素がある限り、多くの Rx NBL を指示することはできません。 ドライバーは、代わりにハンドラーのポーリング データ パラメーターをチェックし、指示できる最大数の NBL を取得する必要があります。

    ドライバーが Rx パケットの最大数までフェッチしたら、NBL を初期化して、ポーリング ハンドラーによって提供される NBL キューに追加し、コールバックを終了します。 終了前にドライバーが割り込みを有効にすることはありません。

  3. NDIS は、進行が終了したとドライバーが評価するまで、ドライバーをポーリングし続けます。 この時点で、NDIS はポーリングを停止し、割り込みを再度有効にするようドライバーに要求します。

NDIS ポーリング モードの標準化された INF キーワード

NDIS ポーリング モードのサポートを有効または無効にするには、次のキーワードを使用する必要があります。

*NdisPoll 列挙標準化 INF キーワードには、次の属性があります。

SubkeyName
INF ファイルで指定する必要があり、レジストリに表示されるキーワードの名前。

ParamDesc
SubkeyName に関連付けられている表示テキスト。

Value
リスト内の各オプションに関連付けられている列挙整数値。 この値は、NDI\params\ SubkeyName\Value に格納されます。

EnumDesc
メニューに表示される各値に関連付けられている表示テキスト。

既定値
このメニューの既定値。

SubkeyName ParamDesc Value EnumDesc
*NdisPoll Ndis ポーリング モード 0 無効
1 (既定値) Enabled

列挙キーワードの使用について詳しくは、「列挙キーワード」をご覧ください。

ポーリング オブジェクトの作成

ポーリング オブジェクトを作成するには、ミニポート ドライバーは、MiniportInitializeEx コールバック関数で次の処理を行います。

  1. プライベート ミニポート コンテキストを割り当てます。
  2. NdisPoll および NdisSetPollNotification コールバック関数のエントリ ポイントを指定する NDIS_POLL_CHARACTERISTICS 構造を割り当てます。
  3. NdisRegisterPoll を呼び出してポーリング オブジェクトを作成し、ミニポート コンテキストに格納します。

次の例は、ミニポート ドライバーが受信キュー フローのポーリング オブジェクトを作成する方法を示しています。 簡潔にするため、エラー処理は省略しています。

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; 
} 

実行のためにポーリング オブジェクトをキューに入れる

ISR から、ミニポート ドライバーは NdisRequestPoll を呼び出して、実行のためにポーリング オブジェクトをキューに入れます。 次の例は、受信処理を示していますが、わかりやすくするため割り込み行の共有を無視しています。

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 を呼び出してポーリング オブジェクトをキューに入れると、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 構造の transmit パラメーターを確認し、完了できる NBL の最大数を取得します。
  2. Tx パケットの最大数までフェッチします。
  3. NBL を完了します。
  4. NDIS_POLL_TRANSMIT_DATA構造体によって提供される NBL キューに追加します (NdisPoll PollData パラメーターのNDIS_POLL_DATA構造体にあります)。
  5. コールバックを終了します。

ドライバーは、NdisPoll 関数を終了する前にポーリング オブジェクトの割り込みを有効にしないでください。 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 コールバックを実装します。 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; 
    } 
}