1. 程式人生 > >Windows NT 驅動程式開發人員提示 -- 應注意避免

Windows NT 驅動程式開發人員提示 -- 應注意避免

  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 以外的任何登錄檔項。
  39. 一定不要為了載入驅動程式而在驅動程式的登錄檔項 (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 來進入關鍵段,然後可以獲取這些資源。