LESSON 03 · BEGINNER4 min read

Virtual Memory, Page Tables, and Process Address Space Architecture

Explore operating system virtual memory architecture: Memory Management Units (MMU), 4KB page translations, Page Faults, and segment layouts (Text, Data, BSS, Heap, Stack).

C++20 / C++23Free to readModule 1 of 11
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Trace how the CPU Memory Management Unit (MMU) translates virtual addresses to physical RAM via hierarchical page tables
  • Dissect the segments of a standard 64-bit Linux process address space (.text, .rodata, .data, .bss, heap, memory map, stack)
  • Diagnose segmentation faults (SIGSEGV) caused by invalid virtual memory access and permission violations

01Virtual Memory Architecture and the Hardware MMU

In modern operating systems (Linux, macOS, Windows), user-space applications never interact with physical RAM addresses directly. Instead, every process executes within an isolated, continuous Virtual Address Space managed cooperatively by the operating system kernel and the CPU's hardware Memory Management Unit (MMU).

On 64-bit x86_64 architectures, processes typically utilize a 48-bit canonical virtual address space (providing 256 Terabytes of addressable memory), split evenly between user space (lower half) and kernel space (upper half). Physical memory is divided into fixed-size chunks called Page Frames (typically 4KB in size, though Huge Pages can be 2MB or 1GB).

The MMU uses a multi-level page table structure (PML4, PDPT, PD, PT) to map virtual page numbers (VPN) to physical frame numbers (PFN). To prevent memory lookup latency from doubling on every memory access, the CPU caches recent page translations in the Translation Lookaside Buffer (TLB).

notes.txtPlain text
+-----------------------------------------+ 0x7FFFFFFFFFFF (Top of User Space)| Environment Variables & CLI Arguments   |+-----------------------------------------+| User Stack (Grows DOWNWARD)             || [Stack frames, local variables]         ||                   |                     ||                   v                     ||                                         ||                   ^                     ||                   |                     || Memory Mapping Segment (mmap)           || [Shared libraries (.so), dynamic files] |+-----------------------------------------+| Heap (Grows UPWARD via brk/sbrk)        || [Dynamic allocations via malloc/new]    |+-----------------------------------------+| .bss (Uninitialized global/static data) | (Zero-filled by OS)+-----------------------------------------+| .data (Initialized global/static data)  | (Read/Write)+-----------------------------------------+| .rodata (Read-only constants & strings) | (Read-Only)+-----------------------------------------+| .text (Executable machine instructions) | (Read & Execute, NO Write)+-----------------------------------------+ 0x0000000000400000 (Base address)

02Dissecting the Standard Process Memory Segments

Every compiled binary loaded into memory is structured into specialized segments, each configured with specific hardware page permissions (Read, Write, Execute):

.text Segment: Contains compiled CPU machine instructions. Marked Read + Execute (RX). Any attempt to write to this memory immediately triggers a CPU exception (SIGSEGV).

.rodata Segment: Stores read-only data, such as string literals ("Hello World") and const global variables. Marked Read-Only (R).

.data Segment: Stores global and static variables that have non-zero initial values (e.g., int g_counter = 42;). Marked Read + Write (RW).

.bss Segment: Stores global and static variables that are uninitialized or initialized to zero (e.g., int g_buffer[1024];). It consumes zero disk space in the executable binary; the OS allocates zeroed pages on demand upon process launch.

03Page Faults and Demand Paging

When a program requests a dynamic allocation of 1GB via malloc() or operator new, the operating system kernel does not immediately allocate 1GB of physical RAM. Instead, it reserves virtual address space and marks the corresponding page table entries as not present (Demand Paging).

When the CPU executes an instruction that reads or writes to an unbacked virtual page, the MMU fails to translate the address and raises a hardware interrupt: a Page Fault. The OS kernel's page fault handler intercepts the interrupt, allocates a physical 4KB RAM frame, updates the page table entry, and resumes CPU execution transparently.

A Minor Page Fault occurs when the required page is already in memory (e.g., shared library cache). A Major Page Fault occurs when the data must be read from slow physical disk storage (e.g., memory-mapped files or swapping).

04Segmentation Faults: The Hardware Boundary Violation

A Segmentation Fault (SIGSEGV) is not an arbitrary C++ runtime crash; it is an operating system signal delivered to the process when an instruction attempts an illegal memory access detected by the hardware MMU.

Common causes include: 1. Dereferencing a NULL pointer (accessing address 0x0, which is intentionally unmapped to trap invalid pointers). 2. Writing to read-only memory (e.g., attempting to overwrite a string literal in .rodata). 3. Stack overflow: exhausting the stack segment and colliding with the memory guard page. 4. Accessing unallocated virtual addresses beyond the process heap limit.

LET IT CLICK

The ideas worth keeping.

  • Modern processes execute inside an isolated virtual address space translated by the hardware MMU.
  • Process memory is organized into distinct segments: .text (code), .rodata, .data, .bss, Heap, and Stack.
  • Segmentation faults (SIGSEGV) indicate hardware MMU violations, such as writing to read-only memory or null dereferencing.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Identify which memory segment each of the fol

Identify which memory segment each of the following variables resides in: a string literal, a local function variable, an uninitialized global array, and a dynamic malloc buffer.

your_turn_1.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
// 1. "Hello World" -> .rodata segment (read-only data)// 2. localVar -> Stack segment// 3. uninitBuffer -> .bss segment (zero-initialized global)// 4. Pointer variable 'p' resides on the Stack; the allocated array memory resides on the Heap

Distinguish between the pointer variable itself (on stack) and the dynamically allocated memory on heap.

02

Exercise 2: Explain why attempting to modify a string lit

Explain why attempting to modify a string literal like char* s = "test"; s[0] = 'b'; triggers a segmentation fault.

your_turn_2.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
// The string literal "test" is placed in the .rodata (read-only data) segment of the binary.// The MMU marks pages containing .rodata with Read-Only (no Write) permissions.// When the CPU executes an instruction attempting to write 'b' to s[0], the MMU detects// the permission violation and triggers a page fault exception, which the OS converts into SIGSEGV.

Check the page permissions of the .rodata segment.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What hardware component translates virtual memory addresses into physical RAM frames?
02Why does the .bss segment consume virtually zero disk space inside the compiled executable binary?
03What is a major page fault in operating system memory management?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Identify which memory segment each of the fol

Identify which memory segment each of the following variables resides in: a string literal, a local function variable, an uninitialized global array, and a dynamic malloc buffer.

EXPECTED OUTPUT
Build completed. Binary executed.
[Exit code: 0]
main.cppC++
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.