Skip to main content

その MDL の中にはいったい何があるのでしょう?

最終更新日: 2008年 1月 11日

メモリディスクリプタリスト (MDL) は、物理アドレスのセットによってバッファーを記述する、システム定義の構造体です。直接の I/O を実行するドライバーは、I/O マネージャーから MDL へのポインタを受け取り、MDL を通じてデータの読み取りおよび書き込みを行います。また、一部のドライバーは、直接の I/O を実行するとき MDL を使用して、デバイスの I/O 制御要求も満たします。

ドライバー作成者は、MDL によって記述される各ページの順序または内容について、仮定を行うべきではありません。ドライバーは、MDL によって参照される任意の場所のデータの値に依存してはならず、メモリの場所を直接に逆参照してデータを取得すべきではありません。直接の I/O 操作用のバッファーを MDL が記述している場合、I/O 要求を発行したアプリケーションは、同じページのビューをそのアドレス スペースにマップした可能性もあります。その場合、アプリケーションとドライバーは、同時にデータの変更を試みることができ、その結果エラーが生じます。

さらに、場合によって、MDL 内の場所は、メモリ マネージャーが維持している同じ物理ページを参照しません。Microsoft Windows メモリ マネージャーは、デバイス読み取り用に MDL を構築する際、転送先として使用する物理ページをロックします。ただし、どのページを保持し (もし存在するなら) どれを破棄すべきか決めるのは、単にメモリ マネージャーしだいです。メモリ マネージャーが、これらのページ内にデータを読み取り、次にそれらを破棄するのはなぜでしょう? それは、より大きなクラスタで I/O を行うと、より良いパフォーマンスが得られるからです。

たとえば、下記の図で、物理ページ自体は必ずしも隣接していませんが、ページ A、Y、Z、および B に対応するファイル オフセットと VA は論理的に隣接しています。ページ A と B は非常駐のため、メモリ マネージャーはそれらを読み取る必要があります。ページ Y と Z は、メモリ内に既に常駐しているため、読み取る必要はありません。(実際、それらは、バッキング ストアから最後に読み取られた時点から、既に変更された可能性があります。その場合、それらの内容を上書きしてしまう重大なエラーになります。) ただし、単一の操作でページ A と B を読み取れば、最初にページ A を読み取り、次にページ B を読み取るより効率的です。したがって、メモリ マネージャーは、バッキング ストアから 4 つのページ (A、Y、Z、および B) をすべて読み取る、単一の読み取り要求を発行します。そのような読み取り要求には、使用可能なメモリの量や現在のシステム使用量などに基づき、一度に読み取ることが可能な最大限のページが含まれています。

MDL

メモリ マネージャーは、要求を記述するメモリディスクリプタリスト (MDL) を構築する際、ページ A と B への有効なポインターを提供します。ただし、ページ Y と Z に対するエントリは、システム全体での単一のダミー ページ X をポイントします。メモリ マネージャーは、X を参照可能にしないので、バッキング ストアにある古い可能性のあるデータで、ダミー ページ X を埋めることができます。ただし、コンポーネントは、MDL の Y と Z のオフセットにアクセスする場合、Y と Z の代わりにダミー ページ X を参照します。

メモリ マネージャーは、任意の数の破棄されたページを単一ページとして表すことができ、そのページは、同じ MDL に複数回埋め込まれている場合があります。また、異なるドライバーに対して使用されている複数の同時実行の MDL に埋め込まれている場合もあります。したがって、破棄されたページを表す場所の内容は、いつでも変わる可能性があります。

MDL がマップするページ上のデータ値に基づき、解読の実行またはチェックサムの計算を行うドライバーは、システムが供給する MDL からポインターを逆参照して、データにアクセスしてはなりません。その代わり、正しい操作を保証するには、このようなドライバーは、ドライバーが I/O マネージャーから受け取ったシステム供給の MDL に基づき、一時的な MDL を作成する必要があります。一時的な MDL を作成するには :

  1. MmGetMdlVirtualAddressMmGetMdlByteCount を呼び出し、システムが供給する MDL のベース仮想アドレスと長さを取得します。

  2. PoolType=NonPagedPoolExAllocatePoolWithTag を呼び出し、非ページ プールからバッファーを割り当てます。システム提供の MDL の長さであるバッファー サイズを指定し、最大 1 ページの境界で丸めます。

  3. IoAllocateMdl を呼び出し、手順 2 で作成したプール バッファーのベース仮想アドレスと長さで MDL を割り当ててます。

  4. MmBuildMdlForNonpagedPool を呼び出し、一時的な MDL を更新して、その MDL が、手順 2 のプール バッファーの基礎となっている物理ページを記述するようにします。

ドライバーは、この一時的な MDL を、ドライバーのハードウェアからデータを読み取る呼び出しに渡し、次に、任意の必要な操作で、一時的な MDL によって記述されたデータの値を使用する必要があります。MmBuildMdlForNonPagedPool を呼び出して一時的な MDL を更新することにより、ドライバーは、一時的な MDL が一時ページをまったく持たないことを保証します。これにより、ドライバーは、ページの内容に対する任意の変更から保護されます。こうすれば、たとえ破棄される (複製された可能性のある) ページがシステム MDL に含まれていても、ドライバーは、不安定な内容を調べるのを避けられます。ドライバーは、操作が完了したら、try/except または try/finally ブロック内で RtlCopyMemory を使用することにより、一時的な MDL での変更されたデータを、システムが供給した MDL にコピーして戻す必要があります。

基礎ページ上のデータにアクセスせず、通常の I/O 操作の一部として MDL を使用するドライバーは、一時的な MDL を作成する必要がありません。内部的に、メモリ マネージャーは、常駐のページをすべて追跡し、それぞれがどのようにマップされたかについて記録します。ドライバーが MDL をシステム サービス ルーチンに渡して I/O を実行するとき、メモリ マネージャーは、正しいデータが使用されていることを保証します。

どうすればよいでしょうか。

  • MDL によってポイントされる任意のメモリの場所の内容が、常に有効であるとは仮定しないでください。

  • 自分のドライバーがデータの値に依存する場合、システムが供給した MDL 内のデータを、必ずダブル バッファーしてください。

詳細情報 :

WHDC Web サイト上のホワイト ペーパー
Windows のメモリ管理の進歩