Beginner–Intermediate
2 of 2
VMAs & Memory Regions
In Part 1 of this free Linux kernel development course tutorial, we learned how the Process Virtual Address Space (VAS) is organized into user space and kernel space. We saw the major segments — text, data, BSS, heap, stack — and learned how to view them through /proc/PID/maps. Now, in Part 2, we go one layer deeper: we examine the kernel data structure that actually implements each of those segments. That structure is called the Virtual Memory Area, or VMA.
Understanding VMAs is critical for anyone pursuing free Linux kernel programming or writing Linux device drivers, because VMAs are the structures you encounter directly when implementing mmap() in a driver, handling page faults, or doing memory-mapped I/O from the kernel side.
What You Will Learn
- What a Virtual Memory Area (VMA) is and why the kernel needs it
- How the kernel stores and manages VMAs for a process
- The important fields inside the vm_area_struct kernel structure
- How VMAs relate to what you see in /proc/PID/maps
- How VMAs are used in Linux device drivers (the mmap file operation)
- VMA flags and protection bits explained clearly
- How page faults and demand paging connect to VMAs
- Best practices for working with VMAs in kernel development
What Is a Virtual Memory Area (VMA)?
Every line you see in /proc/PID/maps represents one Virtual Memory Area (VMA). A VMA is the kernel’s internal representation of a contiguous range of virtual addresses that share the same properties — the same permissions, the same backing store, and the same behavior when accessed.
Think of the user space VAS as a large, mostly empty number line. A VMA is a labeled, colored segment painted onto that number line, saying “this range of addresses belongs to the program’s text segment, is read-only and executable, and is backed by the program’s ELF binary on disk.” The kernel creates a VMA for each distinct segment in the process.
In this free Linux kernel development course, we will look at how these VMAs are actually stored in the kernel and what information each one carries.
The vm_area_struct: The Kernel’s VMA Data Structure
In the Linux kernel source, each VMA is represented by a C structure called vm_area_struct, defined in linux/mm_types.h. Every VMA in a process has one instance of this structure allocated in kernel memory.
The key fields you need to understand for this free Linux kernel programming course are:
vm_start and vm_end
These two fields define the exact virtual address range this VMA covers. The range is half-open: vm_start is the first byte address that belongs to this VMA, and vm_end is the first byte address that does not belong to it. Both values are always page-aligned (multiples of the system page size, typically 4096 bytes).
You can compute the size of a VMA as:
size_in_bytes = vma->vm_end - vma->vm_start;
vm_flags: VMA Permission and Behavior Flags
The vm_flags field is a bitmask that describes both the permissions and the behavior of the VMA. The most important flags for this free Linux kernel development course are:
| Flag | Meaning |
|---|---|
| VM_READ | Pages in this VMA may be read |
| VM_WRITE | Pages in this VMA may be written |
| VM_EXEC | Pages in this VMA may be executed (code) |
| VM_SHARED | Pages are shared between processes (not private COW) |
| VM_MAYREAD / VM_MAYWRITE / VM_MAYEXEC | Maximum allowed permissions (used during mprotect calls) |
| VM_GROWSDOWN | VMA can grow downward (stack) |
| VM_GROWSUP | VMA can grow upward (some architectures) |
| VM_IO | This VMA maps device I/O memory — important for driver mmap |
| VM_PFNMAP | VMA maps raw PFNs (page frame numbers), not normal pages — used in device drivers |
| VM_DONTEXPAND | Cannot be expanded with mremap |
| VM_LOCKED | Pages are locked in RAM (not swappable) |
vm_ops: VMA Operations
The vm_ops field points to a vm_operations_struct, which is a table of function pointers. These functions are callbacks that the kernel calls when specific events happen on this VMA. The most important callback for Linux device drivers is:
The fault() callback is at the heart of demand paging: the kernel does not load every page of a program into RAM when it starts. Instead, it creates the VMA but leaves the page table entries empty. The first time a program touches an address in that VMA, the CPU raises a page fault. The kernel’s page fault handler looks up which VMA the faulting address belongs to, then calls that VMA’s fault() function to load the required page into RAM.
mm_struct: The Process Memory Descriptor
All the VMAs for a single process are managed through a structure called mm_struct. Every process has exactly one mm_struct (or shares one, in the case of kernel threads). This is the top-level memory descriptor for the process.
r-xp
rw-p
…
The kernel finds the mm_struct for the current process via current->mm (where current is a pointer to the current process’s task_struct). Inside mm_struct, the field mmap points to the first VMA in a sorted linked list. In kernels from 6.1 onwards, a maple tree structure is used for faster lookup, but the same concept applies.
VMAs and Demand Paging: How Pages Are Loaded On Demand
One of the most important jobs of the VMA system is enabling demand paging. When the kernel loads a program, it does not immediately copy the entire executable binary into RAM. Instead, it:
- Creates VMAs describing each segment (text, data, etc.)
- Leaves the actual page table entries empty (not present)
- Waits until the CPU tries to access a specific address
- Receives the resulting page fault interrupt
- Looks up which VMA contains the faulting address
- Calls that VMA’s fault() handler to load the needed page from disk into RAM
- Updates the page table entry and resumes the CPU from the faulting instruction
From the user program’s perspective, none of this happens. The program simply accesses memory and everything works. The entire page fault, disk read, and page table update happen invisibly inside the kernel. This is one of the most elegant aspects of the virtual memory system.
VMAs in Linux Device Drivers: The mmap File Operation
For anyone learning this free Linux kernel programming course with an interest in writing device drivers, VMAs are directly encountered when implementing the mmap() file operation in a character device driver.
When a user program calls mmap() on your device file, the kernel:
- Creates a new VMA for the requested address range
- Calls your driver’s .mmap file operation callback
- Passes a pointer to the newly created VMA to your callback
- Expects your driver to set up the page table entries (or set vm_ops) so the user can access the device memory
A Basic Driver mmap Implementation
Here is a minimal example of how a character device driver implements mmap() to map a region of physical device memory (for example, an MMIO buffer) into user space:
static int mydriver_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long pfn;
unsigned long size;
size = vma->vm_end - vma->vm_start;
/* Physical address of device memory, converted to page frame number */
pfn = DEVICE_PHYS_ADDR >> PAGE_SHIFT;
/*
* VM_IO: marks this VMA as device I/O memory.
* VM_DONTEXPAND: prevents mremap from expanding this VMA.
* These flags are mandatory for device memory mappings.
*/
vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
/* Set non-cacheable page protection for device registers */
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/*
* remap_pfn_range: maps physical pages (by PFN) into the VMA.
* Returns 0 on success, negative on failure.
*/
if (remap_pfn_range(vma,
vma->vm_start,
pfn,
size,
vma->vm_page_prot)) {
return -EAGAIN;
}
return 0;
}
How Many VMAs Does a Typical Process Have?
The number of VMAs in a process depends on how many distinct memory regions have been mapped. A minimal statically-linked program might have only 3–5 VMAs. A typical dynamically-linked application that uses several shared libraries might have 50–100 VMAs. A complex application like a web browser or JVM can easily have several hundred to a few thousand VMAs.
You can check the VMA count for any process by looking at the VmPeak / VmSize and VmMapped fields in /proc/PID/status, or by counting lines in /proc/PID/maps:
wc -l /proc/$(pgrep firefox)/maps
Best Practices When Working With VMAs in Kernel Development
When writing kernel code that iterates over VMAs, always acquire the appropriate lock (mmap_read_lock(mm) for read access) before walking the VMA list. Failing to do so is a race condition that can cause kernel crashes or data corruption.
The kernel provides the helper function find_vma(mm, addr) to efficiently find the VMA that contains or starts after a given virtual address. Use this instead of walking the list manually.
Always set VM_IO for device I/O memory mappings. Set VM_PFNMAP when using raw PFN mappings. These flags change how the kernel’s memory management treats the VMA and are critical for correct driver behavior.
If your driver needs to map memory lazily (one page at a time, on demand), implement the fault() callback in a vm_operations_struct and assign it to vma->vm_ops in your mmap() implementation.
Key Takeaways
Frequently Asked Questions (FAQ)
Conclusion
Virtual Memory Areas are the kernel’s way of giving precise meaning to every byte in a process’s user space. They are not just abstract data structures — they directly control what happens when your program accesses memory, what the CPU is allowed to do with each page, and how device drivers expose hardware registers to user programs.
In this free Linux kernel development course tutorial, we walked through the vm_area_struct in detail, explained the mm_struct that ties all VMAs together, showed how demand paging works through the fault callback, and demonstrated how VMAs appear in the context of a character device driver’s mmap() implementation.
Together, Parts 1 and 2 of this tutorial give you a solid foundation in Linux process memory management. These concepts will reappear constantly as you go deeper into the kernel — in memory allocation internals, page table management, slab allocators, and more advanced Linux device driver topics. All of that is covered in the rest of this free Linux kernel programming course on EmbeddedPathashala.
EmbeddedPathashala — Free Linux Kernel, Linux Device Drivers, Embedded Systems, and BLE courses
