Inside Microsoft Windows 2000, Third Edition (Microsoft Programming Series)

[Previous] [Next]

At system initialization, the memory manager creates two types of dynamically sized memory pools that the kernel-mode components use to allocate system memory:

Both memory pools are located in the system part of the address space and are mapped in the virtual address space of every process. (In Table 7-10, you'll find out where in the system memory they start.) The executive provides routines to allocate and deallocate from these pools; for information on these routines, see the functions that start with ExAllocatePool in the Windows 2000 DDK documentation.

There are two types of nonpaged pools: one for general use and a small one (four pages) reserved for emergency use when nonpaged pool is full and the caller can't tolerate allocation failures. (This latter pool type should no longer be used; device drivers should be written to properly handle low memory conditions. Driver Verifier, discussed later in this chapter, makes it easier to test such conditions.) Uniprocessor systems have three paged pools; multiprocessor systems have five. Having more than one paged pool reduces the frequency of system code blocking on simultaneous calls to pool routines. Both nonpaged and paged pool start at an initial size based on the amount of physical memory on the system and then grow, if necessary, up to a maximum size computed at system boot time. You can override the initial size of these pools by changing the values NonPagedPoolSize and PagedPoolSize in the registry key HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management from 0 (which causes the system to compute the size) to the size desired in bytes. You can't, however, go beyond the maximum pool sizes listed in Table 7-4.

Table 7-4 Maximum Pool Sizes

Pool Type Maximum Size
Nonpaged 256 MB (128 MB if booted /3GB)
Paged 491.875 MB

The computed sizes are stored in four kernel variables, three of which are exposed as performance counters. These variables and counters, as well as the two registry keys that can alter the sizes, are listed in Table 7-5.

Table 7-5 System Pool Size Variables and Performance Counters

Kernel Variable Performance Counter Registry Key to Override Description
MmSizeOfNonPaged-PoolInBytes Memory: Pool Nonpaged Bytes Not applicable Current size of nonpaged pool
MmMaximumNon-PagedPoolInBytes Not available HKLM\SYSTEM\CurrentControlSet\

Control\SessionManager\

MemoryManagement\NonPagedPoolSize

Maximum size of nonpaged pool
Not available Memory: Pool Paged Bytes Not applicable Current virtual size of paged pool
MmPagedPoolPage (number of pages) Memory: Pool Paged Resident Bytes Not applicable Current physical (resident) size of paged pool
MmSizeOfPagedPool-InBytes Not available HKLM\SYSTEM\CurrentControlSet\

Control\SessionManager\

MemoryManagement\PagedPoolSize

Maximum (virtual) size of paged pool

EXPERIMENT


Determining the Maximum Pool Sizes

Because paged and nonpaged pool represent a critical system resource, it is important to know when you're nearing the maximum size computed for your system so that you can determine whether you need to override the default maximum with the appropriate registry values. The pool-size performance counters report only the current size, however, not the maximum size. So you don't know when you're reaching the limit until you've exhausted pool.

With LiveKd, you can easily view the maximum sizes by examining the values of the kernel variables listed in Table 7-5. Here's an example:

kd> dd mmmaximumnonpagedpoolinbytes l1 8047f620 0328c000 kd> ? 328c000 Evaluate expression: 53002240 = 0328c000 kd> dd mmsizeofpagedpoolinbytes l1 80470a98 06800000 kd> ? 6800000 Evaluate expression: 109051904 = 06800000

From this example, you can see that the maximum size of nonpaged pool is 53,002,240 bytes (approximately 50 MB) and the maximum size of paged pool is 109,051,904 bytes (104 MB). On the test system we used for this example, current nonpaged pool usage was 5.5 MB and paged pool usage was 34 MB, so both pools were far from full. (To quickly see these numbers, run Task Manager, click the Performance tab, and view the Kernel Memory section.)

EXPERIMENT


Monitoring Pool Usage

The Memory performance counter object has separate counters for the size of nonpaged pool and paged pool (both virtual and physical). In addition, the Poolmon utility (in the Windows 2000 Support Tools) allows you to monitor the detailed usage of nonpaged and paged pool. To do so, you must have the internal Enable Pool Tagging option enabled. (Pool tagging is always turned on in the checked build, so you need to enable it only on a retail system.) To enable pool tagging, run the Gflags utility in the Windows 2000 Support Tools, Platform SDK, or DDK, and select Enable Pool Tagging, as shown here:

Then click Apply, and reboot the system. After the system reboots, run Poolmon; you should see a display like this one:

The highlighted lines represent changes to the display. (You can disable the highlighting feature by typing l while running Poolmon. Type l again to reenable highlighting.) Type ? while Poolmon is running to bring up its help screen. You can configure which pools you want to monitor (paged, nonpaged, or both) and the sort order. Also, the command-line options are shown, which allow you to monitor specific structures (or everything but one structure type). For example, the command poolmon -iCM will monitor only structures of type CM (the configuration manager, which manages the registry). The columns have the following meanings:

Column Explanation
Tag Four-byte tag given to the pool allocation
Type Pool type (paged or nonpaged pool)
Allocs Count of all allocations (The number in parentheses shows the difference in the Allocs column since the last update.)
Frees Count of all Frees (The number in parentheses shows the difference in the Frees column since the last update.)
Diff Allocs minus Frees
Bytes Total bytes consumed by this structure type (The number in parentheses shows the difference in the Bytes column since the last update.)
Per Alloc Size in bytes of a single instance of this structure type

In this example, CM structures are taking up the most space in paged pool, and MmSt structures (a memory management-related structure used for mapped files) are taking up the most space in nonpaged pool.

You can also view pool usage with the kernel debugger !poolused and !xpool commands. (!xpool is in the secondary kernel debugger extension DLL, Kdex2x86.dll.) The command !poolused 2 shows nonpaged pool usage sorted by structure tag using the most amount of pool. The command !poolused 4 lists paged pool usage, again sorted by structure tag using the most amount of pool. The following example shows the partial output from these two commands:

kd> .load kdex2x86 kd> !poolused 2 Sorting by NonPaged Pool Consumed Pool Used: NonPaged Paged Tag Allocs Used Allocs Used File 1702 326784 0 0 Thre 376 240640 0 0 Vad 3167 202688 0 0 Ntf0 3 196608 967 51264 Even 2869 187264 0 0 Npfs 335 154080 221 41504 Devi 249 148480 0 0 WDMA 333 146176 319 49984 Pool 3 134368 0 0 Irp 305 134272 0 0 AmlH 2 131072 0 0 kd> !poolused 4 Sorting by Paged Pool Consumed Pool Used: NonPaged Paged Tag Allocs Used Allocs Used CM 11 704 5146 22311904 Gh 5 0 0 727 2523648 MmSt 0 0 968 1975872 Ntff 10 2240 790 682560 Gla1 1 128 383 600544 Ttfd 0 0 265 545440 Gla5 1 128 1099 457184 NtFB 0 0 2 376832 LXMK 0 0 948 319680 Gla: 1 128 444 298368 Obtb 52 6656 68 278528 NtfF 0 0 250 248000 Gcac 0 0 54 243424 Mmpp 0 0 55 225280 Grgb 0 0 5 163840

The !xpool -map command displays a map of the status of each page in nonpaged pool (!xpool -map 1 displays paged pool) as well as total pool usage. The following are partial examples using this command:

kd> !xpool -map Status Map of Pool Area Pages ============================== 'O': one page in use ('P': paged out) '<': start page of contiguous pages in use ('{': paged out) '>': last page of contiguous pages in use ('}': paged out) '=': intermediate page of contiguous pages in use ('-': paged out) '.': one page not used Non-Paged Pool Area Summary ---------------------------- Maximum Number of Pages = 12940 pages Number of Pages In Use = 1459 pages (11.3%) +00000 +08000 +10000 +18000 +20000 +28000 +30000 +38000 81093000: ....OOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOO<=>OOOOO 810d3000: OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO <=>OOOOOOOOOOOOO 81113000: OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO 81153000: OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOO<=> 81193000: OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO 811d3000: OOOOOOOOOO<=>OOO OOOO<>OOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO 81213000: OOOOOOOOOOOO<=== ><============== ===============> <==========><=== 81253000: ================ ================ ================ =====><========= 81293000: ================ ================ ================ ================ 812d3000: ==><===========> OOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOOOOOO<== 81313000: ================ ====><======>O<> OOOOOO<>OOO<=>OO OO<======>O<==== 81353000: ==>OOOOOOOOOOOOO <=============== ==><==>OOO<><=== ===>O<=>OOOOOOOO 81393000: O<===>OOOOOOOO<= ========>OOOOOOO OOOOOOOOOOOOOOOO OOOOOOOOO<=><=== 813d3000: ====>OO<>OOOOO<= =====><=>OOOOOOO OOOOOOOOOOOO<=== ===========><=== 81413000: ===========>OOOO OOOOO<========== ====>OOOOOOOOOOO OOOOOOOOOO<===== 81453000: =========>OOOOOO OOOO<=>OOOOOOOO< ==============>O OOOO<==========> 81493000: OOOOOOOOOOOOOOOO <======> fcd6c000: ................ ................ ................ ................ fcdac000: ................ ................ ................ ................ fcdec000: ................ ................ ................ ................ kd> !xpool -map 1 Status Map of Pool Area Pages ============================== 'O': one page in use ('P': paged out) '<': start page of contiguous pages in use ('{': paged out) '>': last page of contiguous pages in use ('}': paged out) '=': intermediate page of contiguous pages in use ('-': paged out) '.': one page not used Paged Pool Area Summary ------------------------ Maximum Number of Pages = 26624 pages Number of Pages In Use = 8685 pages (32.6%) +00000 +08000 +10000 +18000 +20000 +28000 +30000 +38000 e1000000: OOOOO<>OO<><>POO OOOOOOOOPPPPPPPO PPPPPPPPPPPPPOPO OPPPPPPPPPPPPOOO e1040000: OOPPOPPPPPPPOOOO OOOOOPOOOPPPPPPP POOOOOOOOOOOOOOO PO{}POPPOPOPPPPP e1080000: OPPPOOPPPPPPPPOO {>PPPPPPPOPPPPPP PPPPPPPPPPPPPPOO OP{}PPPPPPPPPPPP e10c0000: PPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPP PPPPPPPPPOOPPPPP OOOOOOOOPOOOOOOO e1100000: POOOOOOOOOOOO{>O OOOOOOOOOOOOOOOO OOOOOOOOOOOOPOOO OOOOOOOOOOOOOOOO e1140000: OOPPOOOOOOOOOOOO OPPOOOOOO<>OOPPO OOOOPPPPPPOPPPPP PPPPPPPPPOPPOPPP e1180000: PPPPPPPPPPOOPPPP PPPPPPPPPPPPPPPP PPPPOPPPPPPPPPPP PPPPPPPPPPPPPOP{ e11c0000: >OPPOPPPPPPPPPPP POPPPPPPPPPPP{}P PPPPPPPPPPPPPPPP PPPPPPPPPPPPP{}P e1200000: PPPPPPPPPPPPPPPP PPPPPPOPPPOPPOPP PPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPP e1240000: PPPPOPPPPPPPOOPP PPPPPPPPP{}PPPPP PPPPPPPPPPOPOPPP OPOOPPPPPPPPPPOP e1280000: PPPPPPOPPPPPOOPP POPOPPPPPPPPP<>O PPPOPO{>PPPPOPOO POPP{}POPPOPPPPP e12c0000: PPPOOPOOOPOPPPPP PPPPOOOPPPPPPPPP PPPPPPOOPPOPPPOO POOPOOOOOOPOPPOO

Look-Aside Lists

Windows 2000 also provides a fast memory allocation mechanism called look-aside lists. The basic difference between pools and look-aside lists is that while general pool allocations can vary in size, a look-aside list contains only fixed-sized blocks. Although the general pools are more flexible in terms of what they can supply, look-aside lists are faster because they don't use any spinlocks and also because the system doesn't have to search for free memory that fits a varying size allocation.

Executive components and device drivers can create look-aside lists that match the size of frequently allocated data structures using the ExInitializeNPagedLookasideList and ExInitializePagedLookasideList functions (documented in the DDK). To minimize the overhead of multiprocessor synchronization, several executive subsystems (such as the I/O manager, cache manager, and object manager) create separate look-aside lists for each processor for their frequently accessed data structures. The executive also creates a general per-processor paged and nonpaged look-aside list for small allocations (256 bytes or less).

If a look-aside list is empty (as it is when it's first created), the system must allocate from paged or nonpaged pool. But if it contains a freed structure, the allocation can be satisfied very quickly. (The list grows as structures are returned to it.) The pool allocation routines automatically tune the number of freed buffers that look-aside lists store according to how often a device driver or executive subsystem allocates from the list—the more frequent the allocations, the more buffers are stored on a list. Look-aside lists are automatically reduced in size if they aren't being allocated from. (This check happens once per second when the balance set manager system thread wakes up and calls the function KiAdjustLookasideDepth.)

EXPERIMENT


Viewing the System Look-Aside Lists

You can display the contents and sizes of the various system look-aside lists with the kernel debugger !lookaside command. The following excerpt is from the output of this command:

kd> !lookaside Lookaside "nt!IopSmallIrpLookasideList" @ 804758a0 "Irps" Type = 0000 NonPagedPool Current Depth = 3 Max Depth = 4 Size = 148 Max Alloc = 592 AllocateMisses = 32 FreeMisses = 9 TotalAllocates = 52 TotalFrees = 32 Hit Rate = 38% Hit Rate = 71% Lookaside "nt!IopLargeIrpLookasideList" @ 804756a0 "Irpl" Type = 0000 NonPagedPool Current Depth = 4 Max Depth = 4 Size = 436 Max Alloc = 1744 AllocateMisses = 2623 FreeMisses = 2443 TotalAllocates = 7039 TotalFrees = 6863 Hit Rate = 62% Hit Rate = 64% Lookaside "nt!IopMdlLookasideList" @ 80475740 "Mdl " Type = 0000 NonPagedPool Current Depth = 3 Max Depth = 4 Size = 120 Max Alloc = 480 AllocateMisses = 7017 FreeMisses = 1824 TotalAllocates = 10901 TotalFrees = 5711 Hit Rate = 35% Hit Rate = 68% Total NonPaged currently allocated for above lists = 4200 Total NonPaged potential for above lists = 6144 Total Paged currently allocated for above lists = 5136 Total Paged potential for above lists = 12032

Driver Verifier

Driver Verifier is a mechanism that can be used to help find and isolate commonly found bugs in device driver or other kernel-mode system code. Microsoft uses Driver Verifier to check all device drivers that vendors submit for Hardware Compatibility List (HCL) testing. Doing so ensures that the drivers on the HCL are compatible with Windows 2000 and free from common driver errors.

Driver Verifier consists of support in several system components: the memory manager, I/O manager, HAL, and Win32k.sys all have driver verification options that can be enabled. This section describes the memory management-related verification options Driver Verifier provides.

Driver Verifier Configuration and Initialization

To configure Driver Verifier and view statistics about its operation, run the Driver Verifier Manager (\Winnt\System32\Verifier.exe). As shown in Figure 7-5, when you run Driver Verifier, it displays several tabbed pages. You use the Settings tab to specify which device drivers you want to verify (there's an option to Verify All Drivers) and what types of verification you want performed.

Figure 7-5 The Driver Verifier Manager

The settings are stored in the registry under HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management. The value VerifyDriverLevel contains a bitmask that represents the verification types enabled. The VerifyDrivers value contains the names of the drivers to validate. (These values won't exist in the registry until you select drivers to verify in the Driver Verifier Manager.) If you choose to verify all drivers, VerifyDrivers is set to an asterisk (*) character. After you've entered or changed Driver Verifier settings, you might need to reboot the system for verification to occur.

Early in the boot process, the memory manager reads the Driver Verifier registry values to determine which drivers to verify and which Driver Verifier options you enabled. Subsequently, if you've selected at least one driver for verification, the kernel checks the name of every device driver it loads into memory against the list of drivers you've selected for verification. For every device driver that appears in both places, the kernel invokes the MiApplyDriverVerifier function, which replaces the driver's references to any of approximately 40 kernel functions with references to Driver Verifier-equivalent versions of those functions. For example, ExAllocatePool is replaced with a call to VerifierAllocatePool. The windowing system driver also makes similar changes to use Driver Verifier-equivalent functions.

Now that we've reviewed how to set up Driver Verifier, let's examine the four memory-related verification options that can be applied to device drivers: Special Pool, Pool Tracking, Force IRQL Checking, and Low Resources Simulation.

Special Pool The Special Pool option causes the pool allocation routines to bracket pool allocations with an invalid page so that references before or after the allocation will result in a kernel-mode access violation, thus crashing the system with the finger pointed at the buggy driver. Special pool also causes some additional validation checks to be performed when a driver allocates or frees memory.

When special pool is enabled, the pool allocation routines allocate a region of kernel memory for Driver Verifier to use. Driver Verifier redirects memory allocation requests that drivers under verification make to the special pool area rather than to the standard kernel-mode memory pools. When a device driver allocates memory from special pool, Driver Verifier rounds up the allocation to an even-page boundary. Because Driver Verifier brackets the allocated page with invalid pages, if a device driver attempts to read or write past the end of the buffer, the driver will access an invalid page and the memory manager will raise a kernel-mode access violation.

Figure 7-6 shows an example of the special pool buffer that Driver Verifier allocates to a device driver when Driver Verifier checks for overrun errors.

Figure 7-6 Layout of special pool allocations

By default, Driver Verifier performs overrun detection. It does this by placing the buffer that the device driver uses at the end of the allocated page and fills the beginning of the page with a random pattern. Although the Driver Verifier Manager doesn't let you specify underrun detection, you can set this type of detection manually by adding the DWORD registry value HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\MemoryManagement\PoolTagOverruns and setting it to 1 (or by running the Gflags utility and selecting the Verify Start option instead of the default option Verify End). When Windows 2000 enforces underrun detection, Driver Verifier allocates the driver's buffer at the beginning of the page rather than at the end.

The overrun-detection configuration includes some measure of underrun detection as well. When the driver frees its buffer to return the memory to Driver Verifier, Driver Verifier ensures that the pattern preceding the buffer hasn't changed. If the pattern is modified, the device driver has underrun the buffer and written to memory outside the buffer.

Special pool allocations also check to ensure that the processor IRQL at the time of an allocation and deallocation is legal. This check catches an error that some device drivers make: allocating pageable memory from an IRQL at DPC/dispatch level or above.

You can also configure special pool manually by adding the DWORD registry value HKLM\SYSTEMCurrentControlSet\Control\Session Manager\Memory Management\PoolTag, which represents the allocation tag the system uses for special pool. Thus, even if Driver Verifier isn't configured to verify a particular device driver, if the tag the driver associates with the memory it allocates matches what is specified in the PoolTag registry value, the pool allocation routines will allocate the memory from special pool. If you set the value of PoolTag to 0x0000002a or the wildcard (*), all memory that drivers allocate is from special pool, provided there's enough virtual and physical memory. (The drivers will revert to allocating from regular pool if there aren't enough free pages—bounding exists, but each allocation uses two pages).

Pool Tracking When device drivers allocate memory, they can specify an optional four-letter tag in their allocation requests. When you disable pool tracking, Windows 2000 ignores the tag. When you enable pool tracking, however, the pool allocation routines associate the tag with memory the driver allocates. Using Poolmon (part of the Windows 2000 Support Tools), a developer can view how much memory Windows 2000 assigned to each tag. Monitoring driver memory usage lets a developer detect memory leaks, an error that occurs when a driver fails to deallocate memory it no longer requires. Driver Verifier also shows general pool statistics on the Driver Verifier Manager's Pool Tracking tab. You can also use the !verifier kernel debugger command. This command shows more information than Driver Verifier and is useful to driver writers.

If pool tracking is enabled, the memory manager checks at driver unload time whether the driver freed all the memory allocations it made. If it didn't, it crashes the system, indicating the buggy driver.

Force IRQL Checking One of the most common device driver bugs occurs when a driver accesses pageable data or code when the processor on which the device driver is executing is at an elevated IRQL. As explained in Chapter 3, the memory manager can't service a page fault when the IRQL is DPC/dispatch level or above. The system often doesn't detect instances of a device driver accessing pageable data when the processor is executing at a high IRQL level because the pageable data being accessed happens to be physically resident at the time. At other times, however, the data might be paged out, which results in a system crash with the stop code IRQL_NOT_LESS_OR_EQUAL (that is, the IRQL wasn't less than or equal to the level required for the operation attempted—in this case, accessing pageable memory).

Although testing device drivers for this kind of bug is usually difficult, Driver Verifier makes it easy. If you select the Force IRQL Checking option, Driver Verifier forces all kernel-mode pageable code and data out of the system working set whenever a device driver under verification raises the IRQL. The internal function that does this is MmTrimAllSystemPagableMemory. With this setting enabled, whenever a device driver under verification accesses pageable memory when the IRQL is elevated, the system instantly detects the violation and the resulting system crash identifies the faulty driver.

Low Resources Simulation Enabling Low Resources Simulation causes Driver Verifier to randomly fail memory allocations that verified device drivers perform. In the past, developers wrote many device drivers under the assumption that kernel memory would always be available and that if memory ran out, the device driver didn't have to worry about it because the system would crash anyway. However, because low-memory conditions can occur temporarily, it's important that device drivers properly handle allocation failures that indicate kernel memory is exhausted.

Beginning 7 minutes after the system boots—which is enough time to get past the critical initialization period in which a low-memory condition might prevent a device driver from loading—Driver Verifier starts randomly failing allocation calls for device drivers it is verifying. If a driver doesn't correctly handle allocation failures, this will likely show up as a system crash.

Driver Verifier is a valuable addition to the arsenal of verification and debugging tools available to device driver writers. Many device drivers that first ran with Driver Verifier had bugs that Driver Verifier was able to expose. Thus, Driver Verifier has resulted in an overall improvement in the quality of all kernel-mode code running on Windows 2000.

Категории