Virtual Memory · Linux userspace / kernel ABI

The process address space and /proc/maps

Distinguishes how ELF segments, the heap, shared objects, the stack, and vDSO are placed as VMAs from the later faults that attach actual pages.

Series
17 / 38
Build
cc -std=c17 -Wall -Wextra -O2 address_space.c -o address_space
Run
./address_space
Kernel
Linux 6.18.37 LTS

Does a visible virtual-address range mean physical memory has already been allocated?

A VMA describes protection and backing rules for a contiguous virtual-address range. Creating a VMA with mmap or exec does not immediately prepare every PTE and physical page. On access, a page fault connects a file page, zero page, or anonymous page.

/proc/PID/maps shows ranges and permissions, while smaps reports RSS/PSS and private/shared accounting. The single label 'heap' cannot explain malloc objects, the brk VMA, and allocator mmap arenas all at once.

Structure diagram

Figure 1. VMA and physical-page preparation points in a 64-bit process
0x7fff… high addresses0x0040… low addresses
thread stackgrow-down · guard
shared librariesfile-backed RX/RW
mmap anonymous 256 MiBVMA exists · RSS≈0 before access
heapbrk VMA · allocator chunks
main executabletext · rodata · data · bss

A visible VMA in maps does not mean every physical page is resident. A private page appears in an anonymous range on the first write fault.

Call path

Figure 2. From userspace code to observable results
ELF/mmap/brk create VMA
virtual address PTE may not exist yet
CPU access page fault
fault handler select backing page
PTE/TLB access becomes possible

Address reservation, VMA registration, PTE installation, physical-page allocation, and RSS accounting happen at different times.

Figure 3. Major points along the kernel-internal path
do_mmap validate range/prot
vma_merge update VMA tree
handle_mm_fault fault type
do_anonymous_page zero/private page
set_pte mapping publish

This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.

Linux 6.18.37 LTS source locations

Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.

FileFunction / structureWhat to inspect
mm/mmap.c do_mmap(), mmap_region() Choose a VMA range and register it in the maple tree
mm/memory.c handle_mm_fault(), do_anonymous_page() Prepare a PTE and anonymous page on access
fs/proc/task_mmu.c show_map_vma(), show_smap() Inspect which fields /proc maps/smaps print

Complete runnable example

The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.

Buildcc -std=c17 -Wall -Wextra -O2 address_space.c -o address_space
01#define _DEFAULT_SOURCE
02#include <stdio.h>
03#include <stdlib.h>
04#include <sys/mman.h>
05#include <unistd.h>
06
07int main(void)
08{
09    long page = sysconf(_SC_PAGESIZE);
10    size_t length = 256UL * 1024 * 1024;
11    unsigned char *area = mmap(NULL, length, PROT_READ | PROT_WRITE,
12                               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
13    if (area == MAP_FAILED)
14        return 1;
15
16    printf("pid=%ld area=%p length=%zu page=%ld\n",
17           (long)getpid(), (void *)area, length, page);
18    puts("mapping created; press Enter");
19    getchar();
20    for (size_t offset = 0; offset < length; offset += (size_t)page)
21        area[offset] = 1;
22    puts("pages touched; press Enter");
23    getchar();
24    return munmap(area, length) != 0;
25}

Code notes

Source line 9sysconf(_SC_PAGESIZE)

Reads the page size from the execution environment instead of fixing it at 4096. Huge pages and base pages must be distinguished separately.

Source line 10256UL * 1024 * 1024

Reserves a large virtual range, but RSS need not grow by the same amount immediately after mmap returns.

Source line 12MAP_PRIVATE | MAP_ANONYMOUS

Creates a private VMA without file backing. An initial read may use the shared zero page, while a write can create a private anonymous page.

Source line 21area[offset] = 1

Writes to each base page, inducing minor faults and physical-page allocation.

Source line 24munmap(area, length)

Removes the VMA range and releases page-table entries and references. Truncated VMAs may be split or merged.

Detailed behavior

01

ASLR and PIE change addresses

The PIE main executable, shared objects, stack, and mmap base can change on every exec. Interpreting a runtime address requires combining the symbol offset with the mapping load bias.

A design that stores an absolute pointer in a log and reuses it in the next execution is invalid.

02

RSS and PSS answer different questions

RSS counts pages resident in a process's page tables and includes a shared page in every process. PSS divides the cost of a shared page by its mapping count to estimate proportional usage per process.

When examining allocator fragmentation, consider virtual size, RSS, and anonymous private dirty memory together.

03

Major and minor faults are not errors

A minor fault is a normal demand-paging event that can populate a page table without disk I/O. A major fault means the task had to wait for backing data from storage.

A latency-sensitive path may touch pages in advance or consider an mlock policy, trading memory pressure and startup cost for predictability.

Objects and lifetimes

ObjectCreation and releaseValues to inspect
vm_area_structCreated by mmap/exec/brk and removed by munmap/exec/exitvm_start/end, vm_flags, vm_file
PTEInstalled by fault or prefault and removed by reclaim/unmappresent, writable, dirty, accessed
physical page/folioObtained by fault/read-ahead and reclaimable after mapping/references disappearmapcount, refcount, dirty

Failure conditions and common misconceptions

Observed symptomLikely causesHow to verify
VIRT is large but RSS is smallThe range was only reserved and its pages were not accessedCompare maps with Rss in smaps
The first access has latencyA demand fault or file I/Operf faults, mincore, major faults
mmap ENOMEMVA range, map count, commit limitvm.max_map_count, overcommit, address layout

Verify it yourself

  1. Compare Rss and Private_Dirty in /proc/PID/smaps_rollup before and after the first Enter.
  2. Compare zero-page and private-page accounting between a read-only loop and a write loop.
  3. Apply madvise(MADV_HUGEPAGE/NOHUGEPAGE) and compare AnonHugePages with the number of faults.
Run./address_space
Traceperf stat -e page-faults,minor-faults,major-faults ./address_space

Primary sources