Linux vmalloc and Page Faults-Free Linux Device Drivers Tutorial

PREV_LEC  |  NEXT_LEC

Linux vmalloc and Page Faults

Free Linux Kernel Development Course — Kernel Memory Management, Lecture 11

Chapter: Kernel Memory Management
Level: Intermediate
Kernel: 6.x mainline

The linux vmalloc function is one of the two big memory allocation tools every kernel module writer needs to understand, the other one being kmalloc(), which we covered in the previous lecture. While kmalloc() hands you a small, physically contiguous chunk of RAM in a hurry, vmalloc() takes a slower path and gives you a large block of memory that is contiguous only in the kernel’s virtual address space. In this free linux kernel development course lecture, we will build a small driver that uses vmalloc(), watch it in /proc/vmallocinfo, and then go one level deeper to understand the page fault mechanism that makes lazy memory allocation possible in the first place.

free embedded systems course
free linux development course
free linux device drivers course
free linux kernel development course
linux vmalloc function
page fault handling linux

What You Will Learn

vmalloc() vs kmalloc()
vmalloc / vzalloc / vfree API
Lazy memory allocation
Hard, soft, and unresolved page faults
/proc/vmallocinfo
Writing an original vmalloc driver

Prerequisites

Completed the kmalloc allocator lecture
Basic idea of virtual vs physical memory
A Linux VM or board to build/insmod modules
GCC and kernel headers installed

What Is vmalloc() in the Linux Kernel

vmalloc() allocates memory that is virtually contiguous but may be scattered across many non-contiguous physical pages. The kernel allocates individual pages from the page allocator, then builds new page table entries so that, from the CPU’s point of view, the whole block looks like one continuous range of addresses. This is exactly the same trick your operating system uses for user-space malloc() — it is just applied inside kernel space this time.

Because a fresh set of page table entries has to be created (and sometimes the kernel has to remap pages into a dedicated virtual range), vmalloc() is noticeably slower than kmalloc(). It is also not safe to use for DMA buffers, since DMA hardware needs a single contiguous physical address, and a vmalloc() region generally does not have one.

kmalloc vs vmalloc address mapping

kmalloc()

Virtual address range maps 1:1 to physical RAM (shifted by PAGE_OFFSET). Physically contiguous. Fast. Good for DMA.

vmalloc()

Virtual address range is a separate reserved zone. Physical pages can be scattered anywhere. Slower. Not safe for DMA.

kmalloc() vs vmalloc(): Quick Comparison

Property kmalloc() vmalloc()
Physical contiguity Yes No
Virtual contiguity Yes Yes
Speed Fast Slower (page table setup)
Max size Bounded by KMALLOC_MAX_SIZE Bounded by vmalloc address space
Safe for DMA Yes No
Typical use case Small structs, buffers Large software-only buffers (e.g. network/log buffers)

vmalloc Function Prototypes

All three functions live in <linux/vmalloc.h> and are still the standard interface on current mainline kernels:

#include <linux/vmalloc.h>

void *vmalloc(unsigned long size);
void *vzalloc(unsigned long size);   /* vmalloc + zero-fill    */
void vfree(void *addr);              /* frees a vmalloc region */

vmalloc() returns a pointer to the first byte of the block on success, or NULL on failure. vzalloc() does the same thing but also zeroes the memory before handing it back, which saves you an explicit memset(). Every block obtained with either function must eventually be released with vfree() — never with kfree().

Original Driver Example: ep_vmalloc_demo

The following original example allocates a 2 MB software buffer with vzalloc(), writes a pattern into it, and frees it on module removal. A buffer this size would very likely fail with kmalloc(), but is routine for vmalloc().

#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>

#define EP_VBUF_SIZE (2 * 1024 * 1024)  /* 2 MB */

static void *ep_vbuf;

static int __init ep_vmalloc_demo_init(void)
{
    ep_vbuf = vzalloc(EP_VBUF_SIZE);
    if (!ep_vbuf) {
        pr_err("ep_vmalloc_demo: vzalloc failed\n");
        return -ENOMEM;
    }

    memset(ep_vbuf, 0xAA, 64);  /* touch first 64 bytes */

    pr_info("ep_vmalloc_demo: allocated %d bytes at %px\n",
             EP_VBUF_SIZE, ep_vbuf);
    return 0;
}

static void __exit ep_vmalloc_demo_exit(void)
{
    vfree(ep_vbuf);
    pr_info("ep_vmalloc_demo: buffer freed\n");
}

module_init(ep_vmalloc_demo_init);
module_exit(ep_vmalloc_demo_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala vmalloc demo");

Build and Run

$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_vmalloc_demo.ko
$ dmesg | tail -3
$ sudo rmmod ep_vmalloc_demo
$ dmesg | tail -3

Expected Output

[ 1234.567] ep_vmalloc_demo: allocated 2097152 bytes at 000000006e2b1a90
[ 1240.112] ep_vmalloc_demo: buffer freed

Note: on current kernels, %px prints the raw pointer only when you are root and have permission; on production systems the kernel hashes pointers printed with plain %p for security. Use %px for debugging only.

Inspecting Allocations With /proc/vmallocinfo

Every active vmalloc() region in the system is listed in /proc/vmallocinfo, along with its size and the function that allocated it. While ep_vmalloc_demo is loaded, run:

$ sudo cat /proc/vmallocinfo | grep ep_vmalloc_demo
0xffffc90000abc000-0xffffc90000cbd000 2097152 ep_vmalloc_demo_init+0x1a/0x30 [ep_vmalloc_demo] pages=512 vmalloc

This confirms the 2 MB region (512 pages of 4 KB each) and shows which caller created it, which is extremely useful when debugging vmalloc leaks in real drivers.

Why Physical Memory Allocation Is Lazy

Here is something that surprises a lot of students: when you call vmalloc(), mmap(), or even plain brk(), the kernel does not necessarily hand you real, physical RAM right away. In most cases it only updates the page table to reserve a virtual address range. The actual physical page is attached later, the first time you really touch that memory. This strategy is called lazy allocation, and it exists so the kernel never wastes physical RAM on pages that a process declares but never actually uses.

The mechanism that makes lazy allocation work is the page fault. Whenever the CPU tries to access a virtual address whose page table entry does not yet have the right permission bits (or does not point to a physical page at all), the memory management unit raises a page fault interrupt, and control passes to the kernel’s page fault handler.

The Three Outcomes of a Page Fault

A page fault can be triggered by a read, a write, or an execute attempt. Once the CPU raises it, the page fault handler resolves it in one of three ways:

Page fault resolution paths

Soft fault

The page already exists somewhere in memory (e.g. another process’s working set). The handler simply attaches it and adjusts the page table entry. Fast, no I/O needed.

Hard fault

The page is not resident anywhere (e.g. it must be read from a memory-mapped file or swap). The handler performs I/O, possibly suspending the process while data is fetched.

Unresolved fault

The access was simply illegal (out-of-bounds, wrong permissions). The kernel sends SIGSEGV to the offending process, which is killed unless it has installed its own handler.

Fault type Cause Handler action Cost
Soft fault Page resident elsewhere in memory Attach existing page, fix up PTE Low
Hard fault Page must be fetched from disk/swap Perform I/O, may reschedule process High
Unresolved Illegal access / permission violation Deliver SIGSEGV Process may be killed
Page fault handling flow
CPU accesses virtual address
PTE missing / bad permission
MMU raises page fault
Kernel page fault handler runs
Soft / Hard fix  or  SIGSEGV

Important caution for driver writers: a page fault raised while the CPU is already inside an interrupt context leads to a double fault, and the kernel normally calls panic() in response. This is exactly why memory used inside interrupt handlers must be pre-allocated (typically with GFP_ATOMIC) rather than touched for the first time inside the interrupt itself — there must be no page fault possible at that point. If a second fault happens while the double fault is being handled, the CPU triggers a triple fault and the whole system reboots. This behavior is architecture-dependent, but the general rule — never let interrupt context sleep or fault — holds everywhere.

Real-World Use Cases

  • Large software buffers with no DMA requirement, such as ring buffers for logging subsystems.
  • Kernel module loading itself uses vmalloc-style mappings for code and data sections.
  • mmap()-backed device drivers rely on the same lazy-allocation and page-fault machinery to avoid pinning memory that user space has not touched yet.
  • File-backed memory mappings depend on hard faults to pull pages in from disk on demand.

Common Mistakes

Mistake Why It’s a Problem Fix
Using vmalloc() memory for DMA Not physically contiguous Use dma_alloc_coherent() or kmalloc()
Calling vfree() from interrupt context on older kernels May sleep internally in some paths Free from process context, or schedule work
Forgetting vfree() on every error path Memory leak Use a single cleanup label / goto pattern
Touching large memory inside an IRQ handler Risk of double fault / panic Pre-allocate before entering interrupt context

Best Practices

  • Prefer kmalloc()/kzalloc() for anything under a few KB; reach for vmalloc() only for large, software-only buffers.
  • Always check the return value of vmalloc()/vzalloc() for NULL.
  • Match every vmalloc()/vzalloc() with exactly one vfree().
  • When size is not known in advance and could be large or small, consider kvmalloc()/kvfree(), which automatically picks the right allocator.

Performance Considerations

vmalloc() pays a one-time cost building page tables at allocation time, so it should not be called in hot paths that run frequently. Allocate once at driver init/probe time and reuse the buffer for the driver’s lifetime whenever possible.

Security Considerations

Kernel pointers, including vmalloc addresses, are hashed when printed with %p to prevent leaking layout information to user space. Always zero sensitive buffers before vfree() if they held confidential data, since the underlying pages can be reused elsewhere.

Summary / Key Takeaways

  • vmalloc() gives virtually contiguous, physically scattered memory; kmalloc() gives both.
  • Memory allocation in Linux is lazy — physical pages are attached on first access, not at allocation time.
  • Page faults are resolved as soft faults, hard faults, or delivered as SIGSEGV.
  • Faulting inside interrupt context can panic the kernel — pre-allocate before you need it.

Conclusion

Understanding vmalloc() alongside the page fault mechanism gives you the full picture of how the Linux kernel manages memory: reserve a virtual range first, attach physical pages only when actually touched, and rely on the MMU and page fault handler to keep everything consistent. This foundation will make the next lecture on Copy-on-Write much easier to follow, since CoW is really just a clever, deliberate use of the same page fault trap.

Frequently Asked Questions

Is vmalloc() memory contiguous in physical RAM?

No. It is contiguous only in virtual address space. The underlying physical pages can be scattered anywhere in RAM.

Can I use vmalloc() memory for DMA transfers?

No, not directly. DMA hardware generally needs a single contiguous physical address range, so use kmalloc() or dma_alloc_coherent() instead.

What is the difference between vmalloc() and vzalloc()?

vzalloc() behaves exactly like vmalloc() but also zero-fills the returned memory before handing it back.

Why is vmalloc() slower than kmalloc()?

vmalloc() must build new page table entries and sometimes remap pages into a dedicated virtual range, while kmalloc() reuses an existing 1:1 mapping.

What triggers a page fault in the Linux kernel?

Any access (read, write, or execute) to a virtual address whose page table entry lacks the correct permission bits or has no physical page attached.

What is the difference between a hard fault and a soft fault?

A soft fault is resolved instantly because the page already exists in memory somewhere. A hard fault requires the kernel to perform I/O, such as reading from disk or swap.

What happens if a page fault occurs inside interrupt context?

It causes a double fault, which usually makes the kernel call panic(). This is why memory touched inside interrupt handlers must be pre-allocated.

How can I see how much vmalloc memory a driver has allocated?

Read /proc/vmallocinfo, which lists every active vmalloc region along with its size and allocating function.

 

Continue the Free Linux Kernel Development Course

Next up: how the kernel avoids copying memory using Copy-on-Write.

PREV_LEC  |  NEXT_LEC

Leave a Reply

Your email address will not be published. Required fields are marked *