If you really need a huge buffer of physically contiguous memory, you need to allocate it by requesting memory at boot time. This technique is inelegant and inflexible, but it is also the least prone to failure. Needless to say, a module can’t allocate memory at boot time; only drivers directly linked to the kernel can do that.
Allocation at boot time is the only way to retrieve consecutive memory pages while bypassing the limits imposed by get_free_pages on the buffer size, both in terms of maximum allowed size and limited choice of sizes. Allocating memory at boot time is a “dirty” technique, because it bypasses all memory management policies by reserving a private memory pool.
One noticeable problem with boot-time allocation is that it is not a feasible option for the average user: being only available for code linked in the kernel image, a device driver using this kind of allocation can only be installed or replaced by rebuilding the kernel and rebooting the computer. Fortunately, there are a pair of workarounds to this problem, which we introduce soon.
Even though we won’t suggest allocating memory at boot time, it’s
something worth mentioning because it used to be the only way to
allocate a DMA-capable buffer in the first Linux versions, before
__GFP_DMA
was introduced.
When the kernel is booted, it gains access to all the physical memory available in the system. It then initializes each of its subsystems by calling that subsystem’s initialization function, allowing initialization code to allocate a memory buffer for private use by reducing the amount of RAM left for normal system operation.
With version 2.4 of the kernel, this kind of allocation is performed by calling one of these functions:
#include <linux/bootmem.h> void *alloc_bootmem(unsigned long size); void *alloc_bootmem_low(unsigned long size); void *alloc_bootmem_pages(unsigned long size); void *alloc_bootmem_low_pages(unsigned long size);
The functions allocate either whole pages (if they end with
_pages
) or non-page-aligned memory areas. They
allocate either low or normal memory (see the discussion of memory
zones earlier in this chapter). Normal allocation returns memory
addresses that are above MAX_DMA_ADDRESS
; low
memory is at addresses lower than that value.
This interface was introduced in version 2.3.23 of the kernel. Earlier
versions used a less refined interface, similar to the one described
in Unix books. Basically, the initialization functions of several
kernel subsystems received two unsigned long
arguments, which represented the current bounds of the free memory
area. Each such function could steal part of this area, returning the
new lower bound. A driver allocating memory at boot time, therefore,
was able to steal consecutive memory from the linear array of
available RAM.
The main problem with this older mechanism of managing boot-time allocation requests was that not all initialization functions could modify the lower memory bound, so writing a driver needing such allocation usually implied providing users with a kernel patch. On the other hand, alloc_bootmem can be called by the initialization function of any kernel subsystem, provided it is performed at boot time.
This way of allocating memory has several disadvantages, not the least
being the inability to ever free the buffer. After a driver has taken
some memory, it has no way of returning it to the pool of free pages;
the pool is created after all the physical allocation has taken place,
and we don’t recommend hacking the data structures internal to memory
management. On the other hand, the advantage of this technique is
that it makes available an area of consecutive physical memory that is
suitable for DMA. This is currently the only safe way in the standard
kernel to allocate a buffer of more than 32 consecutive pages, because
the maximum value of order
that is accepted by
get_free_pages is 5. If, however, you need many
pages and they don’t have to be physically contiguous,
vmalloc is by far the best function to use.
If you are going to resort to grabbing memory at boot time, you must
modify init/main.c
in the kernel sources. You’ll
find more about main.c
in Chapter 16.
Note that this “allocation” can be performed only in multiples of the page size, though the number of pages doesn’t have to be a power of two.
Another approach that can be used to make large, contiguous memory regions available to drivers is to apply the bigphysarea patch. This unofficial patch has been floating around the Net for years; it is so renowned and useful that some distributions apply it to the kernel images they install by default. The patch basically allocates memory at boot time and makes it available to device drivers at runtime. You’ll need to pass a command-line option to the kernel to specify the amount of memory that must be reserved at boot time.
The patch is currently maintained at http://www.polyware.nl/~middelink/En/hob-v4l.html. It includes its own documentation that describes the allocation interface it offers to device drivers. The Zoran 36120 frame grabber driver, part of the 2.4 kernel (in drivers/char/zr36120.c) uses the bigphysarea extension if it is available, and is thus a good example of how the interface is used.
The last option for allocating contiguous memory areas, and possibly
the easiest, is reserving a memory area at the
end of physical memory (whereas
bigphysarea reserves it at the beginning of
physical memory). To this aim, you need to pass a command-line option
to the kernel to limit the amount of memory being managed. For
example, one of your authors uses mem=126M
to
reserve 2 megabytes in a system that actually has 128 megabytes of
RAM. Later, at runtime, this memory can be allocated and used by
device drivers.
The allocator module, part of the sample code released on the O’Reilly FTP site, offers an allocation interface to manage any high memory not used by the Linux kernel. The module is described in more detail in Section 13.4.2.1 in Chapter 13.
The advantage of allocator over the bigphysarea patch is that there’s no need to modify official kernel sources. The disadvantage is that you must change the command-line option to the kernel whenever you change the amount of RAM in the system. Another disadvantage, which makes allocator unsuitable in some situations is that high memory cannot be used for some tasks, such as DMA buffers for ISA devices.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.