所有驅動程式開發人員都必須考慮中斷要求層級 (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 ,以便稍後還原。 |
| _IRQL_restores_ | 批註參數包含函式傳回時要還原的 IRQL_saves 的 IRQL 值。 |
| _IRQL_saves_global_(種類, 參數) | 目前的 IRQL 會儲存到要從中還原 IRQL 的程式代碼分析工具內部的位置。 此註解可用來註解函數。 位置按類別識別,並通過參數進一步細化。 例如,OldIrql 可以是種類,而 FastMutex 可以是保留舊 IRQL 值的參數。 |
| _IRQL_restores_global_(類型, 參數) | 標註為 IRQL_saves_global 的函式所保存的 IRQL 會從程式碼分析工具的內部位置恢復。 |
| _IRQL_always_function_min_(值) | IRQL 值是指函式能降低 IRQL 的最小值。 |
| _IRQL_always_function_max_(數值) | IRQL 值是函式可以將 IRQL 提高到的最大值。 |
| _IRQL_requires_same_(請注意,這是程序設計中常見的宏,指定必須保持相同的中斷請求級別。) | 批註函式必須在相同的 IRQL 進入和結束。 函式可以變更 IRQL,但必須在結束之前將 IRQL 還原為其原始值。 |
| _IRQL_uses_cancel_ | 批註參數是 IRQL 值,應該由 DRIVER_CANCEL 回呼函式還原。 在大部分情況下,請改用 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_requires_max_ 和 _IRQL_requires_min_ 批註指出不應從高於或低於指定值的 IRQL 呼叫函式。 例如,當 PREfast 看到不會變更 IRQL 的函式呼叫序列時,如果它找到一個 _IRQL_requires_max_ 值低於附近_IRQL_requires_min_的函式呼叫,它會在遇到的第二個呼叫上報告警告。 錯誤實際上可能發生在第一次呼叫中;此訊息會指出另一半衝突發生的位置。
如果函式上的批註提及 IRQL ,且未明確套用_IRQL_requires_max_,程式代碼分析工具會隱含地套用批註 _IRQL_requires_max_ (DISPATCH_LEVEL),這通常是正確的,但只有極少數例外狀況。 隱含地將其應用為預設值可以消除許多註釋雜亂,並使例外更加明顯。
_IRQL_requires_min_(PASSIVE_LEVEL)註解通常是隱含的,因為 IRQL 無法降低,因此,沒有關於最低 IRQL 的相應明確規則。 很少有函數同時具有 DISPATCH_LEVEL 以外的上限和 PASSIVE_LEVEL 以外的下限。
某些函式會在特定情境下被呼叫,其中的限制使得呼叫函式無法安全地提升 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_requires_max_或_IRQL_requires_min_批註一起探索到的不一致,因為它隨後知道 IRQL。
_IRQL_raises_ 註解表示函式傳回並將 IRQL 設定為新值。 當您使用 _IRQL_raises_ 批註時,它也會有效地將 _drv_maxFunctionIRQL 批註設定為相同的 IRQL 值。 不過,如果函式將 IRQL 提高到高於最終值,然後將其降低到最終值,您應該在 _IRQL_raises_ 批註之後新增明確的 _IRQL_always_function_max_ 批註,以允許較高的 IRQL 值。
提高或降低 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_requires_same_ 批註或其他其中一個 IRQL 批註來批註驅動程式所建立的任何函式,以變更 IRQL,以指出預期的 IRQL 變更。 如果沒有指出 IRQL 中任何變更的註釋,程式代碼分析工具會針對任何未在輸入函式的相同 IRQL 結束的函式發出警告。 如果想要變更 IRQL,請新增適當的批註來隱藏錯誤。 如果 IRQL 中的變更不是預期的,則應該更正程式代碼。
儲存和恢復 I/O 取消例程的 IRQL
使用 _IRQL_uses_cancel_ 批註來指出批註參數是 IRQL 值,應該由 DRIVER_CANCEL 回呼函式還原。 ** 此註釋指出該函數是一個從取消常式呼叫的工具函數,它完成對 DRIVER_CANCEL 函數提出的要求,即解除呼叫者的義務。
例如,以下是DRIVER_CANCEL回呼函數類型的宣告。 其中一個參數是此函式應該還原的 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;