驅動開發注意事項集錦

 

1. 一定不要在沒有標註 I/O 請求數據包 (IRP) 掛起 (IoMarkIrpPending) 的情況下通過調度例程返回 STATUS_PENDING。   
2. 一定不要通過中斷服務例程 (ISR) 調用 KeSynchronizeExecution。 它會使系統死鎖。   
3. 一定不要將 DeviceObject->Flags 設置爲 DO_BUFFERED_IO 和 DO_DIRECT_IO。 它會擾亂系統並最終導致致命錯誤。 而且,一定不要在 DeviceObject->Flags 中設置 METHOD_BUFFERED、METHOD_NEITHER、METHOD_IN_DIRECT 或 METHOD_OUT_DIRECT,因爲這些值只在定義 IOCTL 時使用。   
4. 一定不要通過頁面緩衝池分配調度程序對象。 如果這樣做,將會偶爾導致系統故障檢測 (Bugcheck)。   
5. 當運行於 IRQL >= DISPATCH_LEVEL 時,一定不要通過頁面緩衝池分配內存,或訪問頁面緩衝池中的內存。 這是一個致命錯誤。   
6. 一定不要在 IRQL >= DISPATCH_LEVEL 上等候核心調度程序對象出現非零間隔。 這是一個致命錯誤。   
7. 在 IRQL >= DISPATCH_LEVEL 上執行時,一定不要調用任何導致調用線程發生直接或間接等待的函數。 這是一個致命錯誤。   
8. 一定不要把中斷請求級別 (IRQL) 降低到低於您的頂級例程被調用的級別。   
9. 如果沒有調用過 KeRaiseIrql(),則一定不要調用 KeLowerIrql()。   
10. 一定不要使處理器 (KeStallExecutionProcessor) 停止運轉的時間超過 50 微秒。   
11. 一定不要使旋轉鎖 (Spin Lock) 保持鎖定狀態的時間超過您的需要。 要使系統獲得更好的總體性能,請不要使任何系統範圍內有效的旋轉鎖的鎖定時間超過 25 微秒。   
12. 當 IRQL 大於 DISPATCH_LEVEL 時,一定不要調用 KeAcquireSpinLock 和 KeReleaseSpinLock,或 KeAcquireSpinLockAtDpcLevel 和 KeReleaseSpinLockFromDpcLevel。   
13. 一定不要通過調用 KeReleaseSpinLockFromDpcLevel 來釋放 KeAcquireSpinLock 所獲取的旋轉鎖,因爲這會使原始 IRQL 無法被還原。   
14. 一定不要在 ISR 或 SynchCritSection 例程中調用 KeAcquireSpinLock 和 KeReleaseSpinLock 或者其它任何使用可執行旋轉鎖的例程。   
15. 當您在例程中而不是在 DriverEntry 中創建設備對象時,一定不要忘記清除 DO_DEVICE_INITIALIZING 標記。   
16. 一定不要同時在不同處理器的多個線程中將延時過程調用 (DPC) 對象添加到隊列中(使用 KeInsertQueueDpc)。 這會導致致命錯誤。   
17. 一定不要通過 CutomerTimerDPC 例程釋放週期定時器。 您可以通過 DPC 例程釋放非週期定時器。   
18. 一定不要將相同的 DPC 指針傳遞給 KeSetTimer,或者 KeSetTimerEx (CustomTimerDpc) 和 KeInsertQueueDpc (CustomDpc),因爲這將導致競爭。   
19. 旋轉鎖鎖定時,一定不要調用 IoStartNextPacket。 這將使系統死鎖。   
20. 旋轉鎖鎖定時,一定不要調用 IoCompleteRequest。 這將使系統死鎖。   
21. 如果您的驅動程序設置了完成例程,那麼一定不要在沒有把完成例程設置爲 NULL 的情況下調用 IoCompleteRequest。   
22. 調用 IoCompleteRequest 之前,一定不要忘記設置 IRP 中的 I/O 狀態區。   
23. 在將 IRP 添加到隊列中或將它發送到另一個驅動程序 (IoCallDriver) 之後,一定不要調用 IoMarkPending。 在驅動程序調用 IoMarkPending 之前,IRP 可能已經完成,由此可能發生故障檢測。 對於包含完成例程的驅動程序,如果設置了 Irp->PendingReturned,則完成例程必須調用 IoMarkPending。   
24. 一定不要在已經對某個 IRP 調用 IoCompleteRequest 之後再去訪問該 IRP。   
25. 一定不要對不屬於您的驅動程序的 IRP 調用 IoCancelIrp,除非您知道該 IRP 還沒有完成。   
26. 在您的調度例程返回到調用者之前,一定不要對您的調度例程正在處理的 IRP 調用 IoCancelIrp。   
27. 一定不要從中間驅動程序調用 IoMakeAssociatedIrp 來爲較低的驅動程序創建 IRP。 在中間驅動程序中所獲得的 IRP 可能是已被關聯的 IRP,而您不能將其它 IRP 關聯到已經被關聯的 IRP。   
28. 一定不要對使用緩衝 I/O 而設置的 IRP 調用 IoMakeAssociatedIrp。   
29. 一定不要簡單地將指向設備 I/O 寄存器的虛擬指針解除引用並訪問這些指針。 始終使用正確的硬件抽象層 (HAL) 函數來訪問設備。   
30. 如果 IRP 或設備對象可能在 DISPATCH 級別被修改,那麼一定不要通過 ISR 來訪問 它。 在對稱多處理器系統中,這會造成數據損壞。   
31. 正在高級 IRQL 中運行時,如果數據可能被低級 IROL 代碼寫入,那麼一定不要修改該數據。 應當使用 KeSynchronizeExecution 例程。   
32. 在獲取系統範圍的取消旋轉鎖 (IoAcquireCancelSpinLock) 之前,一定不要在您的 DispatchCleanup 例程中獲取驅動程序自己的旋轉鎖(如果有的話)。 要避免可能出現的死鎖,一定要在驅動程序中遵循一致的鎖定獲取層次結構。   
33. 一定不要在取消例程中調用 IoAcquireCancelSpinLock,因爲該例程被調用時已經獲取了系統級的取消旋轉鎖。   
34. 在從取消例程返回之前,一定不要忘記調用 IoReleaseCancelSpinLock。   
35. 一定不要使用基於 IRQL 的同步,因爲它只對單處理器系統有效。 提高單處理器上的 IRQL 將不會掩蔽在其它處理器上的中斷。   
36. 一定不要對重疊的內存地址範圍使用 RtlCopyMemory。 應當使用 RtlMoveMemory。   
37. 一定不要假定頁面大小是常量,即使是用於給定的 CPU。 爲了保持可移植性,應當使用 PAGE_SIZE 以及在頭文件中所定義的其它頁面相關常量。   
38. 一定不要從引導\系統初始化階段加載的驅動程序的 DriverEntry 例程中訪問除 Registry\Machine\Hardware 和 Registry\Machine\System 以外的任何註冊表項。
不要爲了加載驅動程序而在驅動程序的註冊表項 (Registry\Machine\System\CurrentControlSet\Services) 下創建 Enum 項。 系統將動態地創建該項。   
40. 如果沒有先在註冊表中申請必需的與總線相關的 I/O 端口、內存範圍、中斷或直接內存訪問 (DMA) 通道/端口等硬件資源,一定不要初始化物理設備。   
41. 一定不要在您的 DriverEntry 例程調用 IoRegisterDriverReinitialization,除非重初始化例程返回了 STATUS_SUCCESS。   
42. IRQL 爲 PASSIVE_LEVEL 時,一定不要從被頁面調度的線程或驅動程序例程中在 Wait 參數被設置爲 TRUE 的情況下調用 KeSetEvent。 如果碰巧在調用 KeSetEvent 和 KeWait..Object(s) 之間您的例程被頁面調度出去,這類調用就會導致致命的頁面錯誤。   
43. 與上例相同的條件下,同樣不能調用 KeReleaseSemaphore 。   
44. 與上例相同的條件下,同樣不能調用 KeReleaseMutex 。
45. 一定不要通過零售的 Windows NT 驅動程序調用 KeBugCheckEx 或 KeBugCheck 來停止系統的運行,除非您遇到的是破壞系統內存並最終導致系統進入故障檢測的重要錯誤。 應當始終巧妙地處理錯誤條件。
46. 一定不要假定 IoTimer 例程將會準確地在一秒邊界處被調用,因爲任何特定 IoTimer 例程的調用間隔最終取決於系統時鐘。
47. 一定不要從核心模式的設備驅動程序調用 Win32 應用程序編程接口 (API)。
48. 一定不要使用會導致堆棧溢出的遞歸函數,因爲調用線程的核心模式堆棧不能動態增長。
49. 在處理多箇中斷的 ISR 例程中,一定不要使用中斷對象指針 (PKINTERRUPT) 來標識中斷,因爲您在 ISR 中所獲得的中斷對象地址不會始終與您通過 IoConnectInterrupt 所獲得的地址相同。 要想識別當前發生中斷的設備,應當僅使用您在 IoConnectInterrupt 中所指定的 ServiceContext 值。
50. 如果沒有清零 CustomTimerDpc (KeCancelTimer),一定不要卸載驅動程序。 如果在卸載驅動程序後啓動 DPC,它可能調用不存在的代碼,並導致系統進入故障檢測查。
51. 如果 IRP 中設置了某個驅動程序的 I/O CompletionRoutine,那麼一定要等到所有這些 IRP 完成之後,才能卸載該驅動程序。 如果卸載驅動程序後,IRP 被更低級的驅動程序完成,那麼系統會試圖執行不存在的代碼,並導致系統崩潰。
52. 一定要等到驅動程序準備好要處理某個設備中斷時,才能啓用該設備中斷。 應當只在完成驅動程序初始化之後才啓用它,執行 ISR 和 DPC 時,系統才能安全的訪問設備對象的若干私有成員。
53. 在旋轉鎖鎖定時,一定不要調用驅動程序以外的代碼,因爲這會引起死鎖。
54. 如果您的驅動程序通過 IoBuildAsynchronousFsdRequest/IoAllocateIrp 創建了一個 IRP,那麼,一定不要從您的 I/O CompletionRoutine 爲這個 IRP 返回 STATUS_MORE_PROCESSING_REQUIRED 以外的任何狀態,因爲該 IRP 沒有爲與完成有關的 I/O 管理器的處理後工作做好準備。 這樣的 IRP 應當被驅動程序顯式地釋放 (IoFreeIrp)。 如果本來沒有打算重用 IRP,可以在返回狀態 STATUS_MORE_PROCESSING_REQUIRED 之前,在 CompletionRoutine 中將它釋放。
55. 一定不要在任意的線程上下文中使用 IoBuildSynchronousFsdRequest/IoBuildDeviceIoControlRequest 來分配 IRP,因爲該 IRP 依然與該線程保持關聯 (Irp->ThreadListEntry),直到它被釋放。
56. 如果已經使用 IoAllocateIrp 在 ChargeQuota 參數被設置爲 TRUE 的情況下分配了某個 IRP,那麼一定不要對該 IRP 調用 IoInitializeIrp。 如果在 ChargeQuota 設置爲 TRUE 的情況下分配 IRP,則 I/O 管理器將把它爲該 IRP 分配內存時所用的緩衝池的相關信息保存在該 IRP 的內部標記中。

如果對這樣的 IRP 調用 IoInitializeIrp,那麼,當該函數盲目地清零整個 IRP 時,分配池信息將會丟失。 當您釋放 IRP 時,這將導致內存被破壞。 同時,一定不要重用來自 IO 管理器的 IRP。 如果要重用 IRP,應當使用 IoAllocateIrp 分配您自己的 IRP。
57. 如果在調用線程的堆棧中分配了對象,就一定不要在 KeWaitForSingleObject/KeWaitForMultipleObjects 中將 WaitMode 指定爲 UserMode。 這樣做的結果是,如果被等候的對象是在函數堆棧中創建的,那麼您必須將 WaitMode 指定爲 KernelMode 才能防止線程被頁面調度出去。
58. 在沒有對關鍵節中的代碼加以保護的情況下,一定不要在用戶模式線程的上下文中獲取諸如 ERESOURCES 和 FastMutex(Unsafe) 這類資源。
因爲獲取這些資源不會使 IRQL 提高到 APC_LEVEL,所以,如果線程在已獲取資源後被掛起(通過將 APC 加入隊列實現),它可能導致死鎖,並使系統安全性降低。 因此,應當通過顯式地將 IRQL 提高到 APC_LEVEL,或者調用 KeEnterCriticalRegion 來進入關鍵段,然後可以獲取這些資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章