vmalloc and Virtually Contiguous Memory
vmalloc()allocates a region of kernel memory that is contiguous in virtual address space but not in physical memory. Where the kmalloc family returns memory that is contiguous both virtually and physically (a slice of the direct map),vmalloc()gathers physically-scattered single pages from the page allocator and stitches them together by building a fresh page-table mapping in a dedicatedvmallocvirtual-address window — so the caller sees one long contiguous buffer even though the underlying frames are sprinkled all over RAM. This buys the ability to allocate large buffers when physical fragmentation makes a contiguous run impossible, at the cost of building and tearing down page-table entries, a guard page per allocation, and extra TLB pressure (permm/vmalloc.c, v6.12 and thevmalloc(9)man page). It is the right tool for big, rarely-allocated, software-only buffers (module text, large kernel-internal tables); the wrong tool for small, hot, or DMA-able allocations.
This note explains the mechanism — the vm_struct/vmap_area bookkeeping, the guard page, the page-table build, and the vfree/vmap/ioremap family — and contrasts the cost against kmalloc. The head-to-head decision guide lives in the dedicated comparison note kmalloc vs vmalloc; this note owns the vmalloc mechanism.
Mental Model
The defining trick: physical scatter, virtual gather. The page allocator can readily hand out many individual order-0 pages even under heavy fragmentation, but it struggles to find a large physically contiguous run. vmalloc() sidesteps the problem entirely — it grabs the scattered pages, reserves a contiguous slice of virtual address space in the vmalloc window, and programs the page tables so that consecutive virtual pages point at those scattered physical frames.
flowchart TB subgraph PHYS["Physical RAM (fragmented)"] F1["frame 0x4a"] F2["frame 0x9c"] F3["frame 0x17"] F4["frame 0xe1"] end subgraph VA["vmalloc virtual window (contiguous)"] direction LR V1["vaddr+0"] V2["vaddr+4K"] V3["vaddr+8K"] V4["vaddr+12K"] GUARD["guard page<br/>(unmapped)"] V1 --- V2 --- V3 --- V4 --- GUARD end PT["fresh page-table entries<br/>(built by vmap_pages_range)"] V1 -->|"PTE"| PT --> F1 V2 -->|"PTE"| PT V3 -->|"PTE"| PT V4 -->|"PTE"| PT PT -.-> F2 PT -.-> F3 PT -.-> F4
The vmalloc mapping. What it shows: four physically-scattered frames mapped by freshly-built page-table entries into four consecutive virtual pages in the vmalloc window, followed by an unmapped guard page. The insight: the contiguity is an illusion maintained by the MMU — there is no physical contiguity and no fixed virtual↔physical offset (unlike the direct map, where virt_to_phys() is a simple subtraction). Because the mapping is per-allocation rather than pre-existing, every vmalloc() does real page-table work, and dereferencing a vmalloc pointer can take a page fault the first time a CPU touches it (the famous “vmalloc fault” that syncs the per-process PGD with the kernel’s master vmalloc mappings).
Contrast this with the direct map: that is a single, permanent linear mapping of all physical RAM, so kmalloc memory needs no new page-table entries and virt_to_phys() is addr - PAGE_OFFSET. vmalloc memory has no such relationship — vmalloc_to_page() must walk the page tables to find the backing frame.
The Address Range
vmalloc allocations live in a reserved virtual window, [VMALLOC_START, VMALLOC_END), distinct from the direct map. On x86-64 with 4-level paging (48-bit) the vmalloc/ioremap space runs from ffffc90000000000 to ffffe8ffffffffff — 32 TiB — sitting just above the 64 TiB direct map at ffff888000000000–ffffc87fffffffff. On 5-level paging (57-bit) the vmalloc space balloons to 12.5 PiB (ffa0000000000000–ffd1ffffffffffff) above a 32 PiB direct map (per Documentation/arch/x86/x86_64/mm.rst). With CONFIG_RANDOMIZE_MEMORY (KASLR for the kernel memory regions) these bases are randomized, though their relative ordering is preserved.
The vast 32 TiB vmalloc window on x86-64 is why vmalloc can satisfy very large allocations — virtual address space is cheap. It is also where kernel module code, kernel stacks (VMAP_STACK), and ioremap MMIO windows are placed.
Mechanical Walk-through
Bookkeeping: vmap_area and vm_struct
Two structures track every vmalloc region. struct vmap_area describes a reserved virtual range — va_start/va_end plus an rbtree node — and is the unit the global KVA (kernel virtual address) allocator manages in red-black trees of free and busy areas (the “Improving global KVA allocator” rework by Uladzislau Rezki, 2019, noted in the file header). struct vm_struct is the higher-level descriptor of an allocation (include/linux/vmalloc.h, v6.12):
struct vm_struct {
struct vm_struct *next; /* early-boot vmlist chain */
void *addr; /* the virtual base returned to the caller */
unsigned long size; /* size INCLUDING the guard page */
unsigned long flags; /* VM_ALLOC, VM_IOREMAP, VM_MAP, ... */
struct page **pages; /* the array of backing physical pages */
#ifdef CONFIG_HAVE_ARCH_HUGE_VMALLOC
unsigned int page_order; /* >0 if backed by huge pages */
#endif
unsigned int nr_pages; /* number of backing pages */
phys_addr_t phys_addr; /* for ioremap: the physical base */
const void *caller; /* return address, shown in vmallocinfo */
};The pages array is the heart of it: a struct page * for every physical frame backing the region, kept so vfree() can later release each one. caller records the __builtin_return_address(0) of whoever allocated, which is why /proc/vmallocinfo can attribute each region to a function.
Allocation: vmalloc() → __vmalloc_node_range() → __vmalloc_area_node()
The public vmalloc(size) is a thin wrapper: it calls __vmalloc_node_noprof(size, 1, GFP_KERNEL, NUMA_NO_NODE, ...) → __vmalloc_node_range_noprof(size, ..., VMALLOC_START, VMALLOC_END, GFP_KERNEL, PAGE_KERNEL, 0, ...) (vmalloc_noprof, mm/vmalloc.c). The real work happens in two stages:
Stage 1 — reserve virtual space (__get_vm_area_node). It ALIGNs the size, allocates a struct vm_struct, and — critically — adds a guard page: if (!(flags & VM_NO_GUARD)) size += PAGE_SIZE; (line ~3110). It then calls alloc_vmap_area() to carve a [start, end) slice of unused virtual address space out of the free-area rbtree, recording it as a busy vmap_area. Note BUG_ON(in_interrupt()) — vmalloc cannot be called from interrupt/atomic context because it sleeps (page-table allocation and reclaim).
Stage 2 — allocate and map pages (__vmalloc_area_node). This is where the scatter-gather happens (lines ~3600–3713):
- It allocates the
pagesarray itself (viakmallocif it fits in a page, else recursively via__vmalloc— “the recursion is strictly bounded”). - It calls
vm_area_alloc_pages()to pullnr_small_pagesindividual pages from the buddy allocator (defaulting to__GFP_HIGHMEMso they may come from high memory — vmalloc doesn’t care about physical placement). Each page is recorded inarea->pages[i]. - It bumps the global counter:
atomic_long_add(area->nr_pages, &nr_vmalloc_pages)— this is what/proc/meminfo’sVmallocUsedreflects. If__GFP_ACCOUNTis set, each page is charged to the memcg (MEMCG_VMALLOC). - It calls
vmap_pages_range(addr, addr + size, prot, area->pages, page_shift)— the step that actually walks down PGD→P4D→PUD→PMD→PTE and writes a page-table entry for each backing page, making the scattered frames appear contiguous ataddr. This is the expensive, vmalloc-specific workkmallocnever does.
If huge-page backing is allowed (VM_ALLOW_HUGE_VMAP, via vmalloc_huge()) and the size is ≥ PMD_SIZE, it tries PMD-sized mappings to cut TLB pressure, falling back to order-0 pages if the high-order allocation fails.
Freeing: vfree()
vfree(addr) (lines ~3326–3368) reverses everything: it calls remove_vm_area() to unmap the region (tearing down the PTEs and unlinking the vmap_area), then loops over vm->pages[] calling __free_page() on each backing frame, subtracts from nr_vmalloc_pages, and frees the pages array and the vm_struct. Note the symmetry with MEMCG_VMALLOC accounting. vfree() may be called from interrupt context — it detects this (in_interrupt()) and defers the work via vfree_atomic() (the actual unmapping/freeing cannot run atomically because of TLB flush and might_sleep()).
A key cost hidden here: unmapping requires a TLB flush across all CPUs that might have cached the now-stale translations. To amortize this, vmalloc uses lazy unmapping — freed vmap_areas are batched on a per-node “lazy” purge list and the TLB is flushed once for many freed areas rather than once per vfree() (visible as “unpurged vm_area” entries in /proc/vmallocinfo’s purge dump).
The vmap() and ioremap() cousins
vmap(pages, count, flags, prot)maps an already-allocated array of pages into a fresh contiguous vmalloc region — it does the page-table build (vmap_pages_range) but not the page allocation. Used when the caller already owns the pages (e.g. mapping a set of pages from different sources into one view). Note the comment “your top guard is someone else’s bottom guard” —vmap()forcibly clearsVM_NO_GUARD, because removing a guard would compromise the neighbouring mapping.vunmap()is its teardown (frees the mapping, not the pages, unlessVM_MAP_PUT_PAGES).ioremap(phys, size)maps a range of physical MMIO addresses (device registers, not RAM) into the vmalloc/ioremap window with uncached/device protections, so drivers can access hardware registers through normal pointer dereferences. It setsVM_IOREMAPand recordsphys_addr. The backing “pages” are device memory, not buddy-allocated RAM.
The guard page
Every vmalloc region (unless VM_NO_GUARD) is followed by one unmapped guard page — the size += PAGE_SIZE in __get_vm_area_node. Because it is unmapped, a buffer overrun that walks off the end of a vmalloc allocation hits an unmapped page and faults immediately (a clean oops with a vmalloc-region address) rather than silently corrupting the adjacent allocation. This is a cheap, always-on safety net that kmalloc (packed slabs, no inter-object guards by default) does not provide — one of the underrated reasons to prefer vmalloc for buffers where overrun detection matters. The guard page costs one page of virtual address space (cheap, given 32 TiB) and no physical RAM.
Observability: /proc/vmallocinfo and /proc/meminfo
/proc/vmallocinfo dumps every busy vmap_area, one line per region, produced by vmalloc_info_show() (lines ~4943+):
0xffffc90000000000-0xffffc90000005000 20480 module_alloc+0x... pages=4 vmalloc N0=4
0xffffc90000005000-0xffffc90000007000 8192 acpi_os_map_iomem+0x... phys=00000000fed00000 ioremap
0xffffc9000000a000-0xffffc9000002b000 135168 alloc_large_system_hash+0x... pages=32 vmalloc N0=32
Reading the columns: the virtual range, the size in bytes (including guard page), the caller symbol (%pS of the recorded return address — this is how you attribute a leak to a subsystem), then flags-derived tags: pages=N (backing page count), phys=... (for ioremap), and one of vmalloc (VM_ALLOC), ioremap (VM_IOREMAP), vmap (VM_MAP), user (VM_USERMAP), dma-coherent, sparse, plus vpages if even the pages array itself was vmalloc’d. The trailing N0=4 is per-NUMA-node page distribution. The aggregate total surfaces as VmallocUsed in /proc/meminfo (driven by the nr_vmalloc_pages counter incremented in __vmalloc_area_node); VmallocTotal is the size of the whole window. /proc/vmallocinfo is readable only by root (addresses are sensitive).
Uncertain
Verify: whether
/proc/meminfo’sVmallocUsedexactly equalsnr_vmalloc_pages << PAGE_SHIFTon v6.12, or whethervmap/ioremap(non-VM_ALLOC) regions are excluded from that counter (the counter is only bumped in__vmalloc_area_node, which servesVM_ALLOCallocations, so pureioremap/vmapmappings may not be counted inVmallocUsed). Reason: the meminfo wiring was not directly inspected for this note. To resolve: readmm/vmstat.c/fs/proc/meminfo.cat v6.12 and confirm theVmallocUsedsource. uncertain
The Cost vs kmalloc
The trade-off is the whole point, and it falls out directly from the mechanism above:
| Dimension | kmalloc (slab on direct map) | vmalloc |
|---|---|---|
| Physical contiguity | Yes (one buddy run) | No (scattered order-0 pages) |
| Page-table work | None (direct map pre-exists) | Builds/tears down PTEs per allocation |
| TLB footprint | Maps via large direct-map entries (often 2 MiB/1 GiB) → few TLB entries | Order-0 PTEs → one TLB entry per 4 KiB; far more TLB pressure |
| Allocation speed | Fast (slab free-list pop) | Slow (buddy pulls + page-table build + possible reclaim) |
| Context | GFP_ATOMIC works in atomic/IRQ | Sleeps — never in interrupt/atomic context |
| DMA-safe | Yes (contiguous physical) | No — pages are scattered; needs IOMMU/scatter-gather |
| Max size | Limited by fragmentation | Limited by the (huge) vmalloc window |
| Overrun guard | None by default | Guard page after every region |
The TLB cost is the subtle one: because the direct map covers physical RAM with huge pages, a kmalloc buffer of any size typically lives under a handful of huge-page TLB entries, whereas a vmalloc buffer is mapped 4 KiB at a time, so a large vmalloc buffer can consume many TLB entries and thrash the TLB on access. (Huge-page vmalloc via vmalloc_huge()/VM_ALLOW_HUGE_VMAP exists precisely to mitigate this for large allocations, per the LWN coverage of huge-page vmalloc.) The full comparison and decision rules are in kmalloc vs vmalloc.
Failure Modes and Pitfalls
- DMA on vmalloc memory → silent corruption. A device DMAs to physical addresses; vmalloc’s scattered frames are not physically contiguous, so a naive
dma_map_single()of a vmalloc pointer maps the wrong physical range. Usekmalloc/dma_alloc_coherentfor DMA buffers, or build a scatter-gather list. Classic driver bug. virt_to_phys()on a vmalloc pointer → garbage.virt_to_phys()assumes the direct-map linear relationship, which vmalloc addresses do not have. Usevmalloc_to_page()(which walks the page tables) instead.- vmalloc in atomic context →
BUG_ON(in_interrupt()). It sleeps; you cannot call it under a spinlock or in an IRQ handler. - vmalloc address space exhaustion → on 32-bit kernels the vmalloc window is small (tens of MiB) and can genuinely run out, producing “vmalloc: allocation failure” even with free RAM; this drove much of the historical pain that the 64-bit huge window eliminated.
- “vmalloc fault” confusion → on architectures with per-process top-level page tables, a CPU’s process PGD may lack the newest kernel vmalloc PMD entries; the first touch faults and the handler lazily syncs the entry. Benign, but it means dereferencing a vmalloc pointer is not always free of faults.
See Also
- kmalloc vs vmalloc — the dedicated decision/comparison note: which allocator to reach for and why.
- kmalloc and the kmalloc Caches — the physically-contiguous slab-backed counterpart.
- Kernel Address Space and the Direct Map — the permanent linear mapping vmalloc deliberately is not; explains why
kmallocneeds no page-table work andvirt_to_phys()is a subtraction. - The Buddy Allocator — where vmalloc’s individual backing pages come from.
- The Translation Lookaside Buffer and TLB Shootdowns — the TLB-flush cost behind vmalloc’s lazy unmapping and the per-4 KiB TLB pressure of vmalloc mappings.
- Linux Page Table Hierarchy — the PGD→PTE structure
vmap_pages_range()walks and writes. - The Memory Cgroup memcg —
__GFP_ACCOUNTcharges vmalloc pages toMEMCG_VMALLOC. - UP: Linux Memory Management MOC §5 (Slab Allocation and Kernel Object Allocation).