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 dedicated vmalloc virtual-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 (per mm/vmalloc.c, v6.12 and the vmalloc(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 ffffe8ffffffffff32 TiB — sitting just above the 64 TiB direct map at ffff888000000000ffffc87fffffffff. On 5-level paging (57-bit) the vmalloc space balloons to 12.5 PiB (ffa0000000000000ffd1ffffffffffff) 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):

  1. It allocates the pages array itself (via kmalloc if it fits in a page, else recursively via __vmalloc — “the recursion is strictly bounded”).
  2. It calls vm_area_alloc_pages() to pull nr_small_pages individual pages from the buddy allocator (defaulting to __GFP_HIGHMEM so they may come from high memory — vmalloc doesn’t care about physical placement). Each page is recorded in area->pages[i].
  3. It bumps the global counter: atomic_long_add(area->nr_pages, &nr_vmalloc_pages) — this is what /proc/meminfo’s VmallocUsed reflects. If __GFP_ACCOUNT is set, each page is charged to the memcg (MEMCG_VMALLOC).
  4. 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 at addr. This is the expensive, vmalloc-specific work kmalloc never 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 clears VM_NO_GUARD, because removing a guard would compromise the neighbouring mapping. vunmap() is its teardown (frees the mapping, not the pages, unless VM_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 sets VM_IOREMAP and records phys_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’s VmallocUsed exactly equals nr_vmalloc_pages << PAGE_SHIFT on v6.12, or whether vmap/ioremap (non-VM_ALLOC) regions are excluded from that counter (the counter is only bumped in __vmalloc_area_node, which serves VM_ALLOC allocations, so pure ioremap/vmap mappings may not be counted in VmallocUsed). Reason: the meminfo wiring was not directly inspected for this note. To resolve: read mm/vmstat.c/fs/proc/meminfo.c at v6.12 and confirm the VmallocUsed source. uncertain

The Cost vs kmalloc

The trade-off is the whole point, and it falls out directly from the mechanism above:

Dimensionkmalloc (slab on direct map)vmalloc
Physical contiguityYes (one buddy run)No (scattered order-0 pages)
Page-table workNone (direct map pre-exists)Builds/tears down PTEs per allocation
TLB footprintMaps via large direct-map entries (often 2 MiB/1 GiB) → few TLB entriesOrder-0 PTEs → one TLB entry per 4 KiB; far more TLB pressure
Allocation speedFast (slab free-list pop)Slow (buddy pulls + page-table build + possible reclaim)
ContextGFP_ATOMIC works in atomic/IRQSleeps — never in interrupt/atomic context
DMA-safeYes (contiguous physical)No — pages are scattered; needs IOMMU/scatter-gather
Max sizeLimited by fragmentationLimited by the (huge) vmalloc window
Overrun guardNone by defaultGuard 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. Use kmalloc/dma_alloc_coherent for 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. Use vmalloc_to_page() (which walks the page tables) instead.
  • vmalloc in atomic contextBUG_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