MDL の使用

連続する仮想メモリ アドレスの範囲にまたがる I/O バッファーは、複数の物理ページに分散でき、これらのページは不連続になることがあります。 オペレーティング システムは、 メモリ記述子リスト (MDL) を使用して、仮想メモリ バッファーの物理ページ レイアウトを記述します。

MDL は、I/O バッファーが存在する物理メモリを記述するデータの配列が続く MDL 構造体で構成されます。 MDL のサイズは、MDL が記述する I/O バッファーの特性によって異なります。 システム ルーチンは、MDL の必要なサイズを計算し、MDL を割り当てて解放するために使用できます。

MDL 構造体は半不透明です。 ドライバーは、この構造体の Next メンバーと MdlFlags メンバーにのみ直接アクセスする必要があります。 これら 2 つのメンバーを使用するコード例については、次の「例」を参照してください。

MDL の残りのメンバーは不透明です。 MDL の不透明なメンバーに直接アクセスしないでください。 代わりに、オペレーティング システムが提供する次のマクロを使用して、構造体に対する基本的な操作を実行します。

MmGetMdlVirtualAddress は、MDL によって記述された I/O バッファーの仮想メモリ アドレスを返します。

MmGetMdlByteCount は、I/O バッファーのサイズをバイト単位で返します。

MmGetMdlByteOffset は、I/O バッファーの先頭の物理ページ内のオフセットを返します。

IoAllocateMdl ルーチンを使用して MDL を割り当てることができます。 MDL を解放するには、IoFreeMdl ルーチンを使用します。 または、MmInitializeMdl ルーチンを呼び出すことによって、非ページ メモリのブロックを割り当ててから、このメモリ ブロックを MDL としてフォーマットすることもできます。

IoAllocateMdlMmInitializeMdl も、MDL 構造体の直後にあるデータ配列を初期化しません。 非ページ メモリのドライバー割り当てブロックに存在する MDL の場合は、MmBuildMdlForNonPagedPool を使用してこの配列を初期化し、I/O バッファーが存在する物理メモリを記述します。

ページング可能なメモリの場合、仮想メモリと物理メモリの対応は一時的であるため、MDL 構造体に続くデータ配列は特定の状況下でのみ有効です。 MmProbeAndLockPages を呼び出して、ページング可能なメモリを所定の位置にロックし、このデータ配列を現在のレイアウト用に初期化します。 呼び出し元が MmUnlockPages ルーチンを使用するまで、メモリはページングされません。この時点で、データ配列の内容は無効になります。

MmGetSystemAddressForMdlSafe ルーチンは 、指定されたMDLによって記述されている物理ページが、システム アドレス空間にまだマップされていない場合に、システム アドレス空間内の仮想アドレスにマップします。 この仮想アドレスは、I/O を実行するためにページを確認する必要があるドライバーに役立ちます。なぜなら、元の仮想アドレスが元のコンテキストでのみ使用でき、いつでも削除できるユーザー アドレスである可能性があるためです。

IoBuildPartialMdl ルーチンを使用して部分的な MDL をビルドすると、MmGetMdlVirtualAddress は部分的な MDL の元の開始アドレスを返します。 このアドレスは、ユーザー モード要求の結果として MDL が最初に作成された場合のユーザー モード アドレスです。 そのため、要求が発生したプロセスのコンテキスト以外では、アドレスに関連性はありません。

通常、ドライバーは、代わりに、部分的な MDL をマップする MmGetSystemAddressForMdlSafe マクロを呼び出すことによって、システム モード アドレスを作成します。 これにより、プロセス コンテキストに関係なく、ドライバーがページに安全にアクセスし続けることができます。

ドライバーが IoAllocateMdl を呼び出すときに、Irp へのポインターを IoAllocateMdlIrp パラメーターとして指定することで、新しく割り当てられた MDL に IRP を関連付けることができます。 IRPには、1 つ以上の MDL を関連付けることができます。 IRP に 1 つの MDL が関連付けられている場合、IRP の MdlAddress メンバーは、その MDL を指します。 IRP に複数の MDL が関連付けられている場合、MdlAddress は、MDL チェーンと呼ばれる IRP に関連付けられている MDL のリンクされたリスト内の最初の MDL を指します。 MDL は Next メンバーによってリンクされます。 チェーン内の最後の MDL の次の Next メンバーは NULL に設定されます。

ドライバーが IoAllocateMdl を呼び出すときに、SecondaryBuffer パラメーターに対して FAL を指定すると、IRP の MdlAddress メンバーが新しい MDL を指すよう設定されます。 SecondaryBufferTRUE の場合、ルーチンは MDL チェーンの末尾に新しい MDL を挿入します。

IRP が完了すると、システムがロックを解除し、IRP に関連付けられているすべての MDL を解放します。 システムは、I/O 完了ルーチンのキューに入る前に MDL のロックを解除し、I/O 完了ルーチンの実行後に MDL を解放します。

ドライバーは、各 MDL の Next メンバーを使用して、チェーン内の次の MDL にアクセスすることで、MDL チェーンを走査できます。 ドライバーは、Next メンバーを更新することによって、チェーンに MDL を手動で挿入できます。

通常、MDL チェーンは、単一の I/O 要求に関連付けられているバッファーの配列を管理するために使用されます。 (たとえば、ネットワーク ドライバーは、ネットワーク操作で IP パケットごとに 1 つのバッファーを使用できます)。配列内の各バッファーには、チェーン内に独自の MDL があります。 ドライバーは、要求を完了すると、バッファーを 1 つの大きなバッファーに結合します。 その後、システムは要求に割り当てられたすべての MDL を自動的にクリーンアップします。

I/O マネージャーは、I/O 要求のソースになることがよくあります。 I/O マネージャーが I/O 要求を完了すると、I/O マネージャーは IRP を解放し、IRP に接続されている MDL を解放します。 これらの MDL の一部は、デバイス スタックの I/O マネージャーの下にあるドライバーによって IRP に接続されている可能性があります。 同様に、ドライバーが I/O 要求のソースである場合、ドライバーは、I/O 要求が完了したときに IRP と IRP に接続されている MDL をクリーンアップする必要があります。

次のコード例は、IRP から MDL チェーンを解放するドライバー実装関数です。

VOID MyFreeMdl(PMDL Mdl)
{
    PMDL currentMdl, nextMdl;

    for (currentMdl = Mdl; currentMdl != NULL; currentMdl = nextMdl) 
    {
        nextMdl = currentMdl->Next;
        if (currentMdl->MdlFlags & MDL_PAGES_LOCKED) 
        {
            MmUnlockPages(currentMdl);
        }
        IoFreeMdl(currentMdl);
    }
} 

チェーン内の MDL によって記述された物理ページがロックされている場合、この関数の例は MmUnlockPages ルーチンを呼び出してページのロックを解除してから、IoFreeMdl を呼び出して MDL を解放します。 ただし、この関数の例では、IoFreeMdl を呼び出す前に、ページのマップを明示的に解除する必要はありません。 代わりに、 IoFreeMdl は MDL を解放すると、ページのマップを自動的に解除します。