天天看點

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 來進入關鍵段,然後可以擷取這些資源。

繼續閱讀