連続する仮想メモリ アドレスの範囲にまたがる 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 として書式設定することもできます。
IoAllocateMdl も MmInitializeMdl も、MDL 構造体の直後にあるデータ配列を初期化しません。 非ページ メモリのドライバー割り当てブロックに存在する MDL の場合は、 MmBuildMdlForNonPagedPool を使用してこの配列を初期化し、I/O バッファーが存在する物理メモリを記述します。
ページング可能メモリの場合、仮想メモリと物理メモリの対応は一時的であるため、MDL 構造体に続くデータ配列は特定の状況下でのみ有効です。 MmProbeAndLockPages を呼び出して、ページング可能なメモリを所定の位置にロックし、現在のレイアウト用にこのデータ配列を初期化します。 呼び出し元が MmUnlockPages ルーチンを使用するまで、メモリはページングされません。この時点で、データ配列の内容は無効になります。
MmGetSystemAddressForMdlSafe ルーチンは、指定された MDL によって記述された物理ページを、システム・アドレス・スペース内の仮想アドレスにマップします (まだシステム・アドレス・スペースにマップされていない場合)。 この仮想アドレスは、元の仮想アドレスが元のコンテキストでのみ使用でき、いつでも削除できるユーザー アドレスである可能性があるため、I/O を実行するためにページを確認する必要があるドライバーに役立ちます。
IoBuildPartialMdl ルーチンを使用して部分的な MDL をビルドすると、MmGetMdlVirtualAddress は部分 MDL の元の開始アドレスを返します。 このアドレスは、ユーザー モード要求の結果として MDL が最初に作成された場合のユーザー モード アドレスです。 そのため、要求が発生したプロセスのコンテキスト外では、アドレスに関連性はありません。
通常、ドライバーは、代わりに、部分的な MDL をマップする MmGetSystemAddressForMdlSafe マクロを呼び出すことによって、システム モード アドレスを作成します。 これにより、プロセス コンテキストに関係なく、ドライバーがページに安全にアクセスし続けることができます。
ドライバーが IoAllocateMdl を呼び出すときに、Irp へのポインターを IoAllocateMdl の Irp パラメーターとして指定することで、新しく割り当てられた MDL に IRP を関連付けることができます。 IRP には、1 つ以上の MDL を関連付けることができます。 IRP に 1 つの MDL が関連付けられている場合、IRP の MdlAddress メンバーは、その MDL を指します。 IRP に複数の MDL が関連付けられている場合、 MdlAddress は、MDL チェーンと呼ばれる IRP に関連付けられている MDLs のリンクされたリスト内の最初の MDL を指します。 MDL はその Next メンバーによってリンクされています。 チェーン内の最後の MDL の 次 のメンバーは NULL に設定されます。
ドライバーが IoAllocateMdl を呼び出すときに、SecondaryBuffer パラメーターに FALSE を指定する場合、IRP の MdlAddress メンバーが新しい MDL を指すよう設定されます。 SecondaryBuffer が TRUE の場合、ルーチンは MDL チェーンの末尾に新しい MDL を挿入します。
IRP が完了すると、システムがロックを解除し、IRP に関連付けられているすべての MDLs を解放します。 システムは、I/O 完了ルーチンのキューに入る前に MDL のロックを解除し、I/O 完了ルーチンの実行後に解放します。
ドライバーは、各 MDL の Next メンバーを使用して、チェーン内の次の MDL にアクセスすることで、MDL チェーンを走査できます。 ドライバーは、 次 のメンバーを更新することによって、チェーンに MDL を手動で挿入できます。
通常、MDL チェーンは、1 つの I/O 要求に関連付けられているバッファーの配列を管理するために使用されます。 (たとえば、ネットワーク ドライバーは、ネットワーク操作で IP パケットごとに 1 つのバッファーを使用できます)。配列内の各バッファーには、チェーン内に独自の MDL があります。 ドライバーは、要求を完了すると、バッファーを 1 つの大きなバッファーに結合します。 その後、システムは要求に割り当てられたすべての MDL を自動的にクリーンアップします。
I/O マネージャーは、I/O 要求の頻繁なソースです。 I/O マネージャーが I/O 要求を完了すると、I/O マネージャーは IRP を解放し、IRP に接続されている MDLs を解放します。 これらの MDL の一部は、デバイス スタックの I/O マネージャーの下にあるドライバーによって IRP に接続されている可能性があります。 同様に、ドライバーが I/O 要求のソースである場合、ドライバーは、I/O 要求が完了したときに IRP と IRP に接続されているすべての MDLs をクリーンアップする必要があります。
例
次のコード例は、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 を呼び出す前にページのマップを明示的に解除する必要はありません。 MDL を解放するとき、IoFreeMdl は代わりにページを自動的にアンマップします。