INFO: Tips for Windows NT Driver Developers -- Things to Avoid
ID: Q186775
|
The information in this article applies to:
-
Microsoft Win32 Device Driver Kit (DDK) for Windows NT, versions 3.51, 4.0
SUMMARY
Following are some tips for creating Windows NT device drivers. The tips
presented apply to all technologies. You can also use this as a checklist
for troubleshooting driver problems.
You need to have a basic knowledge of Windows NT architecture and some
device driver development experience to use the information presented below
effectively. For more information on device driver development, please see
the Windows NT device driver kit (DDK), which is available through MSDN
Professional membership.
MORE INFORMATION
Following is a list of things that developers should avoid when working
with Windows NT device drivers:
- Never return STATUS_PENDING from a dispatch routine without marking the
I/O request packet (IRP) pending (IoMarkIrpPending).
- Never call KeSynchronizeExecution from an interrupt service routine
(ISR). It will deadlock your system.
- Never set DeviceObject->Flags to both DO_BUFFERED_IO and DO_DIRECT_IO.
It can confuse the system and eventually lead to fatel error. Also,
never set METHOD_BUFFERED, METHOD_NEITHER, METHOD_IN_DIRECT or
METHOD_OUT_DIRECT in DeviceObject->Flags, because these values are only
used in defining IOCTLs.
- Never allocate dispatcher objects from a paged pool. If you do, it will
cause occasional system bugchecks.
- Never allocate memory from paged pool, or access memory in paged pool,
while running at IRQL >= DISPATCH_LEVEL. It is a fatal error.
- Never wait on a kernel dispatcher object for a nonzero interval at IRQL
>= DISPATCH_LEVEL. It is a fatal error.
- Never call any function that causes the calling thread to wait directly
or indirectly while executing at IRQL >= DISPATCH_LEVEL. It is a fatal
error.
- Never lower the interrupt request level (IRQL) below the level at which
your top-level routine has been invoked.
- Never call KeLowerIrql() if you haven't called KeRaiseIrql().
- Never stall a processor (KeStallExecutionProcessor) longer than 50
microseconds.
- Never hold any spin locks longer than necessary. For better overall
system performance, do not hold any system-wide spin locks longer than
25 microseconds.
- Never call KeAcquireSpinLock and KeReleaseSpinLock, or
KeAcquireSpinLockAtDpcLevel and KeReleaseSpinLockFromDpcLevel, while
running at IRQL greater than DISPATCH_LEVEL.
- Never release a spin lock that was acquired with KeAcquireSpinLock
by calling KeReleaseSpinLockFromDpcLevel, because the original IRQL will
not be restored.
- Never call KeAcquireSpinLock and KeReleaseSpinLock or any other
routine that uses an executive spin lock from an ISR or SynchCritSection
routine(s).
- Never forget to clear DO_DEVICE_INITIALIZING flag when you create a
device object in a routine other than DriverEntry.
- Never queue a deferred procedure call (DPC) object (using
KeInsertQueueDpc) with multiple threads on different processors
simultaneously. It can lead to fatal error.
- Never deallocate a periodic timer from a CutomerTimerDPC routine.
You can deallocate nonperiodic timers from a DPC routine.
- Never pass the same DPC pointer to KeSetTimer, or KeSetTimerEx
(CustomTimerDpc) and KeInsertQueueDpc (CustomDpc), because it causes
race conditions.
- Never call IoStartNextPacket while holding a spin lock. It can
deadlock your system.
- Never call IoCompleteRequest while holding a spin lock. It can
deadlock your system.
- Never call IoCompleteRequest without setting the completion routine
to NULL if your driver sets the completion routine.
- Never forget to set the I/O status block in the IRP before calling
IoCompleteRequest.
- Never call IoMarkPending after queuing an IRP or sending it to
another driver (IoCallDriver). The IRP may be completed before the
driver calls IoMarkPending and a bugcheck might occur. For drivers with
completion routines, the completion routines must call IoMarkPending if
Irp->PendingReturned is set.
- Never touch an IRP after you have called IoCompleteRequest on it.
- Never call IoCancelIrp on an IRP that is not owned by your driver
unless you know that the IRP has not been completed yet.
- Never call IoCancelIrp for the IRP that your dispatch routine is
working on until your dispatch routine returns to caller.
- Never call IoMakeAssociatedIrp to create IRPs for lower drivers from
an intermediate driver. The IRP you get in your intermediate driver
could be an associated IRP, and you cannot associate other IRPs to an
already associated IRP.
- Never call IoMakeAssociatedIrp on an IRP that is set up to perform
buffered I/O.
- Never simply dereference virtual pointers to device I/O registers
and access them. Always use correct hardware abstraction layer (HAL)
functions to access a device.
- Never access IRP or device object fields from an ISR that may be
modified from DISPATCH_LEVEL. On a symmetric multiprocessor system this
can cause data corruption.
- Never modify data while running at high-IRQL if that data may be
written by low-IRQL code. Use the KeSynchronizeExecution routine.
- Never acquire one of the driver's own spin locks (if you have any)
in your DispatchCleanup routine, before acquiring the system-wide cancel
spin lock (IoAcquireCancelSpinLock). Following a consistant lock
acquisition hierarchy throughout your driver is essential to avoiding
potential deadlocks.
- Never call IoAcquireCancelSpinLock in your cancel routine because it
is always called with the system cancel spin lock held on its behalf.
- Never forget to call IoReleaseCancelSpinLock before returning from a
cancel routine.
- Never use IRQL-based synchronization because this works only on
single processor systems. Raising IRQL on one processor does not mask
interrupts on other processors.
- Never use RtlCopyMemory for overlapped memory address ranges. Use
RtlMoveMemory.
- Never assume page sizes are constant, even for a given CPU. Use
PAGE_SIZE and other page related constants defined in header files to
maintain portability.
- Never access any registry keys other than Registry\Machine\Hardware
and Registry\Machine\System from DriverEntry routine of a driver loaded
in Boot\System Initialization phase.
- Never create an Enum key for loading a driver under a driver's
registry key (Registry\Machine\System\CurrentControlSet\Services). The
system creates this key dynamically.
- Never attempt to initialize a physical device without claiming the
necessary bus-relative I/O ports, memory ranges, interrupt, or direct
memory access (DMA) channel/port hardware resources in the registry
first.
- Never call IoRegisterDriverReinitialization from your DriverEntry
routine unless it returns STATUS_SUCCESS.
- Never call KeSetEvent with the Wait parameter set to TRUE from a
pageable thread or pageable driver routine that runs at IRQL
PASSIVE_LEVEL. This type of call causes a fatal page fault if your
routine happens to be paged out between the calls to KeSetEvent and
KeWait..Object(s).
- Never call KeReleaseSemaphore with the Wait parameter set to TRUE
from a pageable thread or pageable driver routine that runs at IRQL
PASSIVE_LEVEL. If your routine happens to be paged out between the calls
to KeReleaseSemaphore and KeWait..Object(s), this type of a call causes
a fatal page fault.
- Never call KeReleaseMutex with the Wait parameter set to TRUE from a
pageable thread or pageable driver routine that runs at IRQL
PASSIVE_LEVEL. If your routine happens to be paged out between the calls
to KeReleaseMutex and KeWait..Object(s), this type of a call causes a
fatal page fault.
- Never call KeBugCheckEx or KeBugCheck from a retail Windows NT driver
to bring down the system, unless the error encountered is a critical
error which would corrupt system memory or eventually cause the system
to bugcheck. Always try to handle error conditions gracefully.
- Never assume that an IoTimer routine will be called precisely on a one-
second boundary because the intervals at which any particular IoTimer
routine is called ultimately depends on resolution of the system clock.
- Never call Win32s application programming interfaces (API)
from a kernel-mode device driver.
- Never use recursive functions that can cause the stack to overflow
because the calling thread's kernel-mode stack does not grow dynamically
while it is running in kernel-mode.
- Never use interrupt object pointers (PKINTERRUPT) to identify
interrupts in an ISR that handles more than one interrupt, because the
address of the interrupt object you get in the ISR will not always be
the same as the one you got from IoConnectInterrupt. You should only use
the ServiceContext value that you specify in IoConnectInterrupt to
identify the current interrupting device.
- Never unload a driver without clearing CustomTimerDpc (KeCancelTimer).
If the DPC is fired after the driver is unloaded, it could hit non
existent-code and cause the system to bugcheck.
- Never unload a driver until all the IRPs that have the I/O
CompletionRoutine of the driver set in it are completed. If the IRP gets
completed by the lower driver after your driver is unloaded, the system
could try to execute the non-existent code and cause the system to crash.
- Never enable device interrupt until your driver is ready to handle it.
You should enable only after your driver is completely initialized, and
it is safe for the system to touch the driver's internal structures in
ISR and DPC.
- Never call outside of your driver while holding a spinlock because it
can cause deadlock.
- Never forget to return STATUS_MORE_PROCESSING_REQUIRED from your I/O
CompletionRoutine for an IRP created by your driver with
IoBuildAsynchronousFsdRequest/IoAllocateIrp because the IRP is not
prepared for completion related post-processing by the I/O manager. Such
an IRP should be freed explicitly (IoFreeIrp) by the driver. If the IRP
is not meant for reuse, it can be freed in the CompletionRoutine before
returning status STATUS_MORE_PROCESSING_REQUIRED.
- Never allocate an IRP with
IoBuildSynchronousFsdRequest/IoBuildDeviceIoControlRequest in an
Arbitrary thread context because the IRP remains associated with the
thread (Irp->ThreadListEntry) until it is free.
- Never call IoInitializeIrp on an IRP that has been allocated with
IoAllocateIrp. The DDK reference documentation states that you must call
IoInitializeIrp on an IRP returned by IoAllocateIrp is wrong. This
documentation is incorrect, and a corrective Knowledge Base article is
forthcoming.
REFERENCES
MSDN Device Driver Design guide for Windows NT
Keywords :
Version : WINNT:3.51,4.0;
Platform : winnt
Issue type : kbinfo
|