ページング可能なコードまたはデータのロック

シリアル ドライバーや並列ドライバーなど、特定のカーネル モード ドライバーは、管理するデバイスが開いている場合を除き、メモリ常駐である必要はありません。 ただし、アクティブな接続またはポートがある場合は、そのポートを管理するドライバー コードの一部が、デバイスにサービスを提供するために常駐している必要があります。 ポートまたは接続が使用されていない場合は、ドライバー コードは必要ありません。 これに対し、システム コード、アプリケーション コード、またはシステム ページング ファイルを含むディスクのドライバーは、デバイスとシステムの間で常にデータを転送するため、常にメモリ常駐である必要があります。

散発的に使用されるデバイス (モデムなど) 用のドライバーは、管理するデバイスがアクティブでないときには、システム領域を解放することができます。 アクティブなデバイスにサービスを提供するために常駐する必要があるコードを 1 つのセクションに配置し、デバイスが使用されている間、ドライバがそのコードをメモリにロックする場合は、このセクションをページング可能として指定することができます。 ドライバーのデバイスが開かれると、オペレーティング システムはページング可能なセクションをメモリに取り込み、ドライバーは不要になるまでそれをロックします。

この手法は、システム CD オーディオ ドライバー コードで使用されます。 ドライバーのコードは、CD デバイスの製造元に従ってページング可能なセクションにグループ化されます。 ブランドの中には、特定のシステムに存在しない可能性があるものがあります。 また、CD-ROM がシステム上に存在する場合でも、アクセス頻度が低くなる可能性があります。このような場合、コードを CD の種類別にページング可能なセクションにグループ化することで、特定のコンピューターに存在しないデバイスのコードが読み込まれなくなります。 ただし、デバイスがアクセスされたときには、システムは適切な CD デバイスのコードを読み込みます。 その後、ドライバーは、以下に説明するように MmLockPagableCodeSection ルーチンを呼び出して、デバイスが使用されている間、そのコードをメモリにロックします。

ページング可能なコードを名前付きセクションに分離するには、次のコンパイラ指令で指示します。

#pragma alloc_text(PAGE*Xxx, *RoutineName)

ページング可能なコード セクションの名前は、"PAGE" の 4 文字で始まる必要があります。その後に、セクションを一意に識別するための、最大 4 文字 (ここでは Xxx として表しています) を続けることができます。 セクション名の最初の 4 文字 ("PAGE") は大文字にする必要があります。 RoutineName は、ページング可能なセクションに含めるエントリ ポイントを識別します。

ドライバー ファイル内のページング可能なコード セクションの最も短い有効な名前は、単に PAGE です。 たとえば、次のコード例のプラグマ ディレクティブは、RdrCreateConnection を、PAGE という名前のページング可能なコード セクションのエントリ ポイントとして識別します。

#ifdef  ALLOC_PRAGMA 
#pragma alloc_text(PAGE, RdrCreateConnection) 
#endif 

ページング可能なドライバー コードを常駐させ、メモリでロックするには、ドライバーは MmLockPagableCodeSection を呼び出し、ページング可能なコード セクションにあるアドレス (通常はドライバー ルーチンのエントリ ポイント) を渡します。 MmLockPagableCodeSection は、呼び出しで参照されたルーチンを含むセクションの内容全体をロックします。 つまり、この呼び出しにより、同じ PAGEXxx 識別子に関連付けられているすべてのルーチンがメモリ内に常駐するようになり、ロックされます。

MmLockPagableCodeSection は、セクションのロックを (MmUnlockPagableImageSection ルーチンを呼び出すことによって) 解除するとき、またはドライバーがそのコード内の他の場所からもセクションをロックする必要があるときに使用するハンドルを返します。

また、ドライバーは、ほとんど使用されていないデータをページング可能として扱い、サポートされているデバイスがアクティブになるまでページ アウトすることもできます。 たとえば、システム ミキサー ドライバーでページング可能なデータを使用します。 ミキサー デバイスには関連付けられている非同期 I/O がないため、このドライバーではデータをページング可能にすることができます。

ページング可能なデータ セクションの名前は、4 文字の "PAGE" で始まる必要があり、その後に、このセクションを一意に識別するための最大 4 文字を付けることができます。 セクション名の最初の 4 文字 ("PAGE") は大文字にする必要があります。

コード セクションとデータ セクションには同じ名前を割り当てないでください。 ソース コードを読みやすくするために、ドライバー開発者は一般的に、PAGE という名前をページング可能なコード セクションに割り当てます。これは、この名前が短く、多数の alloc_text プラグマ ディレクティブに含まれる可能性があるためです。 ドライバーに必要なページング可能なデータ セクションには、長い名前 (たとえば、data_seg の場合は PAGEDATA、bss_seg の場合は PAGEBSS など) が割り当てられます。

たとえば、次のコード例の最初の 2 つのプラグマ ディレクティブでは、PAGEDATA と PAGEBSS という 2 つのページング可能なデータ セクションを定義しています。 PAGEDATA は、data_seg pragma ディレクティブを使用して宣言され、初期化されたデータを含みます。 PAGEBSS は、bss_seg pragma ディレクティブを使用して宣言され、初期化されていないデータが含まれます。

#pragma data_seg("PAGEDATA")
#pragma bss_seg("PAGEBSS")

INT Variable1 = 1;
INT Variable2;

CHAR Array1[64*1024] = { 0 };
CHAR Array2[64*1024];

#pragma data_seg()
#pragma bss_seg()

このコード例では、Variable1Array1 は明示的に初期化されているため、PAGEDATA セクションに配置されます。 Variable2Array2 は、暗黙的にゼロ初期化され、PAGEBSS セクションに配置されます。

グローバル変数をゼロに暗黙的に初期化すると、ディスク上の実行可能ファイルのサイズが小さくなり、明示的なゼロ初期化よりも推奨されます。 特定のデータ セクションに変数を配置するために必要な場合を除いて、明示的なゼロ初期化は避ける必要があります。

データ セクションをメモリに常駐させ、メモリ内でロックするには、ドライバーは MmLockPagableDataSection を呼び出し、ページング可能なデータ セクションに表示されるデータ項目を渡します。 MmLockPagableDataSection は、後続のロックまたはロック解除要求で使用されるハンドルを返します。

ロックされたセクションのページング可能な状態を復元するには、MmUnlockPagableImageSection を呼び出し、必要に応じて MmLockPagableCodeSection または MmLockPagableDataSection によって返されるハンドル値を渡します。 ドライバーの Unload ルーチンは、ロック可能なコードとデータ セクションに対して取得した各ハンドルを解放するために、MmUnlockPagableImageSection を呼び出す必要があります。

ページをメモリにロックする前に、メモリ マネージャーは読み込まれたモジュール リストを検索する必要があるため、セクションのロックはコストの高い操作です。 ドライバーは、コード内の多くの場所からセクションをロックする場合は、MmLockPagableXxxSection への最初の呼び出しの後に、より効率的な MmLockPagableSectionByHandle を使用する必要があります。

MmLockPagableSectionByHandle に渡されるハンドルは、MmLockPagableCodeSection または MmLockPagableDataSection への以前の呼び出しによって返されたハンドルです。

メモリ マネージャーは各セクション ハンドルのカウントを保持し、ドライバーがそのセクションの MmLockPagableXxx を呼び出すたびにこのカウントをインクリメントします。 MmUnlockPagableImageSection を呼び出すと、カウントはデクリメントされます。 セクション ハンドルのカウンターがゼロでない間、そのセクションはメモリにロックされたままとなりす。

セクションのハンドルは、ドライバーが読み込まれている限り有効です。 したがって、ドライバーが MmLockPagableXxxSection を呼び出すのは 1 回だけです。 ドライバーが追加のロックの呼び出しを必要とする場合は、MmLockPagableSectionByHandle を使用する必要があります。

ドライバーが既にロックされているセクションに対して MmLockPagableXxx ルーチンを呼び出すと、メモリ マネージャーは、そのセクションの参照カウントをインクリメントします。 ロック ルーチンが呼び出されたときにセクションがページ アウトされると、メモリ マネージャーはそのセクションをページインし、その参照カウントを 1 に設定します。

この手法を使用すると、システム リソースに対するドライバーの影響を最小限に抑えることができます。 ドライバーが実行されたときに、ドライバーは常駐させる必要があるコードとデータをメモリにロックすることができます。 デバイスの未処理の I/O 要求がない場合 (つまり、デバイスが閉じている場合、またはデバイスが一度も開かなかった場合)、ドライバーは同じコードまたはデータのロックを解除して、ページアウトできるようにします。

ただし、ドライバーが割り込みを接続した後は、割り込み処理中に呼び出すことができるすべてのドライバー コードは、常にメモリ常駐である必要があります。 ページング可能にしたり、必要に応じてメモリにロックしたりすることができるデバイス ドライバーもありますが、このようなドライバーのコードとデータのコア セットの一部は、システム空間に永続的に常駐している必要があります。

コードまたはデータ セクションをロックするための、次の実装ガイドラインを検討してください。

  • Mm(Un)LockXxx ルーチンの主な用途は、通常はページングされていないコードまたはデータをページング可能にし、ページングされていないコードまたはデータとして取り込み可能にすることです。 シリアル ドライバーや並列ドライバーなどのドライバーが良い例です。そのようなドライバが管理するデバイスに開いているハンドルがない場合、コードの一部は必要なく、ページ アウトされたままにしておくことができできます。リダイレクターとサーバーも、この手法を使用できるドライバーの良い例です。 アクティブな接続がない場合は、これらのコンポーネントのどちらもページ アウトすることが可能です。

  • ページング可能なセクション全体が、メモリにロックされます。

  • ドライバーごとにコード用とデータ用のセクションを1つずつ持つのが効率的です。 多くの名前付きのページング可能なセクションがあると、一般に非効率的です。

  • 完全にページング可能なセクションと、ページングされたがロックされたオンデマンドのセクションは、別々に保持します。

  • MmLockPagableCodeSectionMmLockPagableDataSection は、頻繁に呼び出すべきではないことを覚えておいてください。 これらのルーチンは、メモリ マネージャーがセクションを読み込んだときに、負荷の大きい I/O アクティビティを生じさせることがあります。 ドライバーは、コード内の複数の場所からセクションをロックする必要がある場合は、MmLockPagableSectionByHandle を使用する必要があります。