ドライバーの IRQL 注釈
すべてのドライバー開発者は、割り込み要求レベル (IRQL) を考慮する必要があります。 IRQL は 0 ~ 31 の整数で、通常、PASSIVE_LEVEL、DISPATCH_LEVEL、APC_LEVELは、記号的に参照され、その他の数値によって参照されます。 IRQL を上げ下げするには、厳密なスタックの規範に従う必要があります。 関数は、呼び出された時と同じ IRQL を返すように目指す必要があります。 IRQL 値は、スタック内で減少していないようにする必要があります。 また、IRQL は最初に上げなければ、関数で IRQL を下げることはできません。 IRQL 注釈は、これらのルールを強制することに役立てることを目的としています。
ドライバー コードに IRQL 注釈がある場合、コード分析ツールは、この時点で関数を実行する必要があるレベルの範囲についてより適切な推論を行い、エラーをより正確に見つけることができます。 たとえば、関数を呼び出すことができる最大 IRQL を指定する注釈を追加できます。より高い IRQL で関数が呼び出された場合、コード分析ツールは不整合を識別できます。
ドライバーの関数には、適切な IRQL に関する情報を含む注釈を付ける必要があります。 追加情報が使用可能な場合は、呼び出し元の関数と呼び出された関数の両方の後続チェックでコード分析ツールに役立ちます。 注釈を追加することが、誤検知を抑制する適切な方法となる場合もあります。 ユーティリティ関数などの一部の関数は、任意の IRQL で呼び出すことができます。 この場合、IRQL 注釈が付いていないのが適切な注釈です。
IRQL の関数に注釈を付ける場合は、現在の実装に限らず、関数がどのように進化するかを考慮することが特に重要です。 たとえば、実装されている関数は、設計者が意図したよりも高い IRQL で正しく動作する可能性があります。 コードが実際に行う内容に基づいて関数に注釈を付けたくなりますが、設計者は将来の要件 (将来の機能強化や保留中のシステム要件のために IRQL の最大値を下げる必要があるなど) を認識している可能性があります。 注釈は、実際の実装からではなく、関数設計者の意図から派生している必要があります。
次の表の注釈を使用して、関数とそのパラメーターの正しい IRQL を示すことができます。 IRQL 値は Wdm.h で定義されます。
IRQL 注釈 | 説明 |
---|---|
_IRQL_requires_max_(irql) | irql は、関数を呼び出すことができる最大 IRQL です。 |
_IRQL_requires_min_(irql) | irql は、関数を呼び出すことができる最小 IRQL です。 |
_IRQL_requires_(irql) | 関数は、irql で指定された IRQL で入力する必要があります。 |
_IRQL_raises_(irql) | 関数は、指定された irql で終了しますが、現在の IRQL で終了しますが、現在の IRQLを上げる (下げない) ためだけに呼び出すことができます。 |
_IRQL_saves_ | 注釈付きパラメーターは、後で復元するために現在の IRQL を保存します。 |
_IRQL_restores_ | 注釈付きパラメーターには、関数が返されたときに復元されるIRQL_saves からの IRQL 値が含まれています。 |
_IRQL_saves_global_(kind, param) | 現在の IRQL は、IRQL が復元されるコード分析ツールの内部にある場所に保存されます。 この注釈は、関数に注釈を付けるために使用されます。 場所は種類によって識別され、パラメーターによってさらに絞り込まれます。 たとえば、OldIrql はその種類で、FastMutex はその古い IRQL 値を保持するパラメーターである可能性があります。 |
_IRQL_restores_global_(kind, param) | IRQL_saves_global で注釈が付けられた関数によって保存された IRQL は、コード分析ツールの内部にある場所から復元されます。 |
_IRQL_always_function_min_(value) | IRQL 値は、関数が IRQL を下げことができる最小値です。 |
_IRQL_always_function_max_(value) | IRQL 値は、関数が IRQL を上げることができる最大値です。 |
_IRQL_requires_same_ | 注釈付きの関数は、同じ IRQL で開始および終了する必要があります。 この関数は IRQL を変更できますが、終了する前に IRQL を元の値に復元する必要があります。 |
_IRQL_uses_cancel_ | 注釈付きパラメーターは、DRIVER_CANCEL コールバック関数によって復元される必要がある IRQL 値です。 ほとんどの場合、代わりに IRQL_is_cancel 注釈を使用します。 |
DRIVER_CANCEL の注釈
_IRQL_uses_cancel_ 注釈と _IRQL_is_cancel_ 注釈には違いがあります。 _IRQL_uses_cancel_ 注釈は、注釈付きパラメーターが、DRIVER_CANCEL コールバック関数によって復元される IRQL 値であることを指定するだけです。 _IRQL_is_cancel_ 注釈は、_IRQL_uses_cancel_ に加えて、DRIVER_CANCEL コールバック ユーティリティ関数の正しい動作を確実にする他のいくつかの注釈で構成される複合注釈です。 単独では、_IRQL_uses_cancel_ 注釈は場合によって役に立ちます。たとえば、_IRQL_is_cancel_ によって説明されている残りの義務が既に他の方法で満たされている場合などです。
IRQL 注釈 | 説明 |
---|---|
_IRQL_is_cancel_ | 注釈付きパラメーターは、DRIVER_CANCEL コールバック関数の呼び出しの一部として渡される IRQL です。 この注釈は、関数が Cancel ルーチンから呼び出されるユーティリティであり、キャンセル スピン ロックの解放など、DRIVER_CANCEL 関数の要件を満たすことを示しています。 |
IRQL 注釈の対話方法
IRQL パラメーターの注釈は、他の注釈よりもたくさん対話します。IRQL 値が、さまざまな呼び出された関数によって設定、リセット、保存、復元されるためです。
最大 IRQL と最小 IRQL の指定
_IRQL_requires_max_ 注釈と _IRQL_requires_min_ 注釈では、指定した値以外の IRQL から関数を呼び出さないでください。 たとえば、PREfast で IRQL を変更しない一連の関数呼び出しが検出されると、近くの _IRQL_requires_min_ より下の _IRQL_requires_max_ 値を持つ関数呼び出しが見つかると、2 回目の呼び出しで検出された警告が報告されます。 エラーは、最初の呼び出しで実際に発生する可能性があります。このメッセージは、競合の残りの半分が発生した場所を示します。
関数の注釈が IRQL を言及し、_IRQL_requires_max_ が明示的に適用されない場合、Code Analysis ツールは暗黙的に注釈 _IRQL_requires_max_(DISPATCH_LEVEL) を適用します。これは通常、まれな例外を除き、正しいです。 これを既定として暗黙的に適用すると、多くの注釈が乱雑になり、例外がたくさん目立つようになります。
_IRQL_REQUIRES_MIN_(PASSIVE_LEVEL) 注釈は常に暗黙的です。IRQL は下がる可能性がないためです。したがって、最小 IRQL に関する対応する明示的な規則はありません。 DISPATCH_LEVEL 以外の上限と PASSIVE_LEVEL 以外の下限の両方を持つ関数はほとんどありません。
一部の関数は、呼び出された関数が IRQL を一部の最大値を超えて安全に上げることができないコンテキストの場合に呼び出されます。また、多くの場合、IRQL をある程度の最小値を下回って安全に下げることはできません。 _IRQL_always_function_max_ 注釈と _IRQL_always_function_min_ 注釈 は、PREfast が意図せず発生するケースを見つけるのに役立ちます。
たとえば、DRIVER_STARTIO 型の関数には、_IRQL_always_function_min_(DISPATCH_LEVEL) と一緒に注釈が付いています。 これは、DRIVER_STARTIO 関数の実行中に、IRQL を DISPATCH_LEVEL 以下に下げることがエラーであることを意味します。 その他の注釈は、DISPATCH_LEVEL で関数を開始して終了する必要があることを示しています。
明示的な IRQL の指定
_IRQL_raises_ または _IRQL_requires_ 注釈を使用すると、PREfast は IRQL を認識するため、_IRQL_requires_max_ または_IRQL_requires_min_ 注釈で検出された不整合をより適切に報告できます。
_IRQL_raises_ 注釈は、IRQL が新しい値に設定された状態で関数が返されることを示します。 _IRQL_raises_ 注釈を使用すると、_drv_maxFunctionIRQL 注釈も同じ IRQL 値に効果的に設定されます。 ただし、関数が IRQL を最終的な値よりも大きくし、最終的な値に下げる場合は、irQL 値を大きくできるように、_IRQL_raises_ 注釈の後に明示的な _IRQL_always_function_max_ 注釈を追加する必要があります。
IRQL の上げ下げ
_IRQL_raises_ 注釈は、関数が IRQL を上げるためにのみ使用する必要があり、関数の構文で許可される場合でも、IRQL を下げるために使用してはならないことを示します。 KeRaiseIrql は、IRQL を下げるために使用すべきではない関数の例です。
IRQL の保存と復元
_IRQL_saves_ 注釈と _IRQL_restores_ 注釈を使用して、現在の IRQL が注釈付きパラメーターに保存されるか、注釈付きパラメーターから復元されるかを示します (正確に認識されている、またはおおよそでしか認識されていないかを示します)。
一部の関数では、IRQL を暗黙的に保存して復元します。 たとえば、ExAcquireFastMutex システム関数は、最初のパラメーターで識別される高速ミューテックス オブジェクトに関連付けられている不透明な場所に IRQL を保存します。保存された IRQL は、その高速ミューテックス オブジェクトの対応する ExReleaseFastMutex 関数によって復元されます。 これらのアクションを明示的に示すには、_IRQL_saves_global_ 注釈と _IRQL_restores_global_ 注釈を使用します。 kind パラメーターと param パラメーターは、IRQL 値が保存される場所を示します。 値を保存および復元する注釈に一貫性がある限り、値を保存する場所を正確に指定する必要はありません。
同じ IRQL の維持
ドライバーが作成する IRQL を変更する関数には、_IRQL_requires_same_ 注釈または他の IRQL 注釈のいずれかを使用して注釈を付け、IRQL の変更が予想されることを示す必要があります。 IRQL の変更を示す注釈がない場合、コード分析ツールは、関数が入力されたのと同じ IRQL で終了しない関数に対して警告を発行します。 IRQL の変更が目的の場合は、エラーを抑制するための適切な注釈を追加します。 IRQL の変更が目的でない場合は、コードを修正する必要があります。
I/O キャンセル ルーチン用の IRQL の保存と復元
_IRQL_uses_cancel_ 注釈を使用して、注釈付きパラメーターが、DRIVER_CANCEL コールバック関数によって復元される IRQL 値であること示します。 この注釈は、関数がキャンセル ルーチンから呼び出されるユーティリティであり、DRIVER_CANCEL 関数で行われた要件を満たすことを示します (つまり、呼び出し元の義務は免除されます)。
例として、DRIVER_CANCEL コールバック関数型の宣言を次に示します。 パラメーターの 1 つは、この関数によって復元する必要がある IRQL です。 注釈は、キャンセル関数のすべての要件を示します。
// Define driver cancel routine type. //
__drv_functionClass(DRIVER_CANCEL)
_Requires_lock_held_(_Global_cancel_spin_lock_)
_Releases_lock_(_Global_cancel_spin_lock_)
_IRQL_requires_min_(DISPATCH_LEVEL)
_IRQL_requires_(DISPATCH_LEVEL)
typedef
VOID
DRIVER_CANCEL (
_Inout_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ _IRQL_uses_cancel_ struct _IRP *Irp
);
typedef DRIVER_CANCEL *PDRIVER_CANCEL;