ユーザーモードの処理内容の送信

重要

一部の情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

この記事では、Windows 11 バージョン 24H2 (WDDM 3.2) の時点でまだ開発中のユーザーモード (UM) の処理内容送信機能について説明します。 UM 処理内容の送信により、アプリケーションは非常に短い待機時間でユーザー モードから直接 GPU に処理内容を送信することができます。 目標は、GPU に小さなワークロードを頻繁に送信するアプリケーションのパフォーマンスを向上させることです。 さらに、ユーザーモードの送信は、コンテナーまたは仮想マシン (VM) 内で実行されている場合、このようなアプリケーションにとって大きなメリットが期待されます。 これがメリットであるのは、VM で実行されているユーザーモード ドライバー (UMD) が、ホストにメッセージを送信しなくても GPU に処理内容を直接送信できるためです。

UM 処理内容の送信をサポートする IHV ドライバーとハードウェアは、従来のカーネル モードの処理内容送信モデルを同時にサポートし続ける必要があります。 このサポートは、最新のホストで実行されている従来の KM キューのみサポートする古いゲストなどのシナリオに必要です。

この記事では、Flip/FlipEx との UM 送信の相互運用性については説明しません。 この記事で説明する UM 送信は、シナリオのレンダリングのみ/コンピューティング クラスに限定されます。 現在のところ、プレゼンテーション パイプラインは、監視対象のネイティブ フェンスに依存しているため、カーネルモードの送信に基づいています。 UM 送信ベースのプレゼンテーションの設計と実装は、コンピューティング/レンダリング用のネイティブ監視フェンスと UM 送信が完全に実装された後のみ考慮できます。 したがって、ドライバーは、キューごとにユーザー モードの送信をサポートする必要があります。

ドアベル

ハードウェア スケジューリングをサポートする GPU の最新世代または今後の世代でも、GPU ドアベルの概念がサポートされます。 ドアベルは、新しい作業がその作業キューに入っていることを GPU エンジンに示すメカニズムです。 ドアベルは通常、PCIe BAR (ベース アドレス バー) またはシステム メモリに登録されます。 各 GPU IHV には、ドアベルの数、システム内の配置場所などを決定する独自のアーキテクチャがあります。 Windows OS では、設計の一部としてドアベルを使用して UM 処理内容の送信を実装します。

大まかに言えば、異なる IHV と GPU によって実装されるドアベルの 2 つの異なるモデルがあります。

  • グローバル ドアベル

    グローバル ドアベル モデルでは、コンテキストとプロセス全体のすべてのハードウェア キューが 1 つのグローバル ドアベルを共有します。 ドアベルに書き込まれた値は、どの特定のハードウェア キューとエンジンに新しい処理内容が含まれているかについて GPU スケジューラに通知します。 GPU ハードウェアは、複数のハードウェア キューがアクティブに処理内容を送信し、同じグローバル ドアベルを呼び出している場合に、ポーリング メカニズムの形式を使用して処理内容をフェッチします。

  • 専用ドアベル

    専用ドアベル モデルでは、各ハードウェア キューに独自のドアベルが割り当てられ、GPU に新しい処理内容が送信されるたびに実行されます。 ドアベルが実行されると、GPU スケジューラは、新しい処理内容を送信したハードウェア キューを正確に認識します。 GPU で作成されたすべてのハードウェア キュー間で共有されるドアベルは限られています。 作成されたハードウェア キューの数が使用可能なドアベルの数を超えた場合、ドライバーは、以前のハードウェア キューまたは最近使用したハードウェア キューのドアベルを切断し、新しく作成されたキューにドアベルを割り当てて、実質的にドアベルを "仮想化" する必要があります。

ユーザーモードの処理内容送信サポートの検出

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

UM 処理内容送信機能をサポートする GPU ノードの場合、KMD の DxgkDdiGetNodeMetadata は、DXGK_NODEMETADATA_FLAGS に追加される UserModeSubmissionSupported ノード メタデータ フラグを設定します。 その後、OS は、このフラグが設定されているノード上でのみ、UMD がユーザー モード送信 HWQueue とドアベルを作成できるようにします。

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

ドアベル固有の情報のクエリを実行するため、OS は KMD の DxgkDdiQueryAdapterInfo 関数を DXGKQAITYPE_USERMODESUBMISSION_CAPS クエリ アダプター情報の種類で呼び出します。 KMD は、ユーザーモード処理内容送信のサポートの詳細を DXGK_USERMODESUBMISSION_CAPS 構造体に設定して応答します。

現時点で必要な上限は、ドアベルのメモリ サイズ (バイト単位) のみです。 Dxgkrnl には、いくつかの理由でドアベル メモリ サイズが必要です。

  • ドアベルの作成時 (D3DKMTCreateDoorbell)、DxgkrnlDoorbellCpuVirtualAddress を UMD に返します。 ドアベルは割り当ても接続もまだ行われていないため、Dxgkrnl では、これを行う前に、まずダミー ページに内部的にマップする必要があります。 ダミー ページを割り当てるには、ドアベルのサイズが必要です。
  • ドアベル接続 (D3DKMTConnectDoorbell) 時に、DxgkrnlDoorbellCpuVirtualAddress を、KMD によって提供される DoorbellPhysicalAddress に回転させる必要があります。 この場合も、Dxgkrnl はドアベルのサイズを知っている必要があります。

D3DKMTCreateHwQueue における D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission

UMD は、ユーザーモード送信モデルを使用する HWQueue を作成するため、追加された UserModeSubmission フラグを D3DDDI_CREATEHWQUEUEFLAGS に設定します。 このフラグを使用して作成された HWQueues は、通常のカーネルモード処理内容送信パスを使用できないため、キューでの処理内容送信においてドアベル メカニズムに依存する必要があります。

ユーザーモード処理内容送信 API

ユーザーモード処理内容送信をサポートするため、次のユーザーモード API が追加されています。

  • D3DKMTCreateDoorbell は、ユーザーモード処理内容送信用に D3D HWQueue のドアベルを作成します。

  • D3DKMTConnectDoorbell は、以前に作成したドアベルを D3D HWQueue に接続して、ユーザーモード処理内容送信を行います。

  • D3DKMTDestroyDoorbell は、以前に作成されたドアベルを破棄します。

  • D3DKMTNotifyWorkSubmission は、HWQueue で新しい処理内容が送信されたことを KMD に通知します。 この機能のポイントは、処理内容の送信時に KMD が関与したり認識されたりしない、待機時間の短い作業の送信パスです。 この API は、HWQueue で処理内容が送信されるたびに KMD に通知する必要があるシナリオで役立ちます。 ドライバーは、このメカニズムを特定のシナリオやまれなシナリオで使用する必要があります。このメカニズムでは、処理内容の送信ごとに UMD から KMD へのラウンドトリップが行われ、低遅延のユーザーモード送信モデルの目的が達成されないためです。

ドアベル メモリとリング バッファー割り当ての常駐モデル

  • UMD は、ドアベルを作成する前に、リング バッファーとリング バッファー制御の割り当てを常駐させる役割を担います。
  • UMD は、リング バッファーとリング バッファー制御の割り当ての有効期間を管理します。 Dxgkrnl は、対応するドアベルが破棄された場合でも、これらの割り当てを暗黙的に破棄することはありません。 UMD は、割り当てと、それらの割り当ての破棄を担当します。 ただし、ドアベルが動作している間に悪意のあるユーザーモード プログラムがこれらの割り当てを破壊するのを防ぐため、Dxgkrnl はドアベルの有効期間中はそれらの参照を取得します。
  • Dxgkrnl がリング バッファーの割り当てを破棄する唯一のシナリオは、デバイスの終了時です。 Dxgkrnl は、デバイスに関連付けられているすべての HWQueue、ドアベル、リング バッファーの割り当てを破棄します。
  • リング バッファーの割り当てが有効である限り、リング バッファー CPUVA は常に有効であり、ドアベル接続の状態に関係なく UMD がアクセスできます。 つまり、リング バッファーの常駐はドアベルに関連付けられません。
  • KMD が DXG コールバックを作成してドアベルを切断すると (つまり、状態が D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY の DxgkCbDisconnectDoorbell を呼び出すと)、Dxgkrnl はドアベル CPUVA をダミー ページまで回転させます。 リング バッファーの割り当てを削除またはマップ解除することはありません。
  • デバイスが失われたシナリオ (TDR/GPU Stop/Page など) が発生した場合、Dxgkrnl はドアベルを切断し、状態を D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT としてマークします。 ユーザー モードは、HWQueue、ドアベル、リング バッファーを破棄し、再作成する役割を担います。 この要件は、このシナリオで他のデバイス リソースを破棄して再作成する方法と似ています。

ハードウェア コンテキストの中断

OS がハードウェア コンテキストを中断すると、Dxgkrnl はドアベル接続をアクティブにし、リング バッファー (作業キュー) 割り当てを常駐させます。 この方法では、UMD はコンテキストに対するキュー処理を続行できます。コンテキストが中断されている間、この作業はスケジュールされません。 コンテキストが再開され、スケジュールされると、GPU のコンテキスト管理プロセッサ (CMP) は、新しい書き込みポインターと処理内容の送信を監視します。

このロジックは、UMD が中断されたコンテキストで D3DKMTSubmitCommand を呼び出すことができる現在のカーネルモード送信ロジックに似ています。 Dxgkrnl はこの新しいコマンドを HwQueue にエンキューしますが、スケジュールは後で行われます。

ハードウェア コンテキストの中断時および再開時には、以下の一連のイベントが発生します。

  • ハードウェア コンテキストの中断:

    1. DxgkrnlDxgkddiSuspendContext を呼び出します。
    2. KMD が、コンテキストのすべての HWQueue を HW スケジューラの一覧から削除します。
    3. Doorbell は接続されたままになり、リング バッファー/リング バッファー制御の割り当ては引き続き常駐しています。 UMD は、このコンテキストの HWQueue に新しいコマンドを書き込むことができますが、GPU はそれらを処理しません。これは、中断されたコンテキストへの現在のカーネルモード コマンドの送信に似ています。
    4. 中断された HWQueue のドアベルを犠牲にすることを KMD が選択した場合、UMD は接続を失います。 UMD はドアベルの再接続を試みることができ、KMD はこのキューに新しいドアベルを割り当てます。 その目的は、UMD を停止させることではなく、コンテキストが再開されたら HW エンジンが最終的に処理できる処理内容を UMD が引き続き送信できるようにすることです。
  • ハードウェア コンテキストの再開:

    1. DxgkrnlDxgkddiResumeContext を呼び出します。
    2. KMD が、コンテキストのすべての HWQueue を HW スケジューラの一覧に追加します。

エンジンの F 状態遷移

従来のカーネルモード処理内容送信では、Dxgkrnl が HWQueue に新しいコマンドを送信し、KMD からの完了割り込みを監視します。 このため、Dxgkrnl は、エンジンがアクティブなときとアイドル状態のときを完全に把握できます。

ユーザーモード処理内容送信では、Dxgkrnl は TDR タイムアウト周期を使用して GPU エンジンが進行しているかどうかを監視するため、2 秒の TDR タイムアウトよりも早く F1 状態への移行を開始する価値がある場合は、KMD は OS にそのように要求できます。

このアプローチを容易にするため、次の変更が加えられました。

  • DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE 割り込み型が DXGK_INTERRUPT_TYPE に追加されました。 KMD は、この割り込みを使用して、GPU 電源アクションまたはタイムアウト回復 (Active -> TransitionToF1Active -> Hung など) を必要とするエンジン状態遷移を Dxgkrnl に通知します。

  • EngineStateChange 割り込みデータ構造体が、DXGKARGCB_NOTIFY_INTERRUPT_DATA に追加されました。

  • DXGK_ENGINE_STATE 列挙型が、EngineStateChange のエンジン状態遷移を表すために追加されました。

KMD が、EngineStateChange.NewStateDXGK_ENGINE_STATE_TRANSITION_TO_F1 に設定して DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE 割り込みを発生させると、Dxgkrnl はこのエンジン上の HWQueues のすべてのドアベルを切断し、F0 から F1 への電源コンポーネント遷移を開始します。

UMD は、F1 状態の GPU エンジンに新しい処理内容を送信しようとするとき、ドアベルを再接続する必要があります。これにより、Dxgkrnl は F0 電源状態への遷移を開始します。

エンジンの D 状態遷移

D0 から D3 へのデバイス電源状態の遷移中、Dxgkrnl は HWQueue を一時停止して、ドアベルを切断し (ドアベル CPUVA をダミー ページまで回転)、DoorbellStatusCpuVirtualAddress ドアベル状態を D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY に更新します。

GPU が D3 にあるときに UMD が D3DKMTConnectDoorbell を呼び出した場合、Dxgkrnl は GPU を D0 に強制的にウェイクアップします。 Dxgkrnl は、HWQueue を再開し、ドアベル CPUVA を物理的なドアベル位置に回転させる役割も担います。

イベントの次のシーケンスが実行されます。

  • D0 から D3 への GPU の電源オフが発生します。

    1. Dxgkrnl は、GPU 上のすべての HW コンテキストに対して DxgkddiSuspendContext を呼び出します。 KMD は、HW スケジューラ一覧からこれらのコンテキストを削除します。
    2. Dxgkrnl は、すべてのドアベルを切断します。
    3. Dxgkrnl は、必要に応じて、VRAM からすべてのリング バッファー/リング バッファー制御の割り当てを削除する可能性があります。 これは、すべてのコンテキストが一時停止され、ハードウェア スケジューラの一覧から削除されて、ハードウェアが削除されたメモリを参照しないようにした後で実行されます。
  • UMD は、GPU が D3 状態のときに HWQueue に新しいコマンドを書き込みます。

    1. UMD は、ドアベルが切断されていることを認識するため、D3DKMTConnectDoorbell を呼び出します。
    2. Dxgkrnl は、D0 遷移を開始します。
    3. Dxgkrnl は、すべてのリング バッファー/リング バッファー制御の割り当てを常駐させます (削除された場合)。
    4. Dxgkrnl は、KMD の DxgkddiCreateDoorbell 関数を呼び出して、KMD がこの HWQueue のドアベル接続を確立するよう要求します。
    5. Dxgkrnl は、すべての HWContext に対して DxgkddiResumeContext を呼び出します。 KMD は、対応するキューを HW スケジューラの一覧に追加します。

ユーザーモード処理内容送信用の DDI

KMD により実装された DDI

ユーザーモード処理内容送信のサポートを実装するため、KMD 用に次のカーネルモード DDI が追加されます。

  • DxgkDdiCreateDoorbell。 UMD が D3DKMTCreateDoorbell を呼び出して HWQueue のドアベルを作成すると、Dxgkrnl は、KMD がそのドアベル構造体を初期化できるよう、この関数に対応する呼び出しを行います。

  • DxgkDdiConnectDoorbell。 UMD が D3DKMTConnectDoorbell を呼び出すと、Dxgkrnl はこの関数に対応する呼び出しを行って、KMD が物理ドアベルの場所にマッピングされた CPUVA を提供し、HWQueue オブジェクト、ドアベル オブジェクト、ドアベル物理アドレス、GPU スケジューラなどの間で必要な接続を行うことができます。

  • DxgkDdiDisconnectDoorbell。 OS は、特定のドアベルを切断するとき、この DDI で KMD を呼び出します。

  • DxgkDdiDestroyDoorbell。 UMD が D3DKMTDestroyDoorbell を呼び出すと、Dxgkrnl は、KMD がそのドアベル構造体を破棄できるよう、この関数に対応する呼び出しを行います。

  • DxgkDdiNotifyWorkSubmission。 UMD が D3DKMTNotifyWorkSubmission を呼び出すと、Dxgkrnl はこの関数に対応する呼び出しを行い、新しい処理内容送信を KMD に通知できるようにします。

Dxgkrnl によって実装された DDI

DxgkCbDisconnectDoorbell コールバックは、Dxgkrnl によって実装されます。 KMD はこの関数を呼び出して、KMD が特定のドアベルを切断する必要があることを Dxgkrnl に通知することができます。

HW キュー進行状況フェンスの変更

UM 処理内容送信モデルで実行されているハードウェア キューには、コマンド バッファーが完了したときに UMD によって生成および書き込まれる、単調に増加する進行状況フェンス値の概念があります。 Dxgkrnl が特定のハードウェア キューに保留中の作業があるかどうかを認識するため、UMD は、リング バッファーに新しいコマンド バッファーを追加して GPU に表示する直前に、キューに登録された進行状況のフェンス値を更新する必要があります。 CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress は、キューに登録された最新の値の読み取り/書き込みユーザーモード プロセス マッピングです。

UMD では、新しい送信が GPU に表示される直前にキューに登録された値が確実に更新されるようにすることが不可欠です。 以下の手順は、推奨される操作のシーケンスです。 HW キューがアイドル状態であり、最後に終了したバッファーの進行状況フェンス値が N であると想定しています。

  • 新しい進行状況フェンス値 N+1 を生成します。
  • コマンド バッファーに入力します。 コマンド バッファーの最後の命令は、N+1 への進行状況フェンス値の書き込みです。
  • *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) を N+1 に設定して、新しくキューに入れた値を OS に通知します。
  • コマンド バッファーをリング バッファーに追加して、GPU から見えるようにします。
  • ドアベルを鳴らします。

通常のプロセス終了と異常プロセス終了

以下のイベント シーケンスは、通常のプロセス終了時に発生します。

デバイス/コンテキストの HWQueue ごとに、以下の手順が実行されます。

  1. DxgkrnlDxgkDdiDisconnectDoorbell を呼び出してドアベルを切断します。
  2. Dxgkrnl は、最後にキューに入れられた HwQueueProgressFenceLastQueuedValueCPUVirtualAddress が GPU で完了するのを待機します。 リング バッファー/リング バッファー制御の割り当てが常駐したままになります。
  3. Dxgkrnl の待機が完了し、リング バッファー/リング バッファー制御の割り当て、およびドアベルオブジェクトと HWQueue オブジェクトを破棄できるようになります。

以下のイベント シーケンスは、異常プロセス終了時に発生します。

  1. Dxgkrnl がデバイスをエラーとしてマークします。

  2. Dxgkrnl は、デバイス コンテキストごとに DxgkddiSuspendContext を呼び出してコンテキストを中断します。 リング バッファー/リング バッファー制御の割り当ては引き続き常駐したままです。 KMD がコンテキストをプリエンプションし、その HW 実行リストから削除します。

  3. コンテキストの HWQueue ごとに、Dxglrnl は以下の手順を実行します。

    a. DxgkDdiDisconnectDoorbell を呼び出してドアベルを切断します。

    b. リング バッファー/リング バッファー制御の割り当て、およびドアベル オブジェクトと HWQueue オブジェクトを破棄します。

疑似コードの例

UMD における処理内容送信擬似コード

次の擬似コードは、UMD がドアベル API を使用して、処理内容を作成し、HWQueues に送信するために使用する必要のあるモデルの基本的な例です。 hHWqueue1 を、既存の D3DKMTCreateHwQueue API を使用して UserModeSubmission フラグで作成された HWQueue へのハンドルだと思ってください。

// Create a doorbell for the HWQueue
D3DKMT_CREATE_DOORBELL CreateDoorbell = {};
CreateDoorbell.hHwQueue = hHwQueue1;
CreateDoorbell.hRingBuffer = hRingBufferAlloc;
CreateDoorbell.hRingBufferControl = hRingBufferControlAlloc;
CreateDoorbell.Flags.Value = 0;

NTSTATUS ApiStatus =  D3DKMTCreateDoorbell(&CreateDoorbell);
if(!NT_SUCCESS(ApiStatus))
  goto cleanup;

assert(CreateDoorbell.DoorbellCPUVirtualAddress!=NULL && 
      CreateDoorbell.DoorbellStatusCPUVirtualAddress!=NULL);

// Get a CPUVA of Ring buffer control alloc to obtain write pointer.
// Assume the write pointer is at offset 0 in this alloc
D3DKMT_LOCK2 Lock = {};
Lock.hAllocation = hRingBufferControlAlloc;
ApiStatus = D3DKMTLock2(&Lock);
if(!NT_SUCCESS(ApiStatus))
  goto cleanup;

UINT64* WritePointerCPUVirtualAddress = (UINT64*)Lock.pData;

// Doorbell created successfully. Submit command to this HWQueue

UINT64 DoorbellStatus = 0;
do
{
  // first connect the doorbell and read status
  ApiStatus = D3DKMTConnectDoorbell(hHwQueue1);
  D3DDDI_DOORBELL_STATUS DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));

  if(!NT_SUCCESS(ApiStatus) ||  DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT)
  {
    // fatal error in connecting doorbell, destroy this HWQueue and re-create using traditional kernel mode submission.
    goto cleanup_fallback;
  }

  // update the last queue progress fence value
  *(CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) = new_command_buffer_progress_fence_value;

  // write command to ring buffer of this HWQueue
  *(WritePointerCPUVirtualAddress) = address_location_of_command_buffer;

  // Ring doorbell by writing the write pointer value into doorbell address. 
  *(CreateDoorbell.DoorbellCPUVirtualAddress) = *WritePointerCPUVirtualAddress;

  // Check if submission succeeded by reading doorbell status
  DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));
  if(DoorbellStatus == D3DDDI_DOORBELL_STATUS_CONNECTED_NOTIFY)
  {
      D3DKMTNotifyWorkSubmission(CreateDoorbell.hDoorbell);
  }

} while (DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY);

KMD でのドアベル擬似コードを犠牲にする

次の例は、KMD が専用ドアベルを使用する GPU 上の HWQueues 間で使用可能なドアベルを "仮想化" して共有する必要がある方法を示しています。

KMD の VictimizeDoorbell() 関数の擬似コード:

  • KMD は、PhysicalDoorbell1 に接続された論理ドアベル hDoorbell1 を犠牲にして切断する必要があると判断します。
  • KMD は DxgkrnlDxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue) を呼び出します。
    • Dxgkrnl は、このドアベルの UMD から見える CPUVA をダミー ページまで回転させ、状態の値を D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY に更新します。
  • KMD は制御を取り戻し、実際の犠牲化/切断を行います。
    • KMD は、hDoorbell1 を犠牲にして PhysicalDoorbell1 から切断します。
    • PhysicalDoorbell1 を使用できます

ここで、次のシナリオについて検討してください。

  1. PCI BAR には、カーネルモード CPUVA が 0xfeedfeee と等しい 1 つの物理ドアベルがあります。 HWQueue 用に作成されたドアベル オブジェクトには、この物理的なドアベル値が割り当てられます。

    HWQueue KMD Handle: hHwQueue1
    Doorbell KMD Handle: hDoorbell1
    Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell1 =>  0xfeedfeee // hDoorbell1 is mapped to 0xfeedfeee
    Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell1 => D3DDDI_DOORBELL_STATUS_CONNECTED
    
  2. OS は、異なる HWQueue2DxgkDdiCreateDoorbell を呼び出します。

    HWQueue KMD Handle: hHwQueue2
    Doorbell KMD Handle: hDoorbell2
    Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell2 => 0 // this doorbell object isn't yet assigned to a physical doorbell  
    Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY
    
    // In the create doorbell DDI, KMD doesn't need to assign a physical doorbell yet, 
    // so the 0xfeedfeee doorbell is still connected to hDoorbell1
    
  3. OS は hDoorbell2DxgkDdiConnectDoorbell を呼び出します。

    // KMD needs to victimize hDoorbell1 and assign 0xfeedfeee to hDoorbell2. 
    VictimizeDoorbell(hDoorbell1);
    
    // Physical doorbell 0xfeedfeee is now free and can be used vfor hDoorbell2.
    // KMD makes required connections for hDoorbell2 with HW
    ConnectPhysicalDoorbell(hDoorbell2, 0xfeedfeee)
    
    return 0xfeedfeee
    
    // On return from this DDI, *Dxgkrnl* maps 0xfeedfeee to process address space CPUVA i.e:
    // CpuVirtualAddressDoorbell2 => 0xfeedfeee
    
    // *Dxgkrnl* updates hDoorbell2 status to connected i.e:
    // StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_CONNECTED
    ``
    
    

GPU がグローバル ドアベルを使用する場合、このメカニズムは必要ありません。 代わりに、この例では、hDoorbell1hDoorbell2 のどちらにも同じ 0xfeedfeee 物理ドアベルが割り当てられます。