Chapter 3: Linux Address Spaces and the Virtual Memory Layout
Chapter 3: Linux Address Spaces and the Virtual Memory Layout
While hardware translation mechanics operate in units of pages and physical frames, the Linux kernel organizes memory from an architectural perspective: dividing the 64-bit virtual address continuum into well-defined user-space segments and privileged kernel zones.
Within user space, an executing application requires code segments, global initialized data, uninitialized BSS buffers, a dynamically expanding heap, thread execution stacks, and memory-mapped file regions. In kernel space, the kernel maintains direct 1:1 physical memory mappings, dynamically allocated vmalloc pools, and fixmapped I/O windows.
This chapter details the virtual memory architecture of Linux: the process address space layout, kernel virtual memory zones, architecture-specific splits, ASLR randomization, the core data structures (mm_struct and vm_area_struct), system call primitives (mmap, brk, mremap), and a hands-on experiment inspecting live process memory maps via /proc/[pid]/maps.
The Process Address Space in Linux
On modern 64-bit architectures, the full 64-bit address space (\(16\text{ Exabytes}\)) is divided into two symmetrical halves separated by a non-canonical gap:
┌────────────────────────────────────────────────────────────────────────┐
│ 64-BIT VIRTUAL MEMORY MAP (x86-64) │
│ │
│ 0xFFFFFFFFFFFFFFFF ┌──────────────────────────────────────────────┐ │
│ │ High Kernel Fixmaps & VSYSCALL │ │
│ 0xFFFF888000000000 │ Direct Physical Memory Map (page_offset_base)│ │
│ │ (Maps all physical RAM 1:1 with kernel base) │ │
│ 0xFFFF800000000000 ├──────────────────────────────────────────────┤ │
│ │ │ │
│ │ NON-CANONICAL "HOLE" │ │
│ │ (Addresses invalid in hardware; 16 Exabytes) │ │
│ │ │ │
│ 0x00007FFFFFFFFFFF ├──────────────────────────────────────────────┤ │
│ │ User Stack (grows downward) │ │
│ │ Memory Mappings (mmap, shared libraries) │ │
│ │ Heap (brk - grows upward) │ │
│ │ BSS & Data Segments │ │
│ 0x0000000000400000 │ Text Segment (Executable Code) │ │
│ 0x0000000000000000 └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
- User Space (
0x0000000000000000to0x00007FFFFFFFFFFF): The lower \(128\text{ TiB}\). Accessible by unprivileged processes executing in Ring 3. - The Non-Canonical Hole: In 48-bit addressing, addresses from
0x0000800000000000to0xFFFF7FFFFFFFFFFFare rejected by CPU hardware. Dereferencing an address inside this range triggers a General Protection Fault (#GP). - Kernel Space (
0xFFFF800000000000to0xFFFFFFFFFFFFFFFF): The upper \(128\text{ TiB}\). Accessible only in Ring 0 supervisor mode. Protected by hardware page table supervisor bits (U/S = 0).
User-Space Layout: Code, Data, Heap, Stack, and Mapped Regions
A process’s user-space memory is partitioned into distinct logical memory segments:
- Text Segment (
.text): Contains compiled machine code instructions. Marked read-only and executable (R-X). Multiple running instances of the same binary share the identical physical text pages. - Data Segment (
.data): Stores globally initialized variables (e.g.,int global_var = 42;). Marked read-write, non-executable (RW-). - BSS Segment (
.bss): Stores uninitialized global and static variables (Block Started by Symbol). Does not occupy disk space in the ELF binary; initialized to zeros on first write via demand paging. - Heap: Contiguous memory managed by
brk()andsbrk(), growing upward toward higher addresses. Used by runtime allocators (malloc) for dynamic memory. - Memory Mapping Segment (mmap): Placed below the stack, growing downward. Accommodates dynamically loaded shared libraries (
libc.so), memory-mapped files, and large anonymous allocations. - User Stack: Stores local function variables, saved frame pointers, and return addresses. Grows downward from the top of user space (
0x7FFFFFFFFFFF).
Kernel Virtual Address Space and Direct Mapping
The upper \(128\text{ TiB}\) kernel address space contains specialized memory areas:
- Direct Mapping (
page_offset_base): A massive virtual window (beginning at0xffff888000000000on x86-64) that maps the entirety of physical system RAM 1:1. A physical address \(P\) corresponds to virtual address: \[V = P + \text{PAGE\_OFFSET}\] Allows the kernel to access any physical frame without modifying page tables. - vmalloc Pool (
VMALLOC_STARTtoVMALLOC_END): Used to allocate virtually contiguous memory buffers that may be physically non-contiguous. - vmemmap: An array of
struct pageheaders representing every physical page frame in system memory.
The mm_struct and vma_struct: Core Data Structures
In kernel space, an active process’s memory space is represented by the struct mm_struct (declared in include/linux/mm_types.h):
struct mm_struct {
struct {
struct maple_tree mm_mt; /* Maple Tree organizing all VMAs */
unsigned long mmap_base; /* Base address of mmap area */
unsigned long task_size; /* Size of user address space */
pgd_t *pgd; /* Pointer to Page Global Directory (CR3) */
atomic_t mm_users; /* Number of threads sharing this mm */
atomic_t mm_count; /* Reference count on struct itself */
unsigned long total_vm; /* Total allocated pages */
unsigned long locked_vm; /* Pages locked in RAM (mlock) */
unsigned long start_code, end_code;
unsigned long start_data, end_data;
unsigned long start_brk, brk; /* Heap boundaries */
unsigned long start_stack; /* Stack base address */
};
};The Virtual Memory Area (struct vm_area_struct - VMA)
Every contiguous virtual memory range possessing uniform permissions is represented by a VMA:
struct vm_area_struct {
unsigned long vm_start; /* First virtual address (inclusive) */
unsigned long vm_end; /* End virtual address (exclusive) */
struct mm_struct *vm_mm; /* Parent mm_struct */
pgprot_t vm_page_prot; /* Access permissions (PROT_READ, etc.) */
unsigned long vm_flags; /* VM_READ, VM_WRITE, VM_EXEC, VM_SHARED */
struct file *vm_file; /* Backing file (NULL for anonymous memory) */
unsigned long vm_pgoff; /* Offset in file (in page units) */
const struct vm_operations_struct *vm_ops; /* VMA callback handlers */
};VMAs are indexed via a Maple Tree (mm_mt), a concurrent B-tree variant optimized for storing overlapping range intervals, allowing \(O(\log N)\) lookup during page fault resolution.
Memory Region Management with mmap, brk, and mremap
User-space processes modify their virtual address space through three primary system calls:
brk(void *addr): Moves the top of the process heap (theprogram break). Increasingbrkexpands heap virtual memory; decreasing it frees memory back to the kernel.mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset): Creates a new VMA.- Anonymous Mapping (
MAP_ANONYMOUS): Allocates RAM backed by zero-filled pages rather than a file; standard mechanism for large heap allocations. - File-Backed Mapping: Maps a disk file directly into the process address space; reads and writes translate into page cache operations without explicit
read()orwrite()syscalls.
- Anonymous Mapping (
mremap(void *old_address, size_t old_size, size_t new_size, int flags): Expands or shrinks an existing VMA, relocating it to a new virtual address if adjacent virtual space is occupied.
Practical Experiment: Inspecting Your Process’s Memory Map
Every running process exposes its complete VMA table through the procfs pseudo-filesystem:
# Launch a background shell and inspect its memory map
cat /proc/$$/maps | head -n 1500400000-00452000 r-xp 00000000 08:01 1234567 /usr/bin/bash # Text
00651000-00652000 r--p 00051000 08:01 1234567 /usr/bin/bash # Read-only Data
00652000-0065b000 rw-p 00052000 08:01 1234567 /usr/bin/bash # Data / BSS
01a2f000-01c4e000 rw-p 00000000 00:00 0 [heap] # Heap (brk)
7f8a1000-7f8b0000 r-xp 00000000 08:01 9876543 /lib64/libc.so.6 # Shared Lib
7ffd8a40-7ffd8a61 rw-p 00000000 00:00 0 [stack] # User Stack
7ffd8a78-7ffd8a7c r-xp 00000000 00:00 0 [vdso] # Kernel vDSO
Each row represents an active vm_area_struct: - 00400000-00452000: Virtual address range (vm_start - vm_end). - r-xp: Permissions (read, write, execute, private / copy-on-write). - 00000000: File offset. - 08:01 1234567: Storage device major:minor and inode number. - [heap] / [stack]: Descriptive label.