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 としてフォーマットすることもできます。
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 に関連付けられている MDL のリンクされたリスト内の最初の MDL を指します。 MDL は Next メンバーによってリンクされます。 チェーン内の最後の MDL の次の Next メンバーは NULL に設定されます。
ドライバーが IoAllocateMdl を呼び出すときに、SecondaryBuffer パラメーターに対して FAL を指定すると、IRP の MdlAddress メンバーが新しい MDL を指すよう設定されます。 SecondaryBuffer が TRUE の場合、ルーチンは 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 を解放すると、ページのマップを自動的に解除します。