IRQL Description
No interrupts PASSIVE_LEVEL Normal thread execution
Software interrupts APC_LEVEL Asynchronous Procedure Call execution
DISPATCH_LEVEL Thread scheduling Deferred Procedure Call execution
Hardware interrupts DIRQLs Device Interrupt Request Level handler execution
PROFILE_LEVEL Profiling timer
CLOCK2_LEVEL Clock
SYNCH_LEVEL Synchronization level
IPI_LEVEL Interprocessor interrupt level
POWER_LEVEL Power failure level

Table 3.3 shows the interrupt levels that Windows uses to provide an abstract view of the actual processor interrupt levels. The lowest priority interrupt levels are at the top. Hardware- generated interrupts always take priority over software interrupts.

Drivers only use three of these interrupt levels. Somewhat confusingly, driver dispatch routines are called at PASSIVE_LEVEL[5] (i.e., with no interrupts). Many driver callbacks run at DISPATCH_LEVEL. Finally, driver hardware interrupt service routines operate at Device Interrupt Request Level (DIRQL).

The DIRQLs level in fact represents the many hardware interrupt levels that are available on the specific processor. A disk hardware interrupt level may well have higher priority than a parallel port hardware interrupt, so the disk may interrupt a parallel port interrupt. Usually a driver has just one DIRQL, though there is no reason why it cannot support more, if that is how its hardware works. The relevant driver interrupt level is referred to as its DIRQL.

The interrupt level at which a driver is operating is very important, as it determines the types of operation that can be undertaken. For example, driver hardware interrupt service routines cannot access memory that might be paged out to a swap file.

A driver has to be aware that it could be interrupted by a higher priority task at any stage. This means that its own interrupt service routine could run in the middle of its own dispatch routines. For this reason, if a normal driver routine is about to interact with its own hardware, it uses a kernel function to raise its interrupt level temporarily to its DIRQL. This stops its own interrupt handler from running at the same time. In addition, in a multiprocessor system, another driver routine might be attempting the same task on another processor. The KeSynchronizeExecution routine is used to run such critical sections in a multiprocessor-safe way.

Runtime Priorities

The Interrupt Level should not be confused with the scheduling priority. All threads normally run at the lowest interrupt level, PASSIVE_LEVEL. The scheduler uses the priority values to determine which thread to run next. Any other interrupt takes priority over a thread.

Drivers need to be aware of scheduling priorities. When it completes an IRP, it can give the calling thread a temporary priority boost so that, for example, programs that interact with the mouse can continue their run more quickly. This technique generally improves the perceived responsiveness of the system.

Deferred Procedure Calls

Any routine servicing a hardware interrupt stops normal program execution. For this reason, it is best to make an interrupt service routine as quickly as possible.

Any nonessential interrupt processing should be deferred until later. Windows lets driver writers use Deferred Procedure Call (DPC) routines, which are called at DISPATCH_LEVEL. The interrupt routine requests that the driver's DPC post-interrupt routine (DpcForIsr) is called when things have calmed down. A typical job in this routine is to indicate that the current I/O request is complete; the relevant kernel call can only be carried out at DISPATCH_LEVEL

When writing your driver, be careful to comment the interrupt level at which each routine can be called. Similarly, make only kernel calls that are appropriate for the current interrupt level.

Using Memory

A device driver has to be very careful when allocating or accessing memory. However, it is not as gruesome to access memory as some other types of driver. For example, you do not need to worry about the murky x86 world of segments and selectors, as the kernel handles all this stuff.

Pool Memory

Windows implements virtual memory, where the system pretends that it has more memory than it really has. This allows more applications (and the kernel) to keep running than would otherwise be the case.

Virtual memory is implemented by breaking each application's potential address space into fixed size chunks called pages. (x86 processors have a 4KB-page size, while Alpha processors use 8KB.) A page can either be resident in physical memory, or not present and so swapped to hard disk.

Drivers can allocate memory that can be paged out, called paged memory. Alternatively, it can allocate memory that is permanently resident, called nonpaged memory. If you try to access paged memory at DISPATCH_LEVEL or above, you will cause a page fault and the kernel will crash. If you access nonresident paged memory at PASSIVE_LEVEL, the kernel will block your thread until the memory manager loads the page back into memory.

Please do not make extravagant use of nonpaged memory. However, you will find that most of the memory your driver uses will be in nonpaged memory. You must use nonpaged memory if it is going to be accessed at DISPATCH_LEVEL or above. Your driver StartIo and ISRs, for example, can access only nonpaged memory.

Table 3.4 shows how to allocate both paged and nonpaged memory using the kernel ExAllocatePool function. The table uses the DDK convention of listing IN or OUT before each parameter. IN parameters contain information that is passed to the function, and vice versa. Instances of IN and OUT in DDK header files are removed using macros.

Specify the ExAllocatePool PoolType parameter as PagedPool if you want to allocate paged memory or NonPagedPool if you want to allocate nonpaged memory. The other PoolType values are rarely used. Do not forget to check whether a NULL error value was returned by ExAllocatePool. The ExAllocatePool return type is PVOID, so you will usually need to cast the return value to the correct type.

When you are finished using the memory, you must release it using ExFreePool, passing the pointer you obtained from ExAllocatePool. Use ExFreePool for all types of memory. If you forget to free memory, it will be lost forever, as the kernel does not pick up the pieces after your driver has unloaded.

The ExAllocatePoolWithTag function associates a four-letter tag with the allocated memory. This makes it easier to analyze memory allocation in a debugger or in a crash dump.

Table 3.4 ExAllocatePool function

PVOID ExAllocatePool (IRQL<=DISPATCH_LEVEL) If at DISPATCH_LEVEL, use one of the NonPagedXxx values
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату