The best technique for allocating memory
depends on how the memory will be used. Table 3 summarizes the Windows
kernel-mode memory allocation routines that are discussed in this section.
Table 3. Summary of Memory Allocation
Routines
Name
|
Description
|
ExAllocatePoolXxx
|
Allocates paged
or nonpaged, cached, and cache-aligned memory from the kernel-mode pool.
ExAllocatePoolWithTag is the primary function
for memory allocation.
|
AllocateCommonBuffer
|
Allocates a buffer
for common-buffer DMA and ensures cross-platform compatibility.
|
MmAllocateContiguousMemory
[SpecifyCache] |
Allocates
nonpaged, physically contiguous, cache-aligned memory.
Drivers should
not use these functions to allocate memory for DMA because the addresses that
they return are not guaranteed to be compatible across all hardware
configurations. Drivers should use AllocateCommonBuffer
instead.
|
MmAllocateMappingAddress
|
Reserves virtual
addresses in a specific range for later use in an MDL, but does not map them to
physical memory.
The driver can
later use IoAllocateMdl, MmProbeAndLockPages, and MmMapLockedPagesWithReservedMapping
to map the virtual addresses to physical memory.
|
MmAllocateNoncachedMemory
|
Allocates
nonpaged, noncached, page-aligned memory. Drivers rarely use this function;
they must not use it to allocate buffers for DMA.
|
MmAllocatePagesForMdl
|
Allocates nonpaged, noncontiguous pages from physical memory,
specifically for an MDL. An MDL can describe up to 4 GB. This function might
return fewer pages than the caller requested. Therefore, the driver must call
MmGetMdlByteCount to verify the
number of allocated bytes.
A driver can call this function to allocate physical memory from a
particular address range, such as allocating memory for a device that cannot
address memory above 4 GB. However, drivers should not use this function to
allocate buffers for DMA. Only the Windows DMA routines guarantee the
cross-platform compatibility that drivers require.
|
The following sections cover techniques
for allocating memory for several specific purposes:
·
Pool memory for long-term
storage
·
Lookaside lists
·
Contiguous memory
Allocating Memory for Long-Term Storage
Pool memory is appropriate for most
long-term driver storage requirements. The pool allocation routines allocate cached
memory from the paged or nonpaged kernel-mode pools. Drivers should use pool
memory unless they have other specific requirements, such as the following:
·
The device or driver requires
more than a page of physically contiguous memory. (Remember that page size
varies depending on the machine architecture.)
·
The device requires noncached
or write-combined memory. Some video devices fall into this category.
·
The device has constraints that
limit the range of addresses it can use.
Pool memory is a limited resource, so drivers
should allocate it economically. When you design your driver, plan your memory
allocations according to memory type, allocation size, and allocation lifetime.
Free the allocated memory as soon as you are done using it.
If your driver requires large amounts of nonpaged
pool for long-term use, allocate the memory at startup if possible, either in
the DriverEntry routine or in an AddDevice routine. Because the nonpaged
pool becomes fragmented as the system runs, later attempts to allocate memory
from the nonpaged pool could fail.
A driver should not, however, preallocate
excessively large blocks of memory (several megabytes, for example) and try to
manage its own allocations within that block. Although this technique might
work for your driver, it often results in inefficient memory usage across the
system and thus poor performance system-wide. Remember that other drivers and
applications also require memory. Never allocate lots of memory “just in case”
your driver might need it. As a general design guideline, consider preallocating
only enough memory for one I/O transaction and then process the transactions
serially. If the transactions all require same-sized allocations, consider
using lookaside lists because these buffers are dynamically allocated.
Drivers can allocate cached memory from either
the paged pool or the nonpaged pool by using the following ExAllocatePoolXxx routines:
·
ExAllocatePoolWithTag
·
ExAllocatePoolWithTagPriority
·
ExAllocatePoolWithQuotaTag
Drivers must use the tagged versions of
the pool allocation routines because the nontagged versions are obsolete. Both
WinDbg and Driver Verifier use pool tags to track memory allocations. Tagging
pool allocations can simplify finding memory-related bugs, including leaks. Be
sure to use unique tags so that the debugging and testing tools can provide
useful information.
Note
The pool type NonPagedPoolMustSucceed has been deprecated. Drivers should specify
the pool type NonPagedPool or NonPagedPoolCacheAligned instead and
should return the NTSTATUS value STATUS_INSUFFICIENT_RESOURCES if the allocation
fails. The Driver Verifier flags any pool allocation that is specified as NonPagedPoolMustSucceed as fatal and causes
a bug check. (Note that on versions of Windows earlier than Microsoft Windows
Vista™ specifying NonPagedPoolCacheAligned
is the same as specifying NonPagedPool.
On Windows Vista, however, specifying NonPagedPoolCacheAligned
ensures that the system allocates cache-aligned memory.)
In the following example, the driver
allocates a buffer from nonpaged pool, aligned on a processor cache-line
boundary:
MyBuffer =
ExAllocatePoolWithTag(NonPagedPoolCacheAligned,
MAX_BUFFER_SIZE,
'tseT');
if (MyBuffer == NULL)
{
DebugPrint((1, " Can't allocate MyBuffer \n"));
return(STATUS_INSUFFICIENT_RESOURCES);
}
The example specifies the pool tag in
reverse order so that it appears as “Test” in the debugger. (You should use a
tag that uniquely identifies your driver.) In the example, MAX_BUFFER_SIZE
specifies the number of bytes to allocate. Because of rounding and alignment,
the driver might receive a buffer of exactly the requested size or a larger
buffer. The ExAllocatePoolXxx routines align and round
allocations as follows:
Size requested
|
Size allocated
|
Default alignment
|
Less than or equal to
(PAGE_SIZE – sizeof (POOL_HEADER)) |
Number of bytes requested
|
8-byte boundary (16-byte on 64-bit Windows). Allocation is contiguous
and does not cross page boundary.
|
Greater than
(PAGE_SIZE – sizeof (POOL_HEADER)) |
Number of bytes requested, rounded up to next full page size
|
Page-aligned. Pages are not necessarily physically contiguous, but
they are always virtually contiguous.
|
The pool header is 8 bytes long on 32-bit
systems and 16-bytes long on 64-bit systems. The size of the pool header does not
affect the caller.
To use the pool most efficiently, avoid
calling the memory allocation routines repeatedly to request small allocations
of less than PAGE_SIZE. If your driver normally uses several related structures
together, consider bundling those structures into a single allocation at driver
start-up. For example, the SCSI port driver bundles an IRP, a SCSI request
block (SRB), and an MDL into a single allocation. By allocating the memory at
start-up, you reduce the number of times your driver calls memory allocation
routines and therefore the number of situations in which it must be prepared to
handle an allocation failure. Eliminating such failure scenarios greatly
simplifies the error paths in your driver.
Pool memory remains allocated until the
driver explicitly frees it. Before the driver exits, it must call ExFreePool or ExFreePoolWithTag to free all the memory that it allocated with any
of the ExAllocatePoolXxx variants. Failing to free the
memory causes memory leaks, which can eventually slow system performance and
cause other components to fail.
Creating and Using Lookaside Lists
A driver calls ExInitialize[N]PagedLookasideList
to set up a lookaside list, ExAllocateFrom[N]PagedLookasideList to allocate
a buffer from the list, and ExFreeTo[N]PagedLookasideList to free a
buffer to the list.
Even if a lookaside list is allocated in
paged memory, the head of the list must be allocated in nonpaged memory because
the system scans it at DISPATCH_LEVEL to perform various bookkeeping chores.
Typically, drivers store a pointer to the head of the list in the device
extension.
For example, a network adapter driver
that receives data into fixed-size blocks might set up a lookaside list for the
blocks as follows:
ExInitializeNPagedLookasideList
(&FdoData->RecvLookasideList,
NULL,
NULL,
0,
RecvBlockSize,
‘LciN’,
0);
The lookaside list in the example is
allocated from nonpaged memory because the driver accesses it at DISPATCH_LEVEL
or higher. The first parameter identifies the location in nonpaged memory where
the driver stores the head of the list. This location must be at least sizeof (NPAGED_LOOKASIDE_LIST). The two
NULL parameters represent the driver routines that the system calls to allocate
and free buffers from the list. If the driver passes NULL, the system uses ExAllocatePoolWithTag and ExFreePoolWithTag. Although a driver
can supply its own routines, using the system’s allocation routines is generally
more efficient. The fourth parameter supplies internal flags and must be zero.
The fifth parameter, RecvBlockSize, specifies the size of each buffer in the
list, and the sixth is the tag used for the pool allocation. The final
parameter is also reserved for internal use and must be zero.
Later, to allocate and use a buffer from
the list, the driver calls ExAllocateFromNPagedLookasideList,
as follows:
pRecvBlock =
ExAllocateFromNPagedLookasideList (
&FdoData->RecvLookasideList);
If the allocation fails for any reason,
the allocation routine returns a NULL pointer. The driver should validate the
result and either return an error or retry the operation until it receives a
valid pointer.
To free a buffer that was allocated from
the list, the driver calls ExFreeToNPagedLookasideList,
passing a pointer to the head of the list and a pointer to the buffer to free,
as follows:
ExFreeToNPagedLookasideList (
&FdoData->RecvLookasideList,
pRecvBlock);
To avoid using memory unnecessarily,
drivers should free buffers as soon as they are no longer needed.
Finally, when the driver no longer needs
any buffers from the list, it calls ExDeleteNPagedLookasideList.
ExDeleteNPagedLookasideList (
&FdoData->RecvLookasideList);
The list need not be empty because this
function deletes any remaining buffers and then deletes the list itself.
Drivers that run on Windows XP and later
versions can use lookaside lists to allocate scatter/gather lists for DMA, as
described in “DMA Support in Windows Drivers,” which is listed in the Resources
section of this paper.
Allocating Contiguous Memory
Drivers that require a page or more of
contiguous memory can allocate it by using one of the following:
·
The DMA functions AllocateCommonBuffer or GetScatterGatherList
·
MmAllocatePagesForMdl
·
MmAllocateContiguousMemorySpecifyCache
To allocate memory for DMA buffers,
drivers should call AllocateCommonBuffer
or GetScatterGatherList. For more
information about these DMA functions, see the Windows DDK and “DMA Support in
Windows Drivers,” which are listed in the Resources section of this paper.
MmAllocatePagesForMdl allocates pages from a specified range of physical addresses. Therefore,
it is useful for drivers of devices that cannot address memory in certain
ranges. For example, some devices cannot address memory above 4 GB. The driver
for such a device could use this function to ensure that a buffer was allocated
in memory below 4 GB. However, that this function allocates independent pages
that are not physically contiguous; that is, each entry in the MDL maps a page
of memory, but pages are typically not contiguous with respect to each other.
If your device requires no more than a page of contiguous memory at a time, but
requires many such pages, MmAllocatePagesForMdl
is appropriate. Do not use this function to allocate buffers for DMA—use the
DMA routines instead.
Drivers that need larger amounts of
physically contiguous memory should use MmAllocateContiguousMemorySpecifyCache.
Drivers should try to allocate such memory during driver initialization because
physical memory is likely to become fragmented as the system runs. A driver
should allocate only as much contiguous memory as it needs, and it should free
the memory as soon as it is no longer needed.
MmAllocateContiguousMemorySpecifyCache returns the base virtual address of the mapped region. The
following sample shows how a driver might allocate contiguous memory in a
specific range:
LowestAcceptable.QuadPart = 0;
BoundaryMultiple.QuadPart = 0;
HighestAcceptable.QuadPart = 0xFFFFFFFF;
MemBuff =
MmAllocateContiguousMemorySpecifyCache(MemLength,
LowestAcceptable,
HighestAcceptable,
BoundaryMultiple,
MmNonCached);
if (MemBuff == NULL) {
return(STATUS_INSUFFICIENT_RESOURCES);
}
In the example, the driver allocates
contiguous, noncached memory in the first 4 GB of the physical address space.
MemLength specifies the number of required contiguous bytes. The driver passes
zero in the LowestAcceptable parameter to indicate that the range of addresses
it can use starts at zero, and it passes 0xFFFFFFFF in the HighestAcceptable
parameter to indicate that the range ends at 4 GB. It sets BoundaryMultiple to zero
because there are no boundaries within the specified range that the memory
should not cross. The function returns a full multiple of the system’s page
size. If MemLength is not a multiple of the page size, the function rounds up
to the next full page.
Do not use MmAllocateContiguousMemorySpecifyCache to allocate buffers for DMA
and then call MmGetPhysicalAddress
to translate the returned virtual address to an address with which to program
your DMA controller. This approach does not work on all hardware, and your
device and driver will not be cross-platform compatible. Use AllocateCommonBuffer or GetScatterGatherList instead.
0 komentar:
Posting Komentar