The Common Driver Design Problem This chapter introduces all the contents of all driver developers to be interested in, including the following parts: § Summarize the default hardware priority (IRQL) of the standard driver routine operation and in appropriate Some policies of IRQL to adjust support routines § About using spin locks, these spin locks are used to synchronize data or resources shared by the driver routine About using the kernel stack and backup list allocation system space General strategy of memory. § How should the driver processes the I / O error, and how the ntstatus value defines how to make all or part of the driver image can be paised § how to register the device interface to enable the other core mode and user mode code to access the device § how to avoid This chapter that affects the reliability of driver reliability, the design problem of device type decisions or design decisions, including the following: § For the minimum layer device driver, is the driver polling device, or build a wait for Kernel The thread of the defined scheduler object is used to use time or signal amount § For DMA or PIO drivers, how to maintain caches during transmission operations § For delete storage media devices (Removable-Media The driver, how to process the user caused by the user (such as providing the wrong storage medium or removing a storage medium on which the file is opened) is as follows: 16.1 Managing hardware priority 16.2 Using a spin lock 16.2.1 Providing storage space for spin lock and protected data 16.2.2 Initialization Self-rotation 16.2.3 Calling routine 16.2.4 Fast release Selfie 16.2.5 Using a spin lock 16.2.5 Preventing errors or deadlocks 16.3 Polling Device 16.4 Managing Memory 16.4.1 Using System Memory 16.4.1.1 Accessing User Space Memory 16.4.1.2 To establish MDL 16.4.1.3 for partial transfer requests. 16.4. 1.4 Remap the bus-related memory space address to virtual address 16.4.2 Using the kernel stack 16.4.3 Using a backup list (LOOKASIDE LIST) 16.5 Conformity for DMA and PIO Maintenance Cache 16.5.1 In DMA operation During refresh cache data 16.5.2 Refresh cache data during PIO operation 16.6 Error record and ntstatus value 16.6.1 Call IOALLOCATEERRORLOGENTRY 16.6.2 Plugging Error Record Pack 16.6.4 Setting the NTSTATUS value in the error record package 16.6.4 calls iowriteErrorLoGentry 16.6. 5 Define new IO_ERR_XXX 16.6.6 Define Private NTSTATUS Constants 16.7 Processing Deleting Storage Media 16.7.1 Response from a File System Verification (Check-Verify) Request 16.7.2 Notification File System Multi-Storage Media Change 16.7.3 Check Device Objects Sign 16.7.4 Establish IRP 16.8 in the intermediate layer driver to make the device Use programs and drivers to use 16.8.1 Registration device interface 16.8.2 Enable device interface available and unavailable 16.8.3 Using device interface 16.9 Speed Paging Code and Data 16.9.1 Make driver code can be paised 16.9.2 lock Speed lock Code or Data 16.9.3 For the entire driver paging 16.10 Frequent driver reliability problem 16.10.1 Buffer I / O Error 16.10.2 Error When the user space address is referenced 16.10.3 Direct I / O Error 16.10 .4 Call input and device status error 16.10.5 Errors in the Dispatch routine 16.10.6 Errors in multiprocessor environment 16.10.7 Error when processed IRP 1.1 Managing hardware priority specific devices or intermediate driver The IRQL of Cheng Decide determines which kernel mode supports the support routine. For example, some support routines require caller running on IRQL for Dispatch_level.
Other routines cannot be securely called when the caller runs in an improved (iRQL above Passive_level). Table 16.1 lists the most common standard driver routines called by the default IRQL and the Kernel defined IRQL value (from low to high). Table 16.1 Default IRQL IRQL (from low to high) shielded interrupts in this IRQL IRQL Passive_LEVEL No DISPATCH, DRIVERENTRY, ADDDEVICE, REINITIALIZE, UNLOAD routine, threads created by drivers, work Work-thread callback, file system driver Dispatch_level dispatch_level, and APC_LEVEL interrupts are blocked. Devices, clocks, and powerful errors can still happen to Startio, AdapterControl, Iotimer, Cancel (Holding Undo Self-spinning), DPCFORSR, CustomTimtimtpc, CustomDPC routine DIRQL driver interrupt objects all IRQL <= DIRQL Interrupt. Clock and Power Error Interrupt still can still occur ISR, SyncCritsection routine When running in the following three IRQL, processing IRP: § Passive_level: No processor interrupt is blocked, in the DISPATCH routine of the driver . Driverentry, addDevice, reinitialize and unload routines are also running in Passive_LEVEL, there are also system threads created by drivers § Dispatch_level: processor's Dispatch_level and APC_LEVEL interrupts are blocked, in the StartIO routine. AdapterControl, AdapterListControl, ControllerControl, Iotimer, Cancel (Holding undone burst), DPCForism, CustomTimtimtPC and CustomDPC routines are also running in Dispatch_level. § Device IRQL (DIRQL): The interrupts of all SynchronizeiRQL below or equal to the driver interrupt object on the processor are blocked, in the ISR and SyncCritSECTION routines. When running in the following two IRQL, processing IRP is processed by a higher level driver: § Passive_level: No processor interrupt is blocked, in the DISPATCH routine of the driver. DriveRentry, addDevice, reinitialize and unload routines are also running in Passive_level, and there are systems threads, workers thread callbacks or file system drivers created by drivers. § Dispatch_level: The processor's Dispatch_level and APC_LEVEL interrupts are blocked, in the IOCompletion routine of the driver. Iotimer, Cancel and CustomtimerdPC routines are also running in Dispatch_level. Sometimes, the intermediate layer and the minimum layer driver of mass storage devices are called on the IRQL equal to APC_LEVEL. In particular, this situation will occur when the file system driver sends an IRP_MJ_READ request to the low-level driver to cause page errors. Most standard driver routines run on IRQL that can only call appropriate support routines. For example, when the device driver runs on the IRQL equal to DISPATCH_LEVEL, it must call allocateAdapter or IOAllocateController.
Since most device drivers call these routines from the StartIO routine, they are usually running in Dispatch_level. It should be noted that for device drivers without STARTIO routines, because it is established and managed your own IRP queue, it is not necessary to run on the IRQL equal to Dispatch_level when it should call allocateAdapter (or IOAllocateController). Such drivers must call allocateAdapter between calling KeraiseiRQL and call KELOWERIRQL, so when it calls allocateAdapter, it can run on the required IRQL, and when the call routine is re-control, the initial IRQL can be restored. In order to be able to call support routines in the appropriate IRQL and can successfully manage hardware priorities in the driver, you should pay attention to the following cases: § Use the NEWIRQL value of the current IRQL to call KeraiseiRQL to cause a fatal error. Call KELOWERIRQL to expect to restore the initial IRQL (that is, after calling KeraiseiRQL) will also cause a fatal error. § When running on an improved IRQL, use the keys defined by kernel to call KewaitForsingleObject or KewaitFormultiPleObjects to wait in a non-zero time period to cause a fatal error. Only the driver routine running in a non-arbitrary thread and passive_level (such as the thread created by the driver, the DriveREntry routine, and the Reinitialize routine, or the DISPATCH routine that operates like most device I / O control requests. ) Can safely wait for time, semaphore, mutex or timer in a non-zero time period. § Even if you run on the passive_level, the page code will never call KESEVENT, KeeleaseSemaphore or KeeleaseMutex if you enter the WAIT parameter to TRUE. Such calls can cause a fatal page error. § The routine running on the IRQL above APC_LEVEL can neither allocate memory from the page storage pool, nor can it securely access memory in the page storage pool. If this routine causes a page error, this error will be fatal. § When the driver is called KeacquireSpinLockAtdpclevel and KereleseSpinLockFromDpclevel, it must be running on Dispatch_level. When the driver is called KEACQUIRESPINLOCK, it can run on the IRQL below Dispatch_level, but it must be released by calling keReleseSpinLock. That is, by calling KereleseSPINLOCKFROMDPCLEVEVELASPINLOCKFROMDPCLEVELEVELE release is a programming error. When the driver is running on the IRQL above Dispatch_level, it absolutely cannot call KeacquiresPinlockcKATDPCLEVEL, KeeLaESPINLOCKFROMDPCLEVEL, KEACQUIRESPINLOCK, or KereleseSpinLock. § If the caller has not run on these increasing IRQL, calling the support routine using the spin lock (such as exinterLocKedXXX) to increase the IRQL on the current processor to Dispatch_level or DIRQL. § Running the driver code on improving IRQL should be executed as soon as possible. In order to achieve good overall performance, the higher the IRQL running in routines, the more the routine execution speed is as fast as possible. For example, calling KeraiseiRQL drivers should be used as soon as possible to use KelowerIRQL. Please use the online DDK to see the reference part of the "Using the Spin" section and the routine.
1.2 Using a spin burst is a synchronous mechanism defined by the kernel mode only (kernel -Mode-only), which outputs an opaque type Kspin_Lock. When performing and running on the Windows NT / Windows 2000 SMP machine, the self-rotating lock is used to protect these shared data or resources when accessing shared data or resources simultaneously on the Windows NT / WINDOWS 2000 SMP machine. Many components, including drivers, have used spin locks. Any type of driver may all perform a spin lock using one or more. For example, most file systems use an interlocking work queue in the FSD device extension to save the IRP of the worker thread callback routine and FSD processed by the file system. The interlocking work queue is protected by performing a spin lock, which solves a problem that the FSD tries to insert the IRP into the queue, while there is a problem that other threads should cause the IRP to remove the queue. Another example is, the system floppy controller driver is used to perform a spin lock. A hierarchy work queue shared with the driver device dedicated thread and another timer object used to protect three driver routines. Each ISR driver uses an interrupt spin lock to protect data or hardware that is shared by its ISR and its SynchCritSECTION routine (usually call it) in the STARTIO and DPCFORSR routines of the driver. Interrupt Self-rotary locks are related to the interrupt object set created when the driver is called IoconnectingerRupt, which has a detailed clarification of this in the "Register ISR" section. When using a spinch in the driver, follow the following rules: § In the resident system space memory (non-page storage pool, as shown in Figure 16.3), all data or resources and corresponding The spin lock provides storage space. The driver must provide storage space for all of the execution spin locks it. However, the device driver does not require storage space for the interrupt spin, unless it has multiple vector ISR or more than one ISR, there is a detailed explanation of this in the registration ISR section. § Provide each self-spinker that uses the driver to synchronize the protected shared data or resources to initialize these spin locks first before the access to the protected shared data or resources. § Calls each support routine that uses self-spinch on the appropriate IRQL. Generally, for the execution of the spin lock, IRQL <= dispatch_level; for interrupt from the driver interrupt object, IRQL <= DIRQL. § When implementing routines, it should be performed as soon as possible when holding self-rotation locks. All routines should not be more than 25 milliseconds from the rotary lock.
§ Note when the routine is implemented. When it holds a self-lock: § Cause hardware abnormal or software abnormal § Try to access the canable memory § Make a deadlock or self-locking time exceeding 25 The recursive call of milliseconds § Try to get another spin lock (doing so may cause a deadlock) § Call an external routine that violates the above rules to see the following sections to understand these rules more invented: § 16.2.1 Self-spinning and protected data provide storage space § 16.2.2 Initialization self-spin lock § 16.2.3 Calling routines using self-rotation lock § 16.2.4 Quick release Self-lock § 16.2.5 When using a self-lock Preventing errors or deadlocks 1.2.1 is a self-locking and protected data to provide storage space as part of the device startup, and the driver must be one of the following points to protect data or resources and corresponding Self-spinning assignment residential storage: § Driver By calling iOcreateDevice Equipment Object Extend Driver By calling IOCREATECONTROLLER Controller Objects Driver By calling ExallocatePool non-page system space When the memory holds a self-rotation, if this page is not in memory, this page is not in memory, it will cause a fatal page error. Quotes invalid spin lock (originally saved in a pageable memory, and now the page it is in the page has been transferred) will also result in a fatal page error. The driver must provide storage space for the following various possible spin locks: § Call the KEACQUIRESPINLOCK and KeReleseSPINLOCK or call the non-ISR driver of KeaCquiresPinlockFromDPClevel to synchronize all spins of access to the driver definition data. Lock § Synchronize all self-rothe drivers to the driver allocated resource access by calling the resource determined to synchronize all the spin lock drivers to the driver to call the exinterlocked..list routine, however it is running higher than When Dispatch_level IRQL, it cannot call KeacquiresPinlock and KereleseSpinlock or KeequireSpinlockcKFromDpclevel. Therefore, all drivers that recall the keys, and exinterlockedxx, must do every call when the IRQL running is lower than DISPATCH_LEVEL. The driver can pass the same spin lock to ExinterLockedInsertHeadList, just like passing to another ExinterLocKedXXXX routine, the premise of doing the two routines use this spin on the same IRQL. If you want to know how to use the use of the use of the spinch, see Quick release self-locking section. In addition to providing a memory space for it, if the device driver has multiple vector ISR or more than one ISR, it must also provide storage space to another spin lock associated with the interrupt object. 1.2.2 Initializing Self-rotation locks Before calling the support routines that need to access the caller, the driver must call KeinitialIzesPinlock to initialize the corresponding rotary lock.
Need to initialize the Executive spin lock support routines as follows: § KeAcquireSpinLock and subsequent KeRelaeseSpinLock § KeAcquireSpinLockAtDpcLevel and subsequent KeRelaeseSpinLockFromDpcLevel § ExInterlockedXxx routine before calling IoConnectInterrupt and KeSynchronizeExecution, the lowest level of the driver must call KeInitializeSpinLock to initialize the storage space provided by its Interrupt self-locking. 1.2.3 Call the support routine that uses a spin lock to call KEACQUIRESPINLOCK to set the IRQL on the current processor to Dispatch_level, until the IRQL returns this IRQL to the value before the changed value is used. Therefore, when the driver is called KEACQUIRESPINLOCK, it must be executed on the IRQL below Dispatch_level. KeacquirespinLockAtdpclevel and keReleseSpinlockFromDpclevel are run faster because they have run on the Dispatch_level, so these support routines do not need to reset the IRQL on the current processor. Therefore, on most Windows NT / Windows 2000 platforms, when running on the IRQL below Dispatch_level, calling keAcquirespinlockATDPCLEVEL is a fatal error. By calling keRelreseSPINLOCKFROMDPCLEVEVEL to release the spin lock obtained with KeaAcquiresPinlock is also a fatal error because there is no recovery of the initial IRQL. The routine that holds a spin lock (such as exinterlocKedXXX) is usually running on Dispatch_level until they release this spin lock and returned to the caller. However, as long as the spinlock to the EXInterlockedXXX set is used by the driver's ISR and SYNCHCHSECTION routines, this ISR This SynchCritSECTION routine (running in DIRQL) can call a certain exinterLockedXXX routine (such as ExinterLocked. .List routine). The routine that holds the burst burst is run on the DIRQL of the associated interrupt object set. Therefore, the driver is definitely not to call the KEACQUIRESPINLOCK and KereseSPINLOCK routine from its ISR or SYNCHCRITSECTION routine, and any other routines that use the execution of the spin are not called. This call will cause the system deadlock and require the user to restart his computer. It should also be noted that if the ISR or SYNCHCRITSECTION routine of the driver calls the exinterlocked..list routine, this driver cannot be transferred to the exinterlocked. The spin lock of the .list routine. If the driver has multiple vector ISR or multiple ISRs, when running IRQL is higher than the SYNCHRONIZEIRQL value specified by the associated interrupt object, it can call KESYNCHRONIZEEXECUTION. See "Managing Hardware Priority". If you want to know more about the IRQL demand determined by the management support routine, see Online DDK. 1.2.4 Fast Release Self-rotational lock The time minimizing the time of the driver can significantly improve the performance of the driver and the overall performance of the driver and the system. For example, Figure 16.1 shows data determined by the interrupt spin lock that protects the SMP machine must be shared by the ISR and STARTIO and DPCFORSR routines.
Figure 16.1 Using interrupt self-spinker 1. Driver's ISR runs on a processor's DIRQL, and its StartIO routine runs on the DISPATCH_LEVEL of the second processor. The kernel interrupt handler holds this driver ISR in the device extension of this driver ISR, which uses to access the protected data determined by the device, such as the status or pointer of the device register (SYNCHRONIZECONTEXT). The StartIO routine that is ready to access the SYNCHRONIZECONText call KesynchronizeExecution, pass the pointer to the associated interrupt object, share the SYNCHRONIZECONTEXT and the SYNCHCRITSECTION routine for the driver (AccessDevice in Figure 16.1). KesynchronizeExecution has been loop on the second processor to prevent AccessDevice from accessing SYNCHRONIZECONTEXT until the ISR returns (thus releasing the driver's InterruptSpinLock). However, KesynchronizeExecution also increases the IRQL on the second processor so that it is equal to the value of the SYNCHRONIZEIRQL of the interrupt object, thereby preventing other devices in this processor, so ISR returns, AccessDevice can run on DIRQL . However, more advanced DIRQL interruptions of other devices, clock interrupts and power error interrupts can still occur on any of two processors. 2. When ISR queues the driver's DPCFORISR queuing and returns, the AccessDevice on the second processor runs on an IRQL equal to the associated interrupt object SynchronizeiRQL value, and accesses SynchronizeContext. At the same time, the DPCForisr on another processor runs at Dispatch_level. DPCFORSR is also ready to access SynchronizeContext, so it calls KESYNCHRONIZEEXECUTION, the call parameters are the same as the parameters of the StartIO routine in step 1. When KesynchronizeExecution gets a spin lock and represents the STARTIO routine, the synchronization routine AccessDevice provided by the driver can access SynchronizeContext. Because AccessDevice is running on the IRQL specified by the SynchronizeiRQL value, the driver's ISR until the split is released, and the spin lock can be obtained, and the same storage area can be accessed, otherwise, even if AccessDevice is running on another processor. The device is not broken. 3. AccessDevice is returned to release the spin lock. The StartIO routine continues to run on the DISPATCH_LEVEL of the second processor. Now KesynchronizeExecution runs AccessDevice on the third processor, so it can represent DPCForisr to access SynchronizeContext. However, if the device interrupt occurs before the DPCForisr calls KesynchronizeExecution in step 2, this ISR may run a spin lock in Kesynchronizeexecution and running AccessDevice on a third processor. Running on another processor. As shown in Figure 16.1, when the routine running on a processor holds a self-rotation, the other routines attempt to obtain this spin are unable to succeed. Each of which attempts to obtain a built-in lock is looped on its current processor until the holder releases this spin lock. After a self-spinker is released, there is only one routine to get it, and there is no other routines that are not obtained from the spiral lock will continue to cycle.
Any self-spinked holder is running on the IRQL, for performing a spin, in Dispatch_level; for the interrupt self-stop, in DIRQL. The caller of KeacquiresPinlock is running on Dispatch_level until they call keReleseSPINLOCK. KesynchronizeExecution The caller automatically enhances the IRQL on the current processor to the SYNCHRONIZEIRQL value of the interrupt object until the SYNCHRITSECTION routine provided by the caller exits and KesynchronizeExecution returns to control. See calling the support routine that uses the spinning lock. Keep in mind the following rules that use spin locks: § On the processor collection occupied by the spin holder or other routines, the code running on the low-grade IRQL to obtain the same self-spiram is unable to achieve. Therefore, minimizing the driver holder time can greatly improve the performance of the driver and the overall performance of the system. As shown in Figure 16.1, on the multi-processor machine, Knernel interrupts the processor executes those routines running on the same IRQL in the same IRQL press the "First-to-first service" principle. Knernel also doing the following: § When the driver routine calls KesynchronizeExecution, Knernel causes the driver's SYNCHCRITSECTION routine to call the KesynchronizeExecution processor. (See steps 1 and 3) § When the ISR of the driver puts its DPCFORSR, Knernel runs the DPC on the first available processor that IRQL below Dispatch_level. It is not necessarily that the processor that occurs in iorequestDPC. (See Step 2) On a single processor, the I / O operation of the driver interrupt drive may require serialization. But on the SMP machine, the same operation can be truly asynchronously. As shown in FIG. 16.1, the ISR of this driver can run on the CPU 4 in the SMP machine before the DPCForism starts to process those ISRs have been interrupted on the CPU1. That is, before the DPCFORSR routine or the CustomDPC routine runs, the interrupt self-lock cannot block: ISR is specified when the operation is saved on one processor, and the device is interrupted on another processor is interrupted by this ISR. Write over. Although the driver can try to serve all interrupt I / O operations to save the ISR collected data, the running of this driver is not much faster on the SMP machine. Under the premise of maintaining the portability of the Windows NT / Windows 2000 single processor and multiprocessor platform, the driver should be preserved by other technologies for DPCForisr subsequent processing. Operate the specified data. For example, the ISR can save the data specified in the IRP it transmits to the DPCForisr. One improvement to this method is: implementing the DPCForisr to query the ISR increased count value (ISR-Augment Count), with the data provided by ISR to process the number of IRPs represented by the count value, and then count values before returning Reset to 0. Of course, this count value must be protected by the interrupt of the driver because the ISR and SYNCHCHSECTION routines of the driver dynamically change its value. 1.2.5 When the driver for preventing errors or deadlocks when using a spin lock, the system performance will decrease as long as it causes hardware or software abnormalities. This means that the ISR and drivers of the driver cannot cause incorrect or traps such as page errors or algorithms when calling KesynchronizeExecution, and cannot cause software exceptions.
The routine that calls the keAcquirespinLock does not cause hardware or software abnormalities before the release of its execution spins and no longer run on the Dispatch_level. When the paging data and support routines hold self-spinch, the driver will never call any routines that access the paged data. Remember: The driver can access some support routines that access the paging data, and the driver is running on the IRQL below Dispatch_level when this call occurs. This definition of IRQL makes the driver cannot call these support routines when holding a self-lock. If you want to know more about IRQL requirements for a specific support routine, see the corresponding reference to this routine on the online DDK. Recursive attempts to recursively obtain self-spi locks will inevitably cause dead locks: Holding examples of recursive routines are cycled in the second instance to try to obtain the same self-spinch, this self-locking is not released. Using a spin lock in the recursive routine should follow the following strategies: The recursive case can not call itself when holding a self-spinch, and will never try to obtain the same spin lock when recursive call. When the recursive routine holds a self-rotation, if recursive may cause a deadlock or may cause the caller's holder more than 25 milliseconds, another driver case cannot call this recursive routine. If you want to know more about the recursive driver routine, see "Using the Core Stack". Getting a nested self-rotation lock When you hold a self-rotation, you try to get the second self-stop lock, which can also cause deadlocks or very poor driver performance. When implementing a driver holding a self-locking, you should follow the following strategies: § Driver Never call the support routines that use the spinked lock unless it is guaranteed that there is no deadlock. § Even without dead lock, the driver should not call the support routine using self-spiram unless it is not possible to provide the same driver performance and functionality. § If the driver is nested to get a spin lock, it must release those spin locks in the opposite order. That is, if the driver obtains a spin A before getting a spin lock, it must first release B, and then release A. In general, the use of nested self-spin locks should be avoided to protect overlapping shared data and resource subset or discrete set. It should be considered that if the driver uses two execution spin lock to protect discrete resources (for example, what is possible by a pair of timer objects that may be set separately by different driver routines), then what is possible. On the SMP machine, the driver intermittently occurs a deadlock when one of the two routines in the routine that holds a self-spirabock is at lock. Even if you can design a driver that is not deadlock, it is difficult to achieve successfully. On Windows NT / Windows 2000 SMP machine, it is difficult to fully debug and detect nested self-locking. In addition, the use of nested self-spin locks greatly reduces the performance of the drivers and systems. 1.3 Polling apparatuses unless there is no already, the device driver should try to avoid polling its device, and the device driver should not be used with a total time. Polling devices are a large operation, which enables the operating system to be calculated within the polling driver (Compute- Bound). To do a lot of polls conflicts with I / O operations on other devices, so that the system becomes very slow, even do not respond to users. Now developed equipment and processors running Windows NT / Windows 2000, they have advanced technology, rarely need to drive its device to ensure that the device is ready to start I / O operation or operation. However, some devices still use are previously designed, they are working with a narrow data bus, and a slow clock rate is working together. The operating system on the old-fashioned processor performs synchronization I / O, and is a single user single task.
Such devices may need to poll or use other ways waiting for the device to update its registers, especially for Windows NT / Windows 2000, because they are designed to do on new processors with wide data bus and fast clock rate. Asynchronous I / O. Although the problem of slow device is solved by writing a simple loop of an increase in the counter seems to be feasible (this can be "wasted a small amount of time when the device update register), but such drivers often cannot be in the Windows NT / Windows 2000 platform. Transplantation. The maximum value of the loop counter is required for each Windows NT / Windows 2000 platform. Moreover, if the driver is compiled with an optimized compiler, the compiler may remove the number of calls for the driver and the cycle of adding the counter. If the driver must stop waiting in the device hardware update status, follow the following implementations: § Driver can call KestallexecutionProcessor before the read device register. The driver should minimize its waiting time interval, and wait time intervals should generally do not exceed 50 milliseconds. The KESTALLEXECUTIONPROCESSOR time interval is 1 millisecond. If the time of the device update status is often more than 50 milliseconds, consider establishing a device-specific thread in the driver. 1.3.1 Driver threads slow device or rarely use the device (such as a floppy disk controller) can solve many wait problems by creating a dedicated system thread. Similarly, most file system drivers use the system worker thread and provide worker thread callback routines. Threads can call KeDelayExecutionThread Wait to the interval of the full time slice length or longer. The unit of KedelayExecutionThread Wait time interval is approximately 10 milliseconds. Because KeDelayExecutionThread is the routine driven by the timer, the unit of the waiting interval will be slightly or slightly less than 10 milliseconds, depending on the operating system platform. However, the call to this routine is portable because the specified time increment is constant. If the device driver has its own thread environment or running in the system thread environment, the device-specific thread or the most high-level driver worker thread callback routine can be extended in the shared communication area of the driver device extension, synchronization KERNEL definitions Synchronization operations on objects such as events or semapons. When its device is not used, the device dedicated thread can wait on the shared scheduler object, for example, by calling KewaitForsingleObject with a signal quantity. The wait thread does not occupy the CPU time before calling this device driver to perform I / O operations and set the amount of semaphore to the Signaled state. The driver can set the base priority of the driver dedicated or device-specific thread created by PscreateSystemThread by calling KESETBASEPRIRITYTHREAD. The driver should specify the priority to avoid the value of Runtime Priority Inversion on the SMP machine. The base priority of the thread created by the driver is too high, and the submission of the I / O request to the execution of the low priority thread of the driver. 1.4 Managing Memory Use Many drivers only used memory extended by devices assigned to their device objects as their global storage; just uses the I / O stack in IRP to use the local storage area specified by the operation. However, the driver can allocate additional system space memory as needed, and can pass a small amount of data when the internal driver routine can be called. 1.4.1 Using System Memory Figure 16.2 Indicates the relationship between Windows NT / Windows 2000 virtual memory space and their physical memory. Figure 16.2 Diabolid Space and Physical Memory As shown in Figure 16.2, the virtual memory actually corresponds to the physical memory of the paging, and the virtual address range actually corresponds to a page that is not adjacent in the CPU.
User space virtual memory and system space memory allocated from the page storage pool are always pageable. That is, any non-current processing and its data can be paging into the secondary storage area, usually on the disk. The high space (HyperSpace) in Figure 16.2 is a dedicated area of the system space address, and the memory manager is used to map the currently processed virtual address space to a series of physical pages in the CPU. Note: Any non-current handling virtual address is invisible, so its memory space is irreparable. 1.4.1.1 The driver driver accesses the user space memory cannot allocate virtual memory of the user space because they run in the kernel mode. In addition, the driver cannot access the memory through the virtual address of the user mode unless it is running in a user mode thread environment that causes the current I / O operation of the driver and it is using the virtual address of this thread. Only the highest layer driver (such as FSD) ensures that their Dispatch routine will be called in such user mode thread environments. The highest layer driver can call MMPROBEADNLOCKPAGES before establishing an IRP for a low-level driver to lock (Lock Down) user buffer. The minimum layer driver and the intermediate layer driver for buffering or direct I / O, which can rely on the I / O manager or the highest layer driver to pass the lock user buffer or system space buffer in IRP. access. 1.4.1.2 Supreme Transmission Request Create MDL If the transfer request is too large, the lower device driver cannot be processed, then the high-level driver can call IOBUILDPARTIALMDL to establish a partially transfer IRP queue for the next device driver. If the highest layer driver cannot lock the entire user buffer with MMPROBEANDLOCKPAGES on a computer limited computer, the initial request must also be split into partially transfer. For this big transmission request, the highest level driver cannot do the following: 1. Call IOBUILDSYNCHRONOSDREQUEST to assign some part of the part to transfer IRP and locked part of the user buffer. Usually the size of the lock zone is either a multiple of the Pagesize or the transmission capacity of the lower device. 2. If the low-level driver returns status_pending, use some of the transfer IRP to call IOCALLDRIVER and call KewaitForsingleObject to wait for the driver to establish an event object that transmits IRP related to its part. 3. Repeat steps 1 and 2 when it re-controls control, until all data is transmitted, then complete the initial IRP. The highest layer device driver that must be dealt with a large transmission request can use the aforementioned technique to simply use the partially transferred IRP calls itself. In addition, there is another solution, in which the highest layer device driver should do the following: 1. Call the IOAllocateMDL to assign some of the MDL that describes the user buffer. 2. Call MMPROBEANDLOCKPAGES to lock this part of the user buffer. 3. Bush the transferred data to this part of the user. 4. Call MMunlockPages and do one of the following: § If the MDL allocated in step 1 is very large, it is enough to transfer MMPReparemdLforreuse and repeat steps 2 to 4. § Otherwise, calling IOFREEMDL and repeating steps 1 through 4. 5. All data is transferred, call MMunlockPages and IOFREMDL. 1.4.1.3 Allocation System Space Memory Figure 16.2 The system space indicated by the system space is composed of a finite page storage pool and a few non-page storage pool. Non-page storage pools are always always standing. Therefore, it can be safely accessed when you run on any level of IRQL. For drivers, page storage pools can only be assigned and accessed under the following conditions: § Use the corresponding page-stored pool deficiency addresses must run on the IRQL below APC_LEVEL.
If a page error occurs when running on the IRQL higher than the APC_LEVEL, it will be a fatal error. See Managing Hardware Priorities to learn more about IRQL content. In addition to drivers or devices initialization, or uninstall (sometimes possible), the minimum layer and the intermediate layer driver rarely allocates memory from the page storage pool, as these types of drivers are typically running higher than the IRQL higher than the APC_LEVEL. Any canable storage area allocated by such drivers can only be clicked by the driver or DriveREntry, addDevice, reinitialize, if any, and unload (if any) routine can be securely accessed, these threads or routines can Saving the data, objects, and resources required to initialize only the driver or device initialization or the unloaded storage pool allocation. Because some standard driver routines run on IRQL above APC_LEVEL, the memory allocated from the page storage pool is irrevable to most intermediate layers or device driver routines. For example, the IOCOMPLETION routine of the high-level driver is executed on a dedicated thread environment and (usually) DISPATCH_LEVEL. Such drivers must never assign a pageable storage area that will be accessed by the IOCOMPLETION routine. See "Managing Hardware Priority". Assign Driver Buffer Space In order to assign I / O buffer space, the driver can call MmallocateNonCachedMemory, MmallocateContiguousMemory, AllocateCommonbuffer (if the driver's device uses the bus controller DMA or the automatic initialization mode of the system DMA controller) or ExallocatePool. When the system is running, the non-page storage pool tends to become a lot of memory fragments, so the DriveREntry routine of the driver should call these routines to establish long-term I / O buffers required for the driver. These routines (prior to except ExalkatePool) allocate memory within the boundary specified by the processor (Data-cache-line), which is determined to avoid caching and consistency problems. The driver should save as much as possible to their internal I / O buffer (if any) because the non-page storage pool is a very limited system resource. Typically, the driver should avoid repeating these support routines to request less than Page_SIZE. In order to save I / O buffer memory, remember the following facts: § Each time you call MmallocateNonCachedMemory to occupy at least a whole page in the non-page-style system space memory, no matter how large storage areas. For requests less than one page, the onpage of the page is wasted: call MmallocateNonCachedMemory drivers to access it, and it cannot be used by other kernel mode. § If the specified number of bytes is less than or equal to one page, call MmallocateContiguousMemory assigns to a memory area. For requests greater than one page, the remaining bytes in the final assigned page are wasted: call MmallocateContiguousMemory drivers that do not access it, and it cannot be used by other kernel mode. § Call allocateComMonBuffer at least one adapter object mapping register, which maps at least 1 byte and maps up to one page. If you want to know more about mapping registers and using public buffers, see "Adapter Objects and DMA Some" in Chapter 3.
Assigning a memory driver with ExallocatePool or ExallocatePoolWithTAG or calling ExallocatePool or ExallocatePoolWithtag, specifying the parameters pooltype as one of the values defined by the following: § NonPoolCachealigned: Drivers use permanent I / O buffers. If the SCSI class driver is a buffer driver for request detection, the buffer driver, which should call MmallocateNonCachedMemory or MmallocateContiGuousMemory assign permanent I / O buffer. § NonpagedPoolCachealignedMusts: Temporary but very important I / O buffer. If you store buffers of data initialization data, these data is used when system startup. § NonPAGEDPOOL: No objects or resources stored in the device extension or controller extension, the driver may access them when running IRQL above APC_LEVEL. When PoolType takes this value, if the specified Numberofbytes is less than or equal to Page_Size, ExallocatePool or ExallocatePoolwithTAG, the memory is allocated. Otherwise, the remaining bytes remaining in the final page is wasted: the caller cannot access it, and it cannot be used by other kernel mode. For example, on the X86 machine, a 5K allocation request will get two 4K pages. Page 2 The surplus 3K cannot be used by the caller or other caller. To avoid waste of non-page storage pools, the driver should effectively allocate multiple pages. For example, in this case, the driver can do two assignments, one size is equal to Page_Size, another equal to 1K, add up to 5K. § NonPoolMustsuCceed: Temporary but very important storage, drivers will release it as soon as possible. If the driver is used to fix the wrong memory, the error will make the system. § PagedPoolcachealigned: I / O buffer of the file system. The driver locks it, then the lower mass storage device driver passes it in the IRP requesting the DMA transmission. § PageDPool: If the buffer will release before the caller returns, DriveRentry or Reinitialize routines can use this value to open a temporary buffer to save the objects, data or resources required for initialization. This value can also be used to open a storage area that can only be accessed by one or more drivers. If the buffer will be released before the unload routine returns control, the unload routine of the driver can also allocate memory from the page storage pool. Because the Must-Succeed storage pool is very limited system resource, the driver should release the assigned space as soon as possible by calling EXFREEPOOL. Most drivers should not use the value of the PoolTyPE parameter for NonPagedPoolMustSucceed or NonPoolCachealignedmusts, unless the system does not continue to run if the driver's allocation request is unsuccessful. If the PoolType parameter is specified as these values, ExAllocatePool will terminate the system when the system cannot assign the requested memory. For other PoolType parameter values, if you cannot assign the request's number of NumberofBytes bytes, ExallocatePool or ExallocatePoolWithtag returns a null pointer. The driver should check the returned pointer.
If its value is NULL, the DriveREntry routine (or any other-returned NTSTATUS driver routine) should return Status_INSUFFICIENT_RESOURCES or handle errors (possible words). See "Error Record and NTSTATUS Value". For the PoolType parameter value of the Cachealigned class, ExallocatePool or ExallocatePoolWithtAg allocates memory within the boundary specified by the processor (determined by the size of the data cache range of the processor) to avoid caching and consistency issues. 1.4.1.4 Remap the bus-related memory space address to the virtual address Some processors have separate memory and I / O address space, and some do not. Due to these differences on the hardware platform, Windows 2000 and WDM drivers are used to access device resources for resident I / O or resident memory vary from platform. The driver requests device I / O and memory resources to respond to IRPs of IRP_MN_QUERY_RESOURCE_REQUIREMENTS of the PNP Manager. Depending on the hardware structure, HAL can assign I / O resources in I / O space or memory space, or allocate memory resources in I / O space or memory space. If the HAL uses the bus-related memory space to access device resources (such as device registers), the driver must map I / O space to virtual memory so that it can access these resources. The driver can determine that the resource is constant I / O by checking the PNP manager to pass the mapping resources to the driver when the device is started. If HAL uses I / O space, no mapping is required. Specifically, when the driver receives an IRP_MN_START_DEVICE request, it should check the IRPSP-> Parameters.StartDevice.allocatedResources.StartDevice.allocatedResourceStranslated structure, which describes the initial and mapping PNP manager assignment The resources of the device. The driver should save a copy of each resource list in the device extension for auxiliary use. The resource list is a pair of CM_Resource_list structure, where each element of the initial list corresponds to the same element of the converted list. For example, if allocatedResources.list [0] describes the initial I / O port range, allocateDResourceStranslated.List [0] describes the same range after the conversion. Each converted resource includes physical addresses and resource types. If the driver is assigned a converted memory resource (CMResourceTypeMemory), it must call MMMapiospace to map the physical address to the virtual address available to access the device register. For drivers operating in a platform, if needed, it should check each returned, converted resources and map it. The following is the steps that each driver responds to IRP_MN_START_DEVICE to ensure access to all device resources: 1. Copy IRPSP-> Parameters.StartDevice.allocatedResources in the device extension. 2. Copy IRPSP-> Parameters.StartDevice.allocatedResourceStranslated in the device extension. 3. In the cycle, check each description element in AllocatedResourceStranslated. If the description resource type is cmResourceTypeMemory, call MMMapiOspiospace, pass the length of the physical address and the resource after conversion.
When the driver receives IRP_MN_STOP_DEVICE or IRP_MN_REMOVE_DEVICE request from the PNP manager, it must release MMUNMapiospace release mapping in a similar loop. If the driver must reject the IRP_MN_START_DEVICE request, it should also call MMunmapiospace. The initial resource type indicates which HAL access routine should be called (read_register_xxx, write_register_xxx, read_port_xxx, write_port_xxx). Most drivers do not need to check the initial resource list to determine which one in these routines, because the driver itself has requested this resource, or the driver developer has determined the type of type when the hardware is known. . For I / O space resources (CmResourceTypePort, CmResourceTypeInterrupt, CmResourceTypeDma), drivers should be (e.g., by the HAL READ_REGISTER_Xxx, WRITE_REGISTER_Xxx, READ_PORT_Xxx, WRITE_PORT_Xxx write routine) with the lower 32 bits of the physical address to access device resources returned. 1.4.2 Using the Core Stack When the driver can pass data to its internal routine, the size of the Windows 2000 core mode stack is about two pages. Therefore, the driver cannot transmit a large amount of data on the kernel stack. In order to avoid the space with the designed stack, comply with the following design rules: avoiding the depth nested call from an internal driver routine, if they are transferred on the kernel stack. If the recursive routine is used in the driver design, pay attention to limit the number of recursive calls. That is, the calling tree structure of the driver should be flat. Since the non-page storage pool is also limited system resources, the driver is best to allocate system space buffer instead of using the downturn. The Windows 2000 kernel mode stack is in the cache, so the driver cannot transmit data on the stack with DMA. In order to avoid DMA data allocation and / or data integrity issues, comply with the following design rules: Never try to transfer data on the kernel stack with DMA. The driver of the DMA device can get a buffer of the NonPagedPoolCachealigned type by calling exallocatepoolwithtag to buffer data to be transmitted (if any). Some drivers can do this by using common buffer DMA. See "Public Buffer System DMA or Public Bus Controller DMA" in Chapter 3. 1.4.3 Using a backup list must dynamically allocate a fixed size buffer to perform the Windows 2000 and WDM drivers of the required I / O operation, you can use the EX..LOOKASIDELIST support routine. After this driver is initialized, the OS will occupy some dynamically assigned, specify the size of the buffer, and efficiently reused, fixed-size buffer. The format and content of the fixed size buffering of the driver in its post list is determined by the driver. For example, a backup list must be established for the SCSI Request Block (SRB) of the lower SCSI port / microir port (SRB). Such type drivers are allocated buffers from its backup list, and as long as SRB returns a class driver in the completed IRP, release each SRB buffer to the backup list. Since the I / O request on the driver is much less, the storage class driver cannot predetermine how many SRBs it needs to be used in advance, so the backup list in such drivers is the assignment and release of the buffer of the fixed size SRB. A convenient and economical way.
OS maintains all currently being used and the state of the non-page backup list, dynamically track the requests for allocation and release entries in all tables, and the available system storage pool of the new entry. When the allocation request is a lot, the OS adds the number of items held in each backup list. When the request is reduced, the OS will increase the rear backup table item back to the system storage pool. In the driver using the EX..LOOKASIDELIST routine, follow the following design rules: § If the driver itself or the lower driver itself is delivered to the list of list items may be accessed by IRQL above Dispatch_level or access these tables in a dedicated thread environment Item, build a non-page backup list with ExinitializenPagedLookasidelist. § Only the backup list of page-style entry is created only when access to the driver backup list item cannot cause a fatal page error. § Provide a resident storage area for the backup list header in the non-page system space, even if the driver has established a page backup list with ExinitializePagedLookasidelist. § In order to get better performance, transfer the NULL pointer for allocate and free when calling exinitialize (n), unless these optional, the routine provided by the driver is allocated to the reserve list item assignment, release memory. Other things (such as the status information of the usage of the dynamic allocation buffer). § If the driver provides an Allocate routine, when this routine calls ExallocatePoolWithtag, a given input parameter (PoolType, Tag, and Size) is used in the routine. § For each exinitialize (n) PagedLookasidelist, once the previously assigned entry is no longer used, it should be used as soon as possible to use Exfreeto (N) PagedLookasidelist. For page backup lists, an entry is assigned from a page storage pool, but the head of such a list must be in the resident memory. Allocate and Free routines are the same as those who call ExcentepoolWithtag and ExfreePool, providing them to waste CPU loops. Exallocate (N) PageSidelist and Exfreeto (N) PagedLookasidelist When the driver is sent to the exinitialize (n) PagedLookasidElist, ExAllocatePoolWithtag and ExfreePool are automatically called. The ALLOCATE case of the driver cannot allocate memory from the page storage pool to the table item in the non-page-style backup list. It must also allocate a fixed size entry because the driver's later calls later later will return the first entry in the backup list, unless the list is empty. That is, call Exallocate (N) PagedLookasidelist only calls the driver provided by the driver for the ALLOCATE routine. Therefore, each time you call Exallocate (N) PagedLookasiDelist, only the returned entry is just the size required by the driver only in all the entries in the backup list. The ALLOCATE routine provided by the driver should not change the driver to the TAG value of Exinitialize (N) PagedLookasidelist, because changes to the storage pool tag make the memory usage of the debug and track the driver become very difficult. Calling EXFREETO (N) PagedLookasiDelist will return to previously assigned, which will be saved in the backup list, unless the number of list tables has reached the maximum value determined.
In order to get better performance, the driver should do its counter-adjusting Exfreeto (N) PageDlookasidelist as soon as possible as soon as possible. When the driver quickly releases the table item back to its backup list, this driver is almost impossible to explicitly distribute the performance of additional memory to another table item to deteriorate. 1.4.4 Read-read memory protection Microsoft's Windows 2000 enhances read-only access to pages that are labeled-writable. The only user mode in the read-only is always protected. But in Windows NT 4.0 and earlier versions, it is not protected in kernel mode. If the Windows 2000 kernel mode driver or application tries to write a read-only memory segment, the system will issue an error detection (Bug Check) 0xbe. (If you want to know a description of the error detection code, see Using the Microsoft Debugger Document) Intercepting system call Some drivers intercept the system call by rewriting the driver code and inserting jump instructions or other modifications. This technique will result in a release of an error detection. Global string If a string will be modified, it will never explain it to a pointer to a constant value: char * mystring = "this string cannot be modified."; In this case, the connector may The string is placed in the read-only memory segment, so trying to modify it will result in error detection. Instead, this string should be explicitly explained as a queue of the L-Value character: char mystring [] = "this string can be modified."; This can guarantee that this string is placed in write memory in. 1.5 Consistency for the DMA and PIO Maintenance Cache On the Windows NT / Windows 2000 computer, when the driver transmits data between system memory and its devices, data can be cached in one or more processor cache and / or The system DMA controller's cache. Using DMA or PIO to read / write IRP or any driver for devices I / O control requested services for DMA or PIO data transfer, you should ensure that the integrity of data can be cached during transmission operations. These contents will be clarified in the following sections. 1.5.1 Refresh Cache data during DMA operations On some platforms, processors and system DMA controllers (or bus controller DMA adapters) exhibit cache consistency exceptions. In order to maintain data integrity during DMA operation, the minimum layer driver must follow the following rules: 1. Call Keflushiobuffers before transferring operations to keep the consistency between data and data in the processor. If the driver uses the value of the value of True Cacheenbuffle, the driver must call Keeflushiobuffers before transmitting operations from the buffer. 2. When each device transfer operation is completed, the FlushadapterBuffers is called to ensure that all remaining bytes in the system DMA controller buffer have been written to memory or slave devices. Or when each device transfer operation of a given IRP is completed, the flushadapterbuffers is called to ensure that all data has been read into the system memory or written to the bus controller DMA device. Figure 16.3 shows that if the primary processor and the DMA controller cannot automatically maintain the cache consistency, how important is it important to refresh the processor cache before using DMA read or written. Figure 16.3 Using DMA's read and write operation asynchronous DMA read or write operations to access data in the memory rather than data in the processor cache.
Unless the cache has been refreshed by calling Keflushiobuffers before the read operation, if the processor cache is refreshed later, the DMA operation transmits data to the system memory may be overwritten by the old data. Unless the cache has been refreshed by calling Keflushiobuffers before writing, the data in the cache may be new than the copy in the memory. If the processor and the DMA controller can automatically keep the cache consistency, it is not necessary to use Keflushiobuffers, so there is almost no overhead on this support routine. Figure 16.3 also shows that the DMA controller represented by the adapter object can have internal buffer. Such a DMA controller can deliver cache data at a fixed size, usually 8 or more bytes at a time. In addition, these DMA controllers can wait until their internal buffer is full before the transfer operation. For the fixed size of the slave DMA read data, the minimum layer driver of the slave DMA read data is called by the fixed size of the variable size or non-system DMA controller, unless the driver calls flushadapterbuffers after each device transfer is complete, otherwise it cannot be determined Each byte of the driver request is actually transmitted. The bus controller DMA device should also call FlushadapterBuffers after each device of the IRP, which ensures that all data has been transmitted to the system memory or transmitted the device. FlushadapterBuffers returns a Bur number, indicating whether the request is successful. The driver can use this value to set the I / O state block when completing the IRP of the DMA read or write. 1.5.2 Refresh Cache Data during PIO operation is on some platforms, and the processor's instructions and data caches exhibit cache consistency during PIO read operations. In order to maintain data integrity during their read operation, the use of PIO's driver must comply with the following rules: § Call Keeflushiobuffers after each read operation is completed. § For example, the driver transmitted from the device to the system memory should call KEFLUSHIOBUFFERS after each device transfer operation is completed. For example, a driver for reading a class of device registers in system memory should call Keeflushiobuffers after reading each class. Otherwise on some platforms, such drivers may try to access the data still in the processor data cache, rather than system memory. If the processor and the DMA controller can automatically keep the cache consistency, it is not necessary to use Keflushiobuffers, so there is almost no overhead calling this support routine on this platform. 1.6 Error Record and NTSTATUS Value Windows NT / Windows 2000 Design Goals are more stronger than other PC operating systems in runtime errors. That is, the system is designed to do the following: § When an error occurs, it is possible to continue to run, and a component (or thread) is not allowed to destroy the code or data of other components. § Whenever an error occurs, it can continue to run without sending a large number of blurred information to terminate the user. It has been recognized that some I / O errors are caused by users. For example, request from files from the file that can be deleted a storage medium, but the user provides an error of the disk, tape or CD-ROM, which generates an error caused by a user. As discussed by deleting the storage media section, this error is easy to correct, as long as the user can provide the correct media. Other I / O errors cannot be corrected simply by terminating user operations. For this I / O error, Windows NT / Windows 2000 continues to run and does not force users to realize that they cannot solve the errors immediately. Instead, it provides a system error recording thread that formats and saves I / O error messages as a table item in the file. Win32 Event Viewer can read and display this error log file.
Windows NT / Windows 2000 users, system administrators, or technical supporters can use it to monitor hardware status on a given computer; if needed, replace fault hardware; adjust device configuration to get better performance; if a hardware problem occurs, Debug these issues. 1.6.1 Calling IOALLOCATEERRORLOGENTRY When the driver discovers an I / O error during the process of processing IRP, it should call IOALLOCATEERRORLOGENTRY: SIZE = SIZEOF (IO_ERROR_LOG_PACKET) (N * Sizeof (Ulong)) Sizeof (Insertionstrings); // where n depends on how much // DumpData the driver will supply errorLogEntry = (PIO_ERROR_LOG_PACKET) IoAllocateErrorLogEntry (deviceExtension-> DeviceObject, // target device for current operation size); error record packet size is limited. System definition limits apply to all dump data (DUMP DATA) and drivers to provide a burst string for packets. The driver can call IoallocateErrorLoGentry with the specified ENTRYSIZE value (usually error_log_maximum_size). IOALLOCATEERROGENTRY returns a pointer to the error record package. If the pointer returns is NULL, the driver does not require a record error. It should just continue to run, and ensure that if the same error occurs again, it can record it. 1.6.2 padding packet error record packet error record is defined as follows: typedef struct _IO_ERROR_LOG_PACKET {UCHAR MajorFunctionCode; UCHAR RetryCount; USHORT DumpDataSize; USHORT NumberOfAtrings; USHORT StringOffset; USHORT EventCategory; NTSTATUS ErrorCode; ULONG UniqueErrorValue; NTSTATUS FianlStatus; ULONG SequenceNumber; ULONG IoControlCode; Large_integer deviceoffset; ulong dumpdata [1];} IO_ERROR_LOG_PACKET, * PIO_ERROR_LOG_PACKET driver should fill the error record package with the following data: MajorfunctionCode points to IRP_MJ_XXX in the current IRP driver I / O stack. RetryCount points to the number of times the driver retry operation and the error encountered. RetryCount is a value based on 0-based value. That is, the driver should be set to 0 when the current IRP is first encountered. DumpDataSize pointers the number of bytes that the driver will need in all DumpData set in the package. The specified value should be an integer multiple of sizeof (ulong). NumberOfStrings supports the number of inserted strings that drive the driver will provide this package. For errors that need to be inserted into a string, the driver is set to 0. Error logging threads can be provided with these drivers to fill information written to the Win32 event log with 0 ended Unicode string, which can be viewed with the Win32 event viewer. I / O Manager assumes: Initial insert strings (if any) or the name of the driver, or the name of the device that has occurred. The insertion string provided by the driver should be unrelated to the language.
Record the error and use the driver inserted string should use the string read from the registry or use the language-independent name (such as a file name). In most cases, the device and the intermediate layer driver can only record I / O errors without providing the insertion string for the high-level event logging component, which can also not establish the event record component specified by the driver. In the driver provided by the system, currently only the network device driver provides insertion strings in the error record package. Stringoffset After DumpData, the StringOffset indicates that the driver provides the offset at the beginning of the inserted string data. If the driver provides this data, each string must be a Unicode string ending with 0. EventCategory saves its own drivers in the registry as an event record component, which is a value defined by a driver that is specified in the MersSage File for Categories. ErrorCode points out the wrong type. This is a constant for system definitions or driver definitions, see defining a new IO_ERR_XXX section. UniqueerrorValue points out where the error is detected where the driver is detected. FinalStatus When IRP is completed, it indicates the value set in the I / O status block of this IRP; or indicates the status_xxx returned by the support routine that the driver call. SequenceNumber pointed out that the driver assigned to the current IRP queue, which should be a constant within the life of a given request. IOCONTROLCODE If MajorfunctionCode is IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL, it indicates the I / O control code in the current IRP driver I / O stack. Otherwise, this value should be 0. If you want to know more about I / O control code and device I / O control, see "Windows 2000 Driver Development Guide" Volume 2 Chapter 13 "IRP Function Code and IOCTL". DeviceOffSet points out the offset that occurred in the device. DumpData can be used to store the data specified by the driver, such as the register value or other useful information to be used when identifying an error reason. Any driver is inserted into the string must follow the dump data, start from the StringOffset. 1.6.3 Setting the NTSTATUS Value in the error record package ErrorCode and FinalStatus members in the record package are NTSTATUS types. Figure 16.4 Indicates the format of the NTSTATUS value. Figure 16.4 NTSTATUS formatting system provides a range of common IO_ERR_XXX constants to set ErrorCode in the error record package. For example, the driver can be defined using the following system constants: IO_ERR_RETRY_SUCCEEDED IO_ERR_INSUFFICIENT_RESOURCES IO_ERR_CONFIGURATION_ERROR IO_ERR_INCORRECT_IRQL IO_ERR_INBALID_IOBASE IO_ERR_DRIVER_ERROR IO_ERR_PARITY:: IO_ERR_OVERRUN_ERROR IO_ERR_TIMEOUT IO_ERR_CONTROLLER_ERROR IO_ERR_INTERNAL_ERROR system also provides a common STATUS _XXX value range, the driver can use to set the packet error record FinalStatus They can return from the standard driver routine that can return NTSTATUS values.
For example, the driver can return the constant defined by the following system from its standard driver routine, which can be set in the I / O status block of the IRP, which can be set to the following values in the error record packet: STATUS_SUCCESS STATUS_DEVICE_CONFIGURATION_ERROR STATUS_DRIVER_INTERNAL_ERROR STATUS_INVALID_DEVICE_STATE STATUS_IO_DEVICE_ERROR:: STATUS_DEVICE_BUSY STATUS_DEVICE_DOSE_NOT_EXIST STATUS_ADAPTER_HARDWARE_ERROR public IO_ERR_XXX and STATUS _XXX constants are system resources. Each status _xxx is mapped by the system to the corresponding Win32 constant. Because they are embedded in Windows NT / Windows 2000, only public IO_ERR_XXX and STATUS _XXX constants can be added to the system only in situations with Microsoft companies. 1.6.4 Calling iowriteErrorLoGentry When the data has been filled in the package returned by IOAllocateerrorLoGentry, it must call iowriteErrorLoGentry so that the error record thread can write its entry in the error log file. As long as it may, the driver should record errors; if needed, reject the IRP; continue to run when you encounter an exception or unpublished I / O error. Drivers being sold should never call KebugChecKex (or KebugCheck) to terminate the system. However, you can use KebugCheckex to debug the driver being developed. 1.6.5 Defining a new IO_ERR_XXX user can create a private IO_ERR_XXX code specified by the driver in the resource file XXXLog.mc of the driver. As shown in Figure 16.4, for such errors, the NTSTATUS type Facility domain must be set to facility_io_error_code. The SEV value and the unique Code value must be provided for each new IO_ERR_XXX. The driver must also set the C bit in its new IO_ERR_XXX to '1'. The SEV domain in Figure 16.4 indicates a severity code, which must be one of the values defined by the following system: status_severity_success indicates that FinalStatus in the error record package is set to status_success, and ErrorCode is set to IO_ERR_RETRY_SUCCEDED This value. Although STATUS_PENDING is also the value of the Status_Severity_Success class, the driver cannot record errors in the unresolved IRP. Status_severity_informational indicates the NTSTATUS value of the prompt message, such as status_serial_more_writes. Status_severity_warning indicates the NTSTATUS value of the warning, such as status_device_paper_empty. STATUS_SEVERITY_ERROR indicates an error NTSTATUS value. For example, set the FinalStatus value to status_insuffect_resources, or set the ERRORCODE value to IO_ERR_CONFIGURATION_ERROR. Most public IO_ERR_XXX constants belong to the Status_Severity_Error class. In order to make a series of driver definitions to system administrators, or to terminate users through the Win32 event viewer, the driver must be established as an error record component in the registry.
1.6.6 Defining a mobile NTSTATUS constant a pair of new drivers (such as class / port driver or video display / micro-port driver) can define the STATUS _XXX value specified by the driver to exchange information about privately defined IRP_MJ_INTERNAL_DEVICE_CONTROL requests, This request is from a low-layer to the high-level driver. If the IOCompletion routine of the existing high-level driver may be a IRP call, then when the class driver completes this IRP, it must map all private status _xxx values as the system-defined NTSTATUS value. For paired display and video micro-port drivers, the video port driver is responsible for the public STATUS _XXX value and Win32 defined constant (returned by the video micro-port driver). If you want to know more about the video micro-port driver, see the Graphics Drive Design Guide. The driver requesting a private status _xxx value for a series of IRP_MJ_Internal_Device_Control must do the following: § As shown in Figure 16.4, set the Facility domain to the appropriate driver defined constant, this value is used to point out the type of the device. § Set the user code mark (indicated by c in Figure 16.4). § Set the SEV domain with an appropriate value. See "Defining a new IO_ERR_XXX section". 1.7 Processing Removable Storage Media File System and Removable Storage Media Device Driver must: guarantee that the correct medium has been installed when the file is turned on on the delete storage medium device; and guarantees the correct media during the operation of the access medium Always installed. The intermediate layer driver located between the file system and the delete storage medium device driver must also be responsible for ensuring this. Therefore, the driver that can delete the storage medium device should be able to do at least one of the following: Responding to the possible storage medium change checking the file system to check the data of the CHECK-VERIFY Request Notification DecmePject-> Flags The IRP 1.7.1 response to the verification request file system from the file system can send an IRP at any time, by setting the parameters.deviceiocontrol.ioControlcode in the I / O stack to the following value by setting the parameters.deviceIocontrol.ioControlcode in the I / O stack. IRP_MJ_ Device_Control Request: IOCTL_XXX_CHECK_VERIFY where xxx is a device type, such as Disk, Tape or CDROM. The Disk type includes an inseparable (floppy disk) and a sequential partition to delete storage media devices. If the next device driver determines that the storage medium is not changed, the driver should complete the IRP, return the iostatus block with the following value: Status: Set to 0 In addition, if the device type is Disk or CDROM, the caller specified output Buffer, the driver returns the memory media saved by IRP-> AssociatedSystemBuffer in the buffer and sets iostatus. Information to SIZEOF (ULONG). By returning this count value, the driver allows the caller to determine if the storage medium has changed. If the next device driver determines that the storage medium has changed, it performs different initiatives depending on whether the storage medium is installed. If the storage medium is installed (VPB_mounted flag in VPB is set to 1), the driver should do the following: 1. Get the new Flags value for the Flags in DeviceObject with DO_VERIFY_VOLUME.
2. Set the iostatus block in the IRP to the following: § Set the Status to status_verify_required § Set the information to 0 3. Use the input IRP to call IOCOMPLETEREQUEST. If the storage medium is not installed, the driver will never set the DO_VERIFY_VOLUME to 1. The driver should set iostatus. Status to status_io_device_error, set iostatus. Information to 0, and then call IOCOMPLETEREQUEST with the input IRP. 1.7.2 Notification File System May Change Deleting Storage Media Device Driver Must: Driver Processing Request To / from the storage medium I / O control operation when the IRP or driver processing sent from the storage medium The storage medium of the device represented by DeviceObject (input of each transmitted IRP driver routine) has not changed. If the physical device always changes to the driver, the best inspection time for changing the media is to transform from the medium from the case where there is a state in which the medium is present. If the physical device indicates that the status of the storage medium may have changed before or during the driver start I / O operation, the driver must be made: 1. Make sure the storage medium is installed by checking the VPB_mounted flag in the VPB. (If there is no storage medium, the driver will never set the do_verify_volume 1. The driver should set iostatus. Status to status_io_device_error, set iostatus information to 0, and then call IOCOMPLETEREQUEST.) 2. Example of DeviceObject FLAGS is "or" the "or" operation with Do_verify_volume to get the new Flags value. 3. Set the iostatus block in the IRP to the following: § Set the Status to status_verify_required § Set the information to 0 4. Before completing the Status domain value in the iostatus block, the driver must call IOiserrorUserinduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduceduced when the Status field to the following values, which returns TRUE: STATUS_BERIFY_REQUIRED STATUS_NO_MEDIA_IN_DEVICE STATUS_WRONG_VOLUME STATUS_UNRECOGNIAED_MEDIA STATUS_MEDIA_WRITE_PROTECTED STATUS_IO_TIMEOUT STATUS_DEVICE_NOT_READY if IoIsErrorUserInduced returns TRUE, the driver must call IoSetHardErrorOrVerifyDevice, so FSD can be sent to provide the correct storage medium popup menu to a user; or retry initial request Or withdraw the request. 1.7.3 Check the flag in the device object to the IRP operation of each request to / from the delete storage medium, the delete storage medium device driver must determine if its deviceObject-> flags is set to do_verify_volume. If so, the driver must do the following: § For IRP_MJ_READ, IRP_MJ_WRITE and some IRP_MJ_DEVICE_CONTROL request, check if the value of the I / O stack position Flags is SL_OVERRIDE_VERIFY_VOLUME. If yes, continue the requested operation. When IFS is installed or reinstated, the device control request returns to the lower storage medium logic structure information sets the value of the I / O stack position Flags to SL_OVERRIDE_ VERIFY_VOLUME.
§ Otherwise, the driver must reject the I / O operation for the corresponding drive, device or partition when DO_VERIFY_VOLUME is DO_VERIFY_VOLUME. As mentioned in the previous section, the driver that can be deleted storage media devices must refuse to send an IRP of the corresponding device, repeat steps 2 and 3 for each IRP until the FSD clears the DO_VERIFY_VOLUME value in DeviceObject-> flags. If the storage media device driver can be deleted when DO_VERIFY_VOLUME is set, no IRP is set, no IRP is rejected, and the file system cannot maintain the integrity of the cached file data, and the user cannot prompt the user to reinstall this stored media that has already opened the file. 1.7.4 Establishing an IRP in the Intermediate Layer Driver This intermediate layer driver between the file system and the delete storage medium device driver must establish a low-level driver's I / O stack location in the IRP. When the intermediate layer driver establishes an I / O stack position for the lower driver, it must be copied from the input IRP_MJ_READ, IRP_MJ_WRITE, and IRP_MJ_DEVICE_CONTROL request, copy its own I / O stack position Flags to the low-level driver I / O Stack position. If the intermediate layer driver is assigned a new IRP for the lower layer to delete a storage medium driver, it must be established as described below: § For the transfer request, it must be based on the value of tail.overlay.thread in the initial IRP, every An IRP assigned to the driver establishes a thread environment. § For IRP_MJ_READ, IRP_MJ_WRITE, and IRP_MJ_DEVICE_CONTROL request, it must be copied from the initial IRP to IRPs allocated to the driver from the initial IRP. Otherwise, the file system neither maintains the integrity of the cached file data, and cannot prompt the user to reinstall this storage medium that has already opened the file. 1.8 Enables the device to use the application and driver to deliver I / O requests directly for any user mode code, whether it is physical, logical, or virtual, and its driver must provide some of its user mode customers. Name. Use this name, user mode application (or other system components) to identify the device requested I / O. In the previous operating system version, the driver named their device objects and establishes a symbolic connection for these names and user visible Win32 logical names in the registry. However, Windows 2000 and WDM drivers are not named device objects, but to register for each device object and enable a user mode I / O request to be sent. The device interface is a way to output devices and drivers to other system components (including other drivers and user mode applications). Equipment objects are classified, each class is related to a GUID. The system defines the GUID for the public device interface class in the header file specified by the device. When the driver registers a device interface, the I / O Manager associates its device interface class GUID with a symbolic connection name. The connection name is saved in the registry, which has been existed during the system startup. The application using this device interface can find its symbol connection name and save it as the target for I / O request. 1.8.1 Registration Equipment Interface Windows 98 and Windows 2000 provide methods for two registered device interfaces: § For kernel mode components, like most drivers, use I / O manager routines. This section describes how to use these routines. § For user mode code, use the setupdixxx function. If you want to know more about these functions, please refer to the device interface function part in the online DDK. Windows 2000 and WDM drivers do not name their device objects. Conversely, when the driver is called IOCREATEVICE to create a device object, it should specify the device name as a null string.
The bus driver should set the file_autogenerated_device_name flag to 1. All PNP functions, filter, and bus drivers should set the file_device_secure_open flag to 1. Accordingly, the system selects a unique device name for this PDO. After creating a device object and connects it to the device stack, a driver is called ioregisterDeviceInterface. I / O manager definition of this routine are as follows: NTSTATUS IoRegisterDeviceInterface (IN PDEVICE_OBJECT PhysicalDebiceObject, IN CONST GUID * InterfaceClassGuid, IN PUNICODE_STRING ReferenceString, OPTIONAL OUT PUNICODE_STRING SymbolicLinkName); typically, the driver does this function from its AddDevice time Call, but sometimes the filter driver registers this interface. The caller passed the pointer to the PDO to PHYSICALDEVICEObject. InterfaceClassGUID is used to identify the interface being registered. Most functions and all filter drivers should pass Null strings in ReferenceString. This parameter provides a bus driver to define the software device definition interface that is creatively created as required. The registered interface has always existed during the operating system startup. If the specified interface has been registered, the I / O Manager transmits its name in SymbolicLinkName and returns the prompt success status status_Object_name_exists. If the interface has not been registered, I / O Manager creates a registration key for this device interface and returns implicit SymbolicLinkName in the Unicode string structure assigned to the caller. The driver passes this connection name when the device interface is available or not available. It also uses this name to access the registration button, which in this registration key can be stored as the information specified by the device interface. (See the IoopendeviceInterfaceRegistryKey application Turn on the device with this connection name. Under the compatible device interface class, the driver can call iReGisterDeviceInterface multiple times as needed to register additional device interfaces. Other system components cannot use it before the driver makes the device interface. See "Enable the device interface and unavailable". 1.8.2 Enable the device interface to use and unavailable after successfully launch the device, the driver to register the interface is available to this interface. I / O manager definition of this routine are as follows: NTSTATUS IoSetDebiceINterfaceState (IN PUNICODE_STRING SymbolicLinkName, IN BOOLEAN Enable); SymbolicLinkName driver passes the Enable parameter is TRUE and returned by IoRegisterDeviceInterface to make this interface is available. If the driver can successfully launch its device, it should call this routine when the IRP_MN_START_DEVICE request to process the PNP manager. After the IRP_MN_START_DEVICE request is completed, the PNP Manager returns notifications to all requests their kernel mode and user mode components. See "Registering for Device Interface" on the online DDK ". In order to make the device interface unavailable, the driver calls iOSetDeviceInterfaceState, passed the symbolicLinkName and the value of the value false, which is false.
When the driver processes IRP_MN_SURPRISE_REMOVAL or IRP_MN_REMOVE_DEVICE for the device, it should make the device's interface is not available. When the device is stopped (IRP_MN_STOP_DEVICE) or in a sleep state, the driver is not available for the interface, and all device interfaces should be used and queued by I / O. 1.8.3 Using the Device Interface Device Interface can be used by the kernel mode components and user mode applications. The code of the user mode must be found with the SETUPDixxx function to find the registered, available device interface. See "Device Interface Functions" in the online DDK. Before the kernel mode component can use the specified device or file object, it must do the following: 1. Determine if the required device interface is registered and available. When the driver is registered in the registry, it can be configured to notify the PNP manager when the device interface is available or not available. For registration, component calls ioregisterPlugPlayNotification. Whenever you set the device interface to a available or unavailable, the ioregisterPlugPlayNotification routine will call the callback function provided by the driver. See "Change Notification of PNP Device Interface" in Chapter 4, Plug and Play, Power Management, and Installation Design Guide. Driver or other kernel mode components can also call IoGetDeviceInterface to get a list of all registered and available device interfaces that specify the device interface class. The returned list has a pointer to the Unicode string of the identity device interface. 2. Get the Unicode string that represents the symbolic connection of the desired device interface class. IOGETDEVICEINTERFACE returns a pointer to string in the SymbolicLIST parameter. If the driver is registered as a to notify the PNP manager, the callback of the above routine can retrieve this string from the Device_Interface_Change_Notification structure. 3. Use this Unicode string to get pointers to the corresponding device or file object. In order to access the specified device object, the driver must call the IOGETDEVICEOBJECTPOINTER, and the ObjectName parameter is placed in the Unicode string of the desired interface. In order to access the file object, the driver must pass this Unicode string with the ObjectName parameter, call InitializeObjectAttributes, and then call the ZwcreateFile transfer the initialized attribute structure. 1.9 Speed Paging Codes and Data Users can make all or part of certain drivers to be pagan. The driver code page reduces the size of the driver loading image, saving system space for other applications. This is very effective for the drivers of the sporadically-use device (such as modem and CD-ROM) or some drivers that are rarely called. In frequently used drivers, the code segment that determines the performance or bad performance should not be pageable. A negative impact on the performance of the driver and the system is always on time. To determine if a part of the driver can be set to be paging, remember the following facts: § If the following is true, the driver code can be a pageable: § It access the page storage pool. § It calls another pager routine. § It references the user buffer in the user thread environment. § The following driver code must be a resident, which cannot be paging: § Run the code on the IRQL above Dispatch_level Get the code of the spiral lock. If the WAIT parameter is set to the keReleasemutex or keReleaseseSemaphore routine. If Knernel is called when the WAIT value is True, call the return time IRQL to dispatch_level, and the sender database is locked.
Generally, if all the total amounts of pageable code (or data) are at least 4K, this segment can be paging is feasible. Purely pageable code (or data) should be separated from the code that must be able to be panelively or can be locked or in accordance with the request, and it is placed in a separate section. Mixing the purely pageable code and the code that can be locked at any time will make some system space that does not need to be locked. However, if the canable code (or data) of the driver is less than 4K, these code can be mixed with the code that can be locked at any time, in the same paragraph, thereby saving system space. If you want to know more about it, see the following: § 16.9.1 To make the driver code can be paised § 16.9.2 lock can be paged code or data § 16.9 for the entire driver paging 1.9.1 makes the driver Code Multi-Paging In order to make the driver routine can be paged, it must ensure that it is running on the IRQL below Dispatch_level, but also guarantees that it does not get any self-lock. In order to detect the code running on the IRQL above Dispatch_level, you can use the PAGED_CODE () macro. In debug mode, if the code is running on the IRQL above Dispatch_level, this macro generates information. Placing this macro in the first sentence of the routine, marks the entire routine as paging code, as shown in the following example: NTSTATUS MyDriverxxx (in Out Pvoid Parsecontext Optional, Out Phandle Handle) {Ntstatus Status; Paged_code () To ensure proper execution, "DRIVER VERIFIER" is run in the "Force IRQL Checking" option in the composed driver. This option allows the system to automatically transfer the canable code from memory at each driver to DISPATCH_LEVEL or higher. Use "Driver Verifier" to quickly find driver errors in this area. Otherwise, these errors can generally only look for users, and they are usually difficult to reproduce. The routines that use the spiral lock cannot be paised. However, in some cases, those who do not need to be self-swirled in a separate routine in the paged segment can be separated.
For example, the following code segment: // PAGED_CODE (); KeInitializeEvent (& event, NotificationEvent, FALSE); irp = IoBuildDeviceIoControlRequest (IRP_MJ_DEVICE_CONTROL, DeviceObject, (PVOID) NULL, 0, (PVOID) NULL, 0, FALSE, & event, & ioStatus) ; if (irp) {irpSp = IoGetNextIrpStackLocation (irp); irpSp-> MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL; irpSp-> MinorFunction = IRP_MN_LOAD_FILE_SYSTEM; status = IoCallDriver (DeviceObject, irp); if (status = = STATUS_PENDING) {(VOID) KeWaitForSingleObject (& event , Executive, KernelMode, FALSE, (PLARGE_INTEGER) NULL);!}} SPINLOCKUSE ExAcquireSpinLock (& IopDatabaseLock, & irql); // Code inside spin lock DeviceObject-> ReferenceCount--;! if (DeviceObject-> ReferenceCount && DeviceObject-> AttachedDevice)! {// unload the driver.. 160 bytes). Also, remember: If the driver calls the WAIT parameter value of True (such as KeeleaseMutex or keReleaseseSemaphore), then its code can never be marked as a pageable. When this call returns, IRQL is Dispatch_LEVEL, and the sender database is locked. 1.9.2 Locking can be paged code or data non-page code is the memory resident, it runs on IRQL equal to or higher than Dispatch_level, and never cause page errors. The page code is running on the IRQL below Dispatch_level, which can be pussed (Page IN) when the reference to it does not cause the system to be embarrassed or negatively affecting the driver operation. Some drivers (such as serial and parallel drivers) do not require resident memory unless they are managed. There is only one activity connection or port, and some part of the driver code for this port must be stationed for device service. However, when the port or connection is not used, the driver code is not required. In contrast, the driver of the disk that stores system code, application code, or system paging files must always be resident memory, because this driver is often transmitted between its devices and systems. The driver of the burst device (such as a modem) can release the system space when the device is in a non-active state. This segment can be designed to be paging if there are code that must be resident in service activity devices in a separate segment. When the device of the driver is opened, the operating system puts the can be pushed into memory. The system CD audio driver code also has this feature. The driver code is classified into a pageable segment according to the CD device manufacturer. Some brands may never appear on a given system.
In addition, even if there is a CD-ROM on the system, it may be rarely accessed. Therefore, in the CD type, you can ensure that the code category into a filed page can ensure that the code of the device that is not available in a machine will never be loaded, and when the device is accessed, the system is loaded with the appropriate CD device. Then, as described below, the driver calls MMLockPagableCodeSecion, locking its code into memory when its device is being used. In order to separate the paged code to a named block, it is tag it with the following compiler command. All of the Windows NT / Windows 2000 compatible compilers support this instruction: #pragma alloc_text (Pagexxxx, Routinename) This block must start with Page, and must be paged by 1 to 4 unique identity drivers. Characters are completed. Segment name is case sensitive, that is, Page must capitalize. The RoutineName identifies the entry point that will be included in the canable segment. For example, the following code indicates an internal PAGEELK RdrCreateConnection segment entry points: #ifdef ALLOC_PRAGMA #pragma alloc_text (PAGELK, RdrCreateConnection) #endif pageable in order to make the driver code resident and locked, the driver calls MmLockPagableCodeSection, can be passed One address in the paging code segment (usually the entry point of the driver routine). MMLockPagableCodeSecion plus the lock in the entire code segment, including the routines referenced in this call. That is, it makes each routine associated with the same Pagexxxx identifier and is locked. The call to MMLockPagableCodeSection will fail only when the symbol address passed is not in a scalable page. MMLockPagableCodeSection returns a handle that will be used when unlocking the block (MMunlockPagableImageSection), or the driver must be used from other parts of its code. The driver can also treat little data as a pagestable, so that it can be transferred to the device until it supports the device activity. For example, a system mixer (Mixer) driver uses the paging data. The mixer device does not have asynchronous I / O related to it, so this driver can make its data can be paged. In order to create a pagestable data segment, use the following compiler instructions at the beginning of the data module: #pragma data_seg ("Page") At the end of the module, use the following instructions: #pragma data_seg () keyword Page is case sensitive, so it Must write. In order for the data segment to be locked and locked, the driver calls MMLockPagableDatasection, and passes the data item in the canable data segment. MMLockPagableDataSECTION returns a handle that will be used in subsequent lock or unlock requests. To recover the paged positionable status, call MMunlockPagAbleImageSecion, pass the handle value returned by MMLockPagableCodeSection or MMLockPagableDAthction. The unload routine of the driver must call MMunlockPagAbleImageSecion release it for each handle obtained for lockable code and data segment. During paralax is a big overhead, because the memory manager must retrieve its load module list before the page is locked. If the driver is locked in many parts of its code, it should use more efficient MMLockPagABleaseByHandle after the first call MMLockPagAblexxxsection.
The handle passed to the MMLockPagABleaseByHandle is the handle that is previously called MMLockPagableDataction or MMLockPagableDataction. The memory manager maintains a count value for each segment handle. Each driver calls MMLockPagablexxxXXXX, and the memory manager adds this value to 1. Call MMUNLOCKPAGABLEMAGESECTION reduces this numerical value by 1. This segment remains locked in memory when the count value of the segment handle is not zero. The segment handle is valid as long as its driver has been loaded. Therefore, the driver should only call the mmlockpagablexxxxsection once. If the driver requires other lockup calls, it should use MMLockPagAplaBleaseByHandle. If the driver is called the MMLockPagAblexxxXSECTION routine for the locked segment, the memory manager is only 1 of the reference value of this segment. If the lock routine is called, the page corresponding to this segment has been called out, the memory manager is transferred to this page and set it to 1. This technology minimizes the impact of the driver on system resources. When the driver is running, it will need a resident code and data to lock into memory. When there is no I / O request for the device, (that is, when the device is turned off or if the device has never been opened), the driver can unlock the same segment code or data, so that it can be adjusted Out. However, after the driver is interrupted, the driver code that can be called during the interrupt processing must be a resident memory. Some device drivers can make it possible to make them pane or lock into memory as needed, and some of the core parts of such drivers and certain core parts of the data must be permanently resident in the system space. When you lock the code or data segment, you should consider the following implementation strategies: § MM (UN) Lockxxx's basic use is: make the code or data that is considered non-page-type, and as a non-page style Code or data is introduced (bring in). Drivers such as serial or parallel drivers are good examples: If this driver management device does not open the handle, some code is unwanted, you can turn its page. The reapper (redirector) and the server are also a good example of this technology. When there is no active connection, the pages of these components can be called out. § The entire target page is locked into memory. § In each driver, a section gives the code, it is very effective. Generally, the use of many named can be less efficient is relatively low. § Save purely pageable segments and page it, but to separate the code that can be locked at any time as required. § Remember: MMLockPagableCodeeSection and MMLockPagableDatasection should not be called frequently. When the memory manager is loaded, these routines will make I / O load overweight. If the driver must lock the segment on several of its code, it should use MMLockPagABleaseByHandle. Using MMLockPagableCodeSection and MMunlockPagableImageSec reduces the size of each driver loading image, but also affects drivers and system performance. Typically, the system performance has a decisive driver (such as keyboard and mouse driver) and a driver that often uses devices (such as disk drivers) should not be available. It is not worth considering the performance loss for the cost of the driver image size temporarily reduced. 1.9.3 Drivers that use the MMLockPagablexxx support routine for the entire driver and specify the paging and discarded segments, which are consisting of the INIT segment discarded by the non-page segment, the page segment, and the driver. After the device driver is interrupted after the device driver is interrupted, the interrupt processing path of the driver must be resident in system space.
The interrupt processing code must be part of the driver segment where the page cannot be called, to prevent interruption. Two additional memory manager routines MmpagentiredRiver and MMRESETDRIVERPAGING can be used to set all segments of all segments that make up the driver image. These routines allow the driver to be completely touched when its device is not used and cannot generate interrupts. Examples of fully paging system drivers include Win32.sys drivers, serial drivers, mail slots (Mailslot) drivers, buzzers (BEEP) drivers, and zero-bit drivers (NULL DRIVER). A typical serial driver is used intermittently. The serial driver can be completely called until it is open to the port it managed. The port of the serial driver must be placed in the non-page system space as part of the serial driver must be placed in the serial driver. The other portions of the driver can still be pageable. The entire touched driver should call MmpagentireDriver during the initialization of the driver before the interruption is interrupted. PNP drivers should not use this routine. When the device managed by the called driver receives an open request, the driver page is transferred. Then, the driver must call MMRESETDRIVERPAGING before the connection is interrupted. Call MmReSetDriverPaging causes the memory manager to handle the driver segment based on the properties obtained by the compilation link phase. Non-page segments (such as text segments) will be transferred to non-page system memory, which can be tuned when they are referenced. Such a driver must save its device to open a reference numerous value. Whenever there is a device to open a request, the driver adds this notation; whenever a request is closed, the driver reduces this notation value. When this record value is 0, the driver should disconnect and then call MMPagentireDriver. If the device managed by the driver exceeds one, the driver can call MmpagentireDriver after all the count values of all devices. The driver must be synchronized when the reference value is changed, and when the driver can be paged, it should prevent the reference value to change. That is, on the SMP machine, the driver must be guaranteed: When the open call on one processor causes the interrupt to be connected and the reference value is increased, the other processor cannot be running MMPagentireDriver. 1.10 Common driver reliability issues take up a large number of code base sites (BASE) with the drivers executed by kernel mode. Therefore, to improve system reliability, this huge code base address must be considered. The most important tool that can be used to ensure the reliability of the driver is Driver Verifier. It can check many common driver issues, which will be discussed in this section. This section is proposed for the following common driver issues: § Buffer I / O User Space Address § Direct I / O Enter Data and Device Status § Dispatch Routine § Multiprocessor Environment § Processing IRP to the above problems The discussion is distributed in the following sections, and many of them come with a program fragment when it comes. These small procedures illustrate typical errors and how to correct them. (For the sake of brevity, these code has been properly modified) 1.10.1 Buffer I / O does not check the buffer size may be the most common driver problem. This problem can occur in many environments, but it is particularly troublesome in the following cases.
For example, suppose the following code appears in one routine called by the Dispatch routine, and assumes that the driver does not verify the validity of the buffer size transmitted in the IRP: Switch (ControlCode) case ioctl_new_address: {tnew_address * pnewaddress = PIRP-> Associatedirp .Stembuffer; pdeviceContext-> addr = ntohl (pnewaddress-> address); this example does not check the buffer size before assigning statement (bold). As a result, if the input buffer size is not enough to place the TNEW_ADDRESS structure, the pNewaddress-> Address reference in the second row is wrong. The following code checks the buffer size, to avoid possible problems: case IOCTL_NEW_ADDRESS: {tNEW_ADDRESS * pNewAddress = pIrp-> AssociatedIrp.SystemBuffer; if (pIrpSp-> Parameters.DeviceIoControl.InputBufferLength> = sizeof (tNEW_ADDRESS)) {pDeviceContext-> AddR = NTOHL (PNewaddress-> Address); a code that handles other buffer I / O (such as WMI requests using variable size) may have similar errors. Checking the buffer size output buffer problem is similar to the input buffer problem for buffer IOCTL and FSCTL output requests. They can easily destroy the storage pool, and the caller of the user mode is not easy to detect their happening. In the following example, the driver does not check SystemBuffer size: case IOCTL_GET_INFO: {Info = Irp-> AssociatedIrp.SystemBuffer; Info-> NumIF = NumIF; Irp-> IoStatus.Information = NumIF * sizeof (GET_INFO_ITEM) sizeof ( Ulong); IRP-> iostatus.status = ntstatus;} Assume the system buffer NumIF domain represents the number of input items, this example can set the iostatus.information to a value greater than the output buffer, so there is too much to return to the user mode code. information. If the application is not good, use too little output buffer call, the previous code segment will destroy the storage pool due to the system buffer. Remember: I / O Manager assumes that the value in the Information domain is valid. When the caller passes an invalid kernel mode address and zero-byte size for the output buffer, if the driver does not check the size of the output buffer, it will generate a serious problem. Returning an unconscious data driver to the caller with a buffer I / O path to initialize all output buffer data to 0 before returning the output buffer data to the caller. There is no initialization buffer leads to spam, and the number is the number of uninited bytes.
In the following example, the driver returns unwanted junk data: case IOCTL_GET_NAME: {outputBufferLength = ioStack-> Parameters.DeviceControl.OutputBufferLength; outputBufferLength = (PGET_NAME) Irp-> AssociatedIrp.SystemBuffer; if (outputBufferLength> = sizeof (GET_NAME) ) {length = outputBufferLength-sizeof (GET_NAME); ntStatus = IoGetDeviceProperty (DeviceExtension-> PhysicalDeviceObject, DevicePropertyDriverKeyName, length, outputBuffer-> DriverKeyName, & length); outputBuffer-> ActualLength = length sizeof (GET_NAME); Irp-> IoStatus.Information = OutputBufferLength;} else {ntstatus = status_buffer_too_small;} Set the value of Iostatus.information to the output buffer makes the entire output buffer is returned to the caller. I / O Manager does not initialize data that exceeds the input buffer size (due to buffer requirements, input and output buffer overlapping). Because the system support routine IOGETDEVICEPROPERTY does not write the entire buffer, this IOCTL returns an uninitialized data to the caller. Some drivers returns the code that provides I / O requesting more detail with the Information field. Before doing so, such a driver should check the IRP flag to ensure that this IRP_INPUT_OPERATION is not set 1. When this flag bit is not set 1, IOCTL or FSCTL does not output buffer, so the information domain does not need to provide buffer size. In this case, the driver can safely use the Information field to return its own code. Verify that the variable length buffer validity (can cause an integer overflow or overflow) Driver often uses an input buffer with fixed size head and variable length data, as shown in the following example: typedef struct_wait_for_buffer {large_integer timeout; ulong NameLength; BOOLEAN TimeoutSpecified; WCHAR Name [1];} WAIT_FOR_BUFFER, * PWAIT_FOR_BUFFER; if (InputBufferLengthAssociatedIrp.SystemBuffer; if (fIELD_OFFSET (WAIT_FOR_BUFFER, Name [0]) WaitBuffer-> NameLength> InputBufferLength) {IoCompleteRequest (Irp, STATUS_INVALID_PARAMETER); Return (status_invalid_parameter);} If Waitbuffer-> Namelength is a very large Ulong value, it will cause an integer overflow on the offset (OFFSET). In contrast, the driver should subtract offset from InputBufferLength. Compared with WaitBuffer-> Namelength, as shown in the following example: IF (InputBufferLength
AssociatedIrp.SystemBuffer; if ((InputBufferLength FIELD_OFFSET (WAIT_FOR_BUFFER, Name [0])> WaitBuffer-> NameLength) {IoCompleteRequest (Irp, STATUS_INVALID_PARAMETER); Return (STATUS_INVALID_PARAMETER);} foregoing subtraction does not underflow, since the first if statement ensures InputBufferLength WAIT_FOR_BUFFER size of greater than or equal Here is a more complex problem of underflow: case IOCTL_SET_VALUE: dwSize = sizeof (SET_VALUE); if (inputBufferLengthNumEntries * sizeof (SET_VALUE_INFO); if (inputBufferLength.
Parameters.DeviceIoControl.Type3InputBuffer; * EntryPoint = (ULONG) DriverEntryPoint; The following code avoids this problem: case IOCTL_GET_HANDLER: {PULONG_PTR EntryPoint; EntryPoint = IrpSp-> Parameters.DeviceIoControl.Type3InputBuffer; try {if (Irp-> RequestorMode =! KernelMode) {ProbeForWrite (EntryPoint, sizeof (ULONG_PTR), TYPE_ALIGNMENT (ULONG_PTR));} * EntryPoint = (ULONG_PTR) DriverEntryPoint;} except (EXCEPTION_EXECUTE_HANDLER) {It should be noted: the correct code DriverEntryPoint ULONG_PTR cast type. This conversion can do a further application in a 64-bit Windows environment. The validity of the pointer embedded in the buffer I / O request is not embedded in the buffer request, as shown in the following example: struct RET_BUF {void * arg; // Pointer Embedded in Request int RVAl;}; pBuf = Irp-> AssociatedIrp.SystemBuffer; ... arg = pBuf-> arg; // Fetch the embedded pointer ... // If the pointer is invalid, // this statement can corrupt the system RtlMoveMemoru (arg, & info, sizeof (info. In this example, the driver should use the probexxx routine in the TRY / Except block to verify the validity of the embedded pointer, like the validity of Method_neither IOCTL in front. Although the embedded pointer allows the driver to returns more information, the driver can be more efficiently achieved by using the relevant offset or variable length buffer. 1.10.3 Errors in Direct I / O The most common direct I / O problem is not correctly handling zero length buffer. Because I / O Manager creates MDL without zero length transmission, zero length buffer makes the value of IRP-> MDLADDRESS NULL. To map the address space, the Windows 2000 driver should use MmgetsystemAddressFormdlsafe, which returns NULL when the mapping fails. If the driver passes a value Null MDLADDRESS, it will also return NULL. On Windows 98, MmgetSystemAddressFormdlsafe returns NULL when an error. The driver should check the return until each time attempts to use the return address, is NULL. Direct I / O includes two-way mapping between user address space and system address buffer, which two different virtual addresses have the same physical address. Bidirectional mapping causes the following results, sometimes they will cause problems: § The offset of the user address virtual page becomes the offset of the system page. Access to the system buffer boundary may not be notified for a long time, this time depends on the page size of the map. If the buffer page boundary assigned to the caller is far, the data written outside the buffer boundary will still appear in the buffer, and the caller will not realize that errors have occurred. If the buffer boundary is just over with the page boundary, the system virtual address other than the boundary may point to other content or invalid. This problem is difficult to find. § If the calling process has another thread that is modified, the content of the system buffer will also change when the user memory mapping changes.
In this case, the use of system buffers to save expiration (SCRATCH) data will bring problems. Two operations from the same memory cells may get different values. The following code segments receive a string in a direct I / O request, then try to convert this string to uppercase: pwchar portname = null; portname = (pwchar) mmgetsystemaddressFrommdl (Irp-> mdladdress); //// null-terminate the PortName so that RtlInitUnicodeString will not // be invalid // PortName [Size / sizepf (WCHAR) -1] = UNICODE_NULL;. RtlInitUnicodeString (& AdapterName, PortName); buffering may not have the correct format as required, so the code Trying to enforce the NULL of Unicode as the last buffer character. However, if the lower physical memory is mapped to the user mode and kernel mode address, once the write operation is completed, the other thread of this process will write to the buffer. Conversely, if there is no null, the call to RTLinitUnicODestring will exceed the scope of the buffer. And if it is outside the system map, it may cause an error detection. If the driver creates and maps its own MDL, it should ensure that it only uses the method you have verified to access this MDL. That is, when the driver is called MMPROBEANDLOCKPAGES, it specifies an access method (ioreadAccess, iowriteaccess, or omodifyAccess). If the driver specifies ioreadAccess, it will never try again to write to the system with MMgetSystemAddressFormdl or MmgetsystemAddressFormdlsafe. 1.10.4 Error Drivers for Call Enter and Device Status should verify that all validity of all inputs from the caller. Because the driver does not guarantee that the caller passes the correct data type or number, it cannot guarantee that the caller is only called when the device or driver is in the correct state. The driver without verification device status The driver of the following example uses the Assert Macro inspection device in the debug version (free build), but does not check the status of the device in the release (free build): case ioctl_wait_for_event: assert ((! Extension-> waiteventirp); extension-> waiteventirp = IRP; iomarkirppending (IRP); status = status_pending; in debug version, if the driver has kept the IRP in a unreal state, the system will send a notice (Assert) ). However, in the release, the driver does not check this error. Two calls to the same IOCTL make the system lose the tracking of IRP. In multiprocessor systems, this code segment may bring other problems. Suppose this routine has this IRP ownership (manipulated rights) in the entry point. When the routine holds the IRP pointer in the global structure extension-> WaiteVentIRP, another thread can get this IRP address from this global structure and then operate this IRP. In order to prevent this problem, the driver should mark this IRP as unresolved before saving IRP, and should include call and assignment of IOMarkIrPpending in interlocking queues.
The driver may also need to establish a CANCEL routine for this IRP, see Chapter 12, "CANCEL RR). Unexpected I / O Received Unknown Device Objects Request Many drivers create more than one device object by calling IOCREATEDEVICE. Some drivers create a control device object in their DriveREntry routines even before the driver creates FDO, allowing the application to communicate with the driver. For example, the file system driver creates a device object to process file system notifications when using iReGisterFileSystem to register with a file system with IoreGisterFileSystem. The driver should be able to process the CREATE request on any device itself created. After successfully completing the CREATE request, it should be prepared to receive any user accessible I / O request on the created file object. Therefore, the driver for creating more than one device must check which device object specified for each I / O request. For example, the driver may expect I / O requests to specify FDOs for a given device, and actually requests its control device objects. If the driver is not in other device objects, the same domain is initialized in the device extension of this control device object, and when attempts to use device extension information from this control device object, the driver may crash. Validity without verification handle Some drivers must manipulate the objects transmitted to them, or must handle two file objects at the same time. For example, a modem driver may receive a handle of an event object, or a network driver may receive a handle of two different file objects. The driver must verify the validity of these handles. Because they are passed by the caller, instead of passing the I / O manager, I / O Manager cannot do any validity verification. For example, in the following code segment, the handle of the driver is transmitted AscInfo-> AddressHandle, but did not verify it before calling ObReferenceObjectByHandle: // // This handle is embedded in a buffered request // status = ObReferenceObjectByHandle (AscInfo-. > AddressHandle, 0, NULL, KernelMode, & fileObject, NULL); if (NT_SUCCESS (status)) {if ((fileObject-> DeviceObject = = DeviceObject) && (fileObject-> FsContext2 = = TRANSPORT_SOCK)) {ObReferenceObjectByHandle While the call Successful, the code cannot guarantee that the returned pointer references a file object, it believes that the caller passes the correct information. Even if all the parameters of the ObreferenceObjectByHandle are correct, and the call is successful, if the file object is not the driver you want, the driver will still be unexpected.
In the following code segment, driver assumes a successful call returns a pointer to the file object it wants: status = ObReferenceObjectByHandle (AscInfo-> Handle, 0L, DesiredAccess, * IoFileObjectType, Irp-> RequestorMode, (PVOID *) & AcpEndpointFileObject , NULL); if) {goto complete;} (NT_SUCCESS (status!) AcpEndpoint = AcpEndpointFileObject-> FsContext;! if (AcpEndpoint-> Type = BlockTypeEndpoint) Although ObReferenceObjectByHandle returns an object pointer to a file, or the driver can not be guaranteed pointer Quoted is the file object it wants. In this case, the driver should verify the validity of this pointer before accessing the acpendPointFileObject-> fsconText data specified by the driver. In order to prevent such problems, the driver should check to get effective data, as shown below: § Check the object type to ensure that it is the driver you want. § Guaranteed access to this object type and the task required. For example, if the driver performs a quick file replication, you must ensure that the handle can read. § Be sure to specify the correct access mode (usermode or kernelmode), to ensure that this access mode is compatible with the request's access. § If the driver needs its handle of the file object you created yourself, verify the validity of this handle according to the device object or driver. However, be careful not to destroy the filter that wants to send I / O requests unknown. § If the driver supports multiple types of file objects (such as control channels; address objects; TDI drivers or file systems for Volume, Directory, File object connection), to ensure that they can distinguish them. 1.10.5 Errors in the Dispatch routine Some drivers do not distinguish between the Dispatchcleanup routine and the tasks required in the DispatchClose routine. When the last handle of the file object is turned off, the I / O Manager calls the Dispatchcleanup routine. When the last one is released from this file object, the DispatchClose routine is called. The driver should not be tried to release resources in its Dispatchcleanup routine because they are being connected to a file object and may be used by other DISPATCH routines. When you call the sending routine, the I / O Manager calls the normal I / O to keep a reference to the file object. As a result, the driver can receive the I / O request to the file object before the DispatchCloseanup routine is called, and the DispatchClose routine is called before the I / O request of the file object is received. For example, when the I / O manager request from another thread is being processed, the user mode caller may close this file handle. If the driver has deleted or released the necessary resources before the I / O Manager calls its DispatchClose routine, an invalid pointer reference or other problem occurs. Merge public IOCTL and private IOCTL paths, the driver should not have private (internal) and public IOCTL merge execution paths. The driver cannot determine if IOCTL code is determined from the kernel mode or user mode. Therefore, both the same path will be processed to ensure the safety of the driver is not guaranteed. If a private IOCTL is authorized, unauthorized users who know this IOCTL code may be able to access it. Therefore, if the driver creates a private IOCTL, then ensure that these IOCTLs are separated from the public IOCTL it must support.
1.10.6 Errors in multiprocessor environments In Windows NT / Windows 2000 systems, drivers are multithreaded, which can receive multiple I / O requests from different threads simultaneously. When designing a driver, it must be ensured on the SMP system and take appropriate measures to ensure data integrity. Specifically, whenever the driver changes the global or file object data, it must prevent the production of the stamping conditions with locks or interlock queues. When the data specified by the global or file object is generated in the code segment below, a race condition is generated when the driver accesses the global data in Data.lpcinfo, produces a race condition: PLPC_INFO PLPCINFO = & data.lpcinfo; // Pointer To Global Data ... // this Saved Pointer May Be overwritten by Another Thread, PLPCINFO-> LPCPORT.BUFFER = ExallocatePool (PagedPool, arg-> portname.length); Thread will be a thread that enters this code segment as a result of IOCTL call results Leak memory because the pointer is covered. To avoid this problem, the driver should use the exinterLocKedxxx routine or a certain type of lock when changing global data. The demand for the driver determines the type of lock. See "Using Spinner", "Schemers" and Exacquireresourcecelite. The following example attempts to reallocate a specified file buffer (Endpoint-> LocalAddress) to hold the end (Endpoint) Address:! Endpoint = FileObject-> FsContext; if (Endpoint-> LocalAddress = NULL && Endpoint-> LocalAddressLength