効率的なメモリ使用のための 6 つのヒント
最終更新日: 2004年3月17日
トピック
この記事では、Microsoft Windows ファミリ オペレーティング システム用のカーネル モード ドライバでの、効率的なメモリ使用に関するヒントを提供します。メモリをうまく使用すれば、ドライバ パフォーマンスの向上に役立ちます。ここでは、効率的なメモリ使用に関する 6 つのヒントを提示します。
データ構造体を効率的にレイアウトし、可能な場合、それらを再使用してください
ドライバを設計する際、メモリの種類、サイズ、および有効期間に応じて、メモリ割り当てを計画してください。有効期間が類似している各割り当てを結合し、不要になったらすぐ、未使用のメモリを解放できるようにしてください。それぞれが適切に配置されることを確証できない限り、同じ割り当て内で、サイズが大幅に異なる構造体を混合しないでください。
構造体を解放し、他の用途のために後でメモリを再割り当てするのではなく、構造体を再使用してください。構造体を再使用すれば、追加の再割り当てが避けられ、メモリ プールの断片化を防ぐのに役立ちます。
ドライバは、I/O 要求の処理中、追加のメモリを必要とすることがよくあります。ドライバは、メモリ記述子リスト (MDL) または内部バッファを割り当てて、特定の I/O 要求に使用する場合があります。また、IRP を割り当てて、より低位のドライバに送る必要がある場合もあります。これらの構造体のサイズは、要求に応じて異なります。たとえば、MDL のサイズは、それが記述するバッファのサイズに依存します。
自分のドライバに、I/O のサイズを制限したり、サイズの大きい I/O 要求を分割する技法が備わっている場合、バッファを固定サイズにすることもできます。それにより、MDL のサイズが修正され、バッファが再利用可能になります。
パフォーマンスのすべての問題には、チューニングとバランスが関与するということを念頭においてください。一般的なルールとして、まれにしか生じない非常に大きい要求または小さい要求に対してではなく、最も頻繁な操作に対して最適化すべきです。
起動時に、長期の使用のため、非ページ プール メモリを割り当ててください
ドライバは通常、長期の I/O バッファ用に、非ページ プール メモリを使用します。システムの実行中、非ページ プールはしだいに断片化するため、ドライバは、長期の構造体用に必要となるメモリを前もって割り当て、デバイスが削除された際、その割り当てを解除する必要があります。たとえば、常に DMA を実行し、いくつかのイベントを作成し、lookaside リストを使用するドライバは、起動時に DriverEntry または AddDevice ルーチンでそれらのオブジェクトにメモリを割り当て、デバイス削除要求を処理する一部としてメモリを解放する必要があります。
ただし、ドライバは、過度に大きいメモリ ブロック (たとえば、数メガバイト) を事前に割り当て、そのブロック内で独自の割り当てを管理しようとすべきではありません。
適切なメモリ割り当てルーチンは、ExAllocatePoolWithTag、ExAllocatePoolWithQuotaTag、ExAllocatePoolWithTagPriority、AllocateCommonBuffer などです (ドライバのデバイスが、バス - マスタ DMA またはシステム DMA コントローラの自動初期化モードを使用する場合)。
ドライバは、プール割り当てルーチンについて、古いタグなしバージョンの代わりに、タグ付きバージョンを使用する必要があります。WinDbg や多くのテスト ツールは、メモリ割り当てを追跡するタグを使用します。プール割り当てにタグを付けると、より容易にメモリ関連のバグを見つけるのに役立ちます。
メモリを経済的に使用してください
非ページ プール メモリは、限定されたシステム リソースです。ドライバは、できるだけ経済的に I/O バッファを割り当てる必要があります。一般に、PAGE_SIZE より小さい割り当てを要求するために、メモリ割り当てサポート ルーチンを繰り返し呼び出すのは避けてください。ドライバが通常、いくつかの関連した構造体を共に使用する場合、それらの構造体を単一の割り当て内にまとめることを考慮してください。たとえば、SCSI ポート ドライバは、IRP、SCSI 要求ブロック (SRB)、および MDL を単一の割り当て内にまとめます。
DMA を使用するドライバは、例外です。DMA を実行するドライバが 1 ページのバッファをいくつか必要とし、ただし各バッファが隣接している必要がない場合、ドライバは、そのような各バッファに対し、AllocateCommonBuffer を 1 回呼び出すべきです。
このアプローチにより、隣接したアドレス スペースが節約され、メモリ割り当ての成功する可能性が向上します。
さらに、使用を計画しているメモリ割り当てルーチンが、割り当て要求を次のページ境界まで合わせるかどうか考慮してください。
ドライバが PAGE_SIZE より少ないバイト数を要求する場合、ExAllocatePoolWithTag は、要求されたバイト数を割り当てます。
ドライバが PAGE_SIZE 以上のバイト数を要求する場合、ExAllocatePoolWithTag は、サイズが PAGE_SIZE のバイト数の整数の倍数で、ページ境界に配置されたバッファを割り当てます。
PAGE_SIZE より小さいメモリ割り当ては、ページ境界を越えず、必ずしもページ境界に合わせて配置されません。その代わり、8 バイトの境界に合わせて配置されます。
AllocateCommonBuffer は、少なくとも 1 ページのメモリを常に割り当てます。ドライバが PAGE_SIZE の整数倍より少ないバイト数を要求する場合、ドライバは、最後のページ上の残りのバイトにアクセスできません。
lookaside リストを使用してください
lookaside リストは、固定サイズの、再利用可能なバッファを提供します。これらは、予想できない数だけドライバが動的に割り当てを要する可能性がある構造体用に設計されています。
lookaside リストは、ページ プールまたは非ページ プールから割り当てることができます。ドライバは、その要件に合わせて、リスト内のエントリのレイアウトとコンテンツを定義します。また、システムは、リストの状態を維持し、需要に応じて、使用可能なエントリの数を調整します。
ドライバは、lookaside リストをセットアップするために ExInitialize[N]PagedLookasideList、リストにエントリを割り当てるために ExAllocateFrom[N]PagedLookasideList、リストのエントリを解放するために ExFreeTo[N]PagedLookasideList を呼び出します。
リストのエントリ自体がページ メモリ内にある場合でも、リストの先頭は、非ページ メモリから割り当てられる必要があります。
仮想アドレス スペースを頻繁にマップしてマップ解除するのは避けてください
仮想アドレス スペースを頻繁にマップおよびマップ解除すると、translation lookaside buffer (TLB) の頻繁なフラッシュにつながるため、システム全体のパフォーマンスが下がる場合があります。TLB は、仮想アドレスから物理アドレスに変換する、プロセッサごとのキャッシュです。TLB の各エントリには、ページ テーブル エントリ (PTE) が含まれています。
新しいページを参照する仮想アドレスをシステムが変換するたびに、TLB にエントリが追加されます。TLB が一杯になると、システムは、新規エントリを追加する必要があるたびに、既存のエントリを削除する必要があります。その後、呼び出し側がアドレス スペースを再マップまたはマップ解除し、PTE が変更されるたびに、システムは、PTE を含む任意の TLB エントリを更新できるよう、すべての CPU に割り込む必要があります。
内部的に、I/O マネージャは、Irp->MdlAddress での MDL に対するこの問題を避けます。
カーネル モードのコンポーネントが MmGetSystemAddressForMdlSafe を最初に呼び出すとき、I/O マネージャは、対応する物理アドレスと共に、システム アドレスを MDL に格納します。
IRP が、完了後、I/O マネージャに戻ったとき、I/O マネージャは MDL をマップ解除します。したがって、I/O マネージャは、各 I/O 要求に対し、単一のマッピング (および単一の仮想 - 物理アドレス変換) のみを必要とします。
テストおよび確認してください
Driver Verifier (verifier.exe)、GFlags (gflags.exe)、および PoolMon (poolmon.exe) を使用し、メモリ割り当ての問題を追跡、テスト、および確認してください。
Driver Verifier は、特別なプールからメモリを割り当てて、割り当てられたメモリへのドライバのアクセスを監視できます。このツールは、割り当てられた範囲外のメモリや、解放後のメモリへのアクセスの試みを検出できます。さらに、メモリ リークもチェックします。メモリ リークは、割り当てられたが、もう使用されておらず、まだ解放されていないメモリです。Verifier は、特別なプールから要求されたメモリ割り当てや、割り当てが成功または失敗したかどうかの件数に関する統計も集めます。
GFlags は、Driver Verifier と共に動作します。GFlags を使用すれば、Driver Verifier の Special Memory Pool オプションを構成したり、または個々のメモリ割り当てで使用できるよう特別なプールを指定したりできます。
PoolMon は、メモリ割り当てに関するさまざまなデータを集め、メモリ割り当て中に割り振られたプール タグ別に並べ替えて表示します。
リソース
メモリ割り当てに関するその他の情報については、下記の URL で、Microsoft Windows Driver Development Kit (DDK) を参照してください。
http://www.microsoft.com/japan/whdc/devtools/ddk/default.mspx
| • | Kernel-Mode Driver Architecture | • | Design Guide ("Memory Management") |
|
| • | Driver Development Tools | • | Tools for Testing Drivers ("Driver Verifier"、"GFlags with Page Heap Verification"、および "PoolMon") |
|